React的useEvent 和 ahooks 的 useMemorizedFn 的深度分析和对比

父组件

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、由于每次父组件更新,都会执行到 下面的代码。这个是首要条件!
const changeFun_useEvent = useEvent(() => {
console.log('useEvent closure 里的  State', State);
});

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 。

此文纯属个人理解,有偏差望指正。

你可能感兴趣的:(react.js,javascript,ecmascript)