父组件
const TestParent: React.FC<any> = () => {
const [State, setState] = useState(0);
const changeFun = useCallback(() => {
console.log('useCallback closure 里的 State', State);
}, [State]);
const changeFun_useEvent = useEvent(() => {
console.log('useEvent closure 里的 State', State);
});
const changeFun_usememrized = useMemorizedFn(() => {
console.log('useMemorizedFn closure 里的 State', State);
});
return (
<div style={{ width: '100%', padding: '20px', backgroundColor: 'green' }}>
父组件的state: {State}
<button onClick={() => setState(Math.random())}>
click-change state
</button>
<TestChild changeFun={changeFun_useEvent}></TestChild>
<TestChild changeFun={changeFun_usememrized}></TestChild>
</div>
);
};
子组件
const TestChild = (props: any) => {
const { changeFun } = props;
useEffect(() => {
console.log('changeFun-》地址change了,导致了子组件刷新了。');
}, [changeFun]);
return (
<div style={{ backgroundColor: 'pink' }}>
子组件
<br />
<br />
<button onClick={changeFun}>child click - get parent's state </button>
</div>
);
};
React.memo(TestChild);
useEvent
已被官方废弃
useEvent 源码解析
【解释1】
1、由于每次父组件更新,都会执行到 下面的代码。这个是首要条件!
2、其次,每次执行都会传递过来一个新的handler,自然带来了新的闭包、新的数据。
3、最后,useLayoutEffect的作用是每次数据发生更新,自动的去执行里面的方法。
4、 所以 handlerRef.current 就能拿到带着最新数据的一个handler
【解释2】
useCallback 并且依赖为[],只执行一次,地址也就永远不会变了。
那么,useEvent()return出去函数的地址永不变化。
可以解释为
1、useEvent() return 出去了一个对象,为 obj = {A:{}}
2、 useCallback里的箭头函数return出去的是这个 fn=handlerRef.current;相当于一个属性 A
由于handlerRef.current指向地址是不停变化的,所以A得指向地址就是不停变化的。
3、因此我们真正使用 changeFun_useEvent=useEvent(xx)的时候, 拿到的是obj。永远不变的地址。
changeFun_useEvent()调用的时候拿到的就是obj.A了。就是最新的带着最新数据的一个handler
function useEvent(handler: any) {
const handlerRef = useRef(null);
// In a real implementation, this would run before layout effects
// 这行保证handlerRef一直处于,最新的闭包、拿到最新的数据。【解释1】
useLayoutEffect(() => {
console.log('useLayoutEffect执行了');
handlerRef.current = handler;
});
//这行保证返回的数据地址永远不变,但是执行的时候拿到的最新的。【解释2】
return useCallback((...args: any) => {
// In a real implementation, this would throw if called during render
const fn = handlerRef.current;
return fn(...args);
}, []);
}
useMemorizedFn源码解析
源码地址
【解释1】
fn在这里做依赖的原因: 由于每次父组件重新执行,都会走到useMemorizedFn里并传过来一个新的箭头函数。所以fn每次地址都是新的。也就带来了新数据的闭包。fnRef.current也就是解释2里的A的地址每次都是新的,带着新的数据的闭包。
【解释2】
这里可以解释为: const obj = {A:{}} ;
memoizedFn.current = obj;
fnRef.current = A
当执行memoizedFn.current的时候也就是去访问并执行了 obj.A
function useMemorizedFn(fn: any) {
const fnRef = useRef(fn);
// 这行保证fn一直处于,最新的闭包、拿到最新的数据。【解释1】
//这里为什么不直接 fnRef.current = fn ,可参考官网。总结就是 fnRef.current的需要放到useMemo。否则reactdev tool监听不到变化。
fnRef.current = useMemo(() => {
console.log('useMemo执行了'); // 父级改变就执行
return fn;
}, [fn]);
const memoizedFn = useRef<any>();
// 下面保证fn引用地址不变、且每次都能拿到最新的闭包 【解释2】
if (!memoizedFn.current) {
memoizedFn.current = function (this: any, ...args: any) {
return fnRef.current.apply(this, args);//立即执行、改变this指向、传递参数 3个作用
};
}
return memoizedFn.current;
}
总结
由此我们看出。其实二者的原理和出发点都是一致的,都是返回的是个固定的对象obj,该对象地址不变,但是调用的方法的时候相当于调用了obj.A,此属性的指向是会一直更新的。
只不过更新的时候 :useevent用了useLayoutCallback做更新,useMemorizedFn则使用了useMemo。
保持地址不变的时候:useevent用的是useCallback ,useMemorizedFn使用的一个 !memoizedFn.current + 新固定的function 。
此文纯属个人理解,有偏差望指正。