react hooks的短板,官方终于出手弥补
如果你喜欢我的文章,请给个点赞支持一下。
我们在使用hooks的时候都会存在闭包陷阱,这是react保证依赖正确的做法。比如以下的代码。
function App() {
const [text, setText] = useState('');
const onClick = useCallback(() => {
sendMessage(text);
}, []);
return <SendButton onClick={onClick} />;
}
我们预期是点击onClick能传递text新的值。但是由于useCallback缓存,形成了闭包所以最后你得到结果是sendMessage('')
hooks上手容易,但是如果不去思考,也很容易让开发者写出有bug的代码。有时候有些人写的hooks忘了添加依赖导致排除很久。
React 新 hooks useEvent
在官网RC的版本中引入了一个新的hooks,useEvent
当前还处于RFC(Request For Comments)阶段。
他用于定义一个函数,这个函数有2个特性:
- 在组件多次
render
时保持引用一致 - 函数内始终能获取到最新的
props
与state
上面的例子使用useEvent
改造后:
function App() {
const [text, setText] = useState('');
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
在Chat
组件多次render
时,onClick
始终指向同一个引用。
并且onClick
触发时始终能获取到text
的最新值。
之所以叫useEvent
,是因为React
团队认为这个Hook
的主要应用场景是:封装事件处理函数。这将会让我们更好的做DDD领域设计驱动
实现
useEvent的实现其实也不是很难的。
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);
}, []);
}
官网还处于RC阶段,目前的项目怎么处理呢?
从上面的例子来看实现一个useEvent并不难,也有很多开源库实现了类似的功能,比如start最多的hooksahooks
就有这么一个hooksuseMemoizedFn
。
这不过 useMemoizedFn
定位于缓存各种函数。
function useMemoizedFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn);
// 更新fnRef.current
fnRef.current = useMemo(() => fn, [fn]);
// ...省略代码
}
useEvent
定位于处理事件回调函数这一单一场景。
useLayoutEffect(() => {
handlerRef.current = handler;
});
官方这么做的是为了更加稳定,能否获取到最新的state
与props
取决于handlerRef.current
更新的时机。在上面模拟实现中,useEvent
更新handlerRef.current
的逻辑放在useLayoutEffect
回调中进行。而事件回调触发的时机显然在视图完成渲染之后,所以能够稳定获取到最新的state
与props
。
useMemoizedFn
,fnRef.current
的更新时机是useMemoizedFn执行时(即组件render时)
在React18启用并发更新后,组件render的次数与时间都不确定。有后端并发编程的同学都知道并发是无顺序的。所以导致useMemoizedFn
的更新时机也是不确定。从而增加了并发更新下的潜在风险。
可以说useEvent
通过限制handlerRef.current
更新时机,进而限制应用场景,最终达到稳定的目的。
转载自:https://juejin.cn/post/7095195845938642957