Hello everyone, I'm Casson.
We know that there is a so-called closure trap when using Hooks
, consider the following code:
function Chat() {
const [text, setText] = useState('');
const onClick = useCallback(() => {
sendMessage(text);
}, []);
return <SendButton onClick={onClick} />;
}
We expect that after clicking sendMessage
will pass the latest value of text
.
However, in fact, since the callback function is cached by useCallback
to form a closure, the effect of the click is always sendMessage('')
.
This is the closure trap .
A workaround for the above code is to add a dependency to useCallback :
const onClick = useCallback(() => {
sendMessage(text);
}, [text]);
但是这么做了后,每当依赖项( text
)变化, useCallback
会返回一个全新的onClick
引用, useCallback
The role of caching function references .
The emergence of the closure trap has increased the threshold for getting started with Hooks
, and also made it easier for developers to write code with bug
.
Now, React
the official team is going to fix this problem.
Welcome to join the human high-quality front-end framework group , with flying
useEvent
The solution is to introduce a new native Hook
useEvent
.
It is used to define a function, this function has 2 properties:
- Keep references consistent across components multiple times
render
- The function can always get the latest
props
andstate
The above example uses useEvent
after transformation:
function Chat() {
const [text, setText] = useState('');
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
When Chat
component multiple times render
, onClick
always points to the same reference.
And onClick
can always get the latest value of text
4f938572aa9e57462acf7ce99560fd79--- when it is triggered.
The reason why it is called useEvent
is because React
The team believes that the main application scenario of this Hook
is to encapsulate event processing functions .
Implementation of useEvent
The implementation of useEvent
is not difficult, the code is similar to the following:
function useEvent(handler) {
const handlerRef = useRef(null);
// 视图渲染完成后更新`handlerRef.current`指向
useLayoutEffect(() => {
handlerRef.current = handler;
});
// 用useCallback包裹,使得render时返回的函数引用一致
return useCallback((...args) => {
const fn = handlerRef.current;
return fn(...args);
}, []);
}
The whole consists of two parts:
- Returns a
useCallback
with no dependencies, so that the reference to the function is consistent every timerender
useCallback((...args) => {
const fn = handlerRef.current;
return fn(...args);
}, []);
- Update
handlerRef.current
at the right time, so that the actually executed function is always the latest reference
Differences with Open Source Hooks
Many open source Hooks
libraries have implemented similar functions (such as ahooks
in useMemoizedFn
)
useEvent
The differences with these open source implementations are mainly reflected in:
useEvent
is located in the single scene of processing event callback functions , while useMemoizedFn
is located in the cache of various functions .
So the question is, since the functions are similar, useEvent
should you limit your usage scenarios?
The answer is: for more stability.
useEvent
Whether you can get the latest state
and props
depends on the timing of handlerRef.current
update.
In the above simulation implementation, the logic of useEvent
update handlerRef.current
is placed in the useLayoutEffect
callback.
This ensures that handlerRef.current
is always updated after the view has finished rendering :
useLayoutEffect(() => {
handlerRef.current = handler;
});
The timing of the event callback is obviously after the view is rendered , so the latest state
and props
can be obtained stably.
Note: The actual update time in the source code is earlier, but it does not affect the conclusion here
Let's take a look at ahooks
in useMemoizedFn
, and the update timing of fnRef.current
is when useMemoizedFn is executed (that is, when the component is rendered):
function useMemoizedFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn);
// 更新fnRef.current
fnRef.current = useMemo(() => fn, [fn]);
// ...省略代码
}
When React18
enables concurrent updates , the number and timing of the component render
are uncertain.
Therefore, the update timing of useMemoizedFn
fnRef.current
is also uncertain.
This increases the potential risk when used with concurrent updates .
It can be said that useEvent
By limiting handlerRef.current
update timing, and then limit the application scenarios, and finally achieve the purpose of stability.
Summarize
useEvent
It is still in the RFC (Request For Comments) stage.
Many enthusiastic developers have suggested the naming of this Hook
, for example: useStableCallback
:
Another example: useLatestClosure
:
From these names, they obviously expand the application scenarios of useEvent
.
After the analysis of this article, we know that expanding application scenarios means increasing the risk of developers making mistakes .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。