React性能优化之常用hook的解析

useCallback

功能和缺点:生成一个根据依赖项变化的方法。可以防止函数每次生成新的地址。传给其他组件后影响性能。
缺点:1、每次更新依赖项的时候,返回的方法会更新,导致子组件重新渲染。2、不在依赖项里的数据就无法获取最新的。
解决方案(可以同时解决上面2个缺点):
1、useEvent :react官方出的一个,可以返回一个地址不变的方法,且没有依赖项的,可以提高性能,且数据永远最新的。(但是被废除了
2、useMemorizedFun:功能同上,是ahooks的方法。

useMemo

useLayoutEffect 和 useEffect

useLayoutEffect文档:参考1 的例子 + 参考2 的结论 参考1参考2

总结:

1、顺序问题。先useLayoutEffect,后useEffect
2、快照问题:useLayoutEffect使用了快照,用的是closure
3、执行时机:useLayoutEffect 和componentDidUpdate 是一样的,state更新后 ,页面渲染完成前调用。因此useLayoutEffect里setState并影响页面渲染时,不会出现闪动,但是可以获取到真实dom。具体请查看:点击
4、阻塞问题:useLayoutEffect里setState。会阻断渲染重新再走一次 ,虚拟dom-diff-渲染前-渲染-渲染后。
在这里插入图片描述
5、useLayoutEffect会影响服务端渲染的首屏加载
在这里插入图片描述
服务端渲染的解释(next.js):click

useEvent

useEvent参考文档:参考1 参考2
下图是 useEvent的大致的源码。精髓就是:handler放到ref.current里保存。返回的是ref.current的引用。当任意state发生变化时,即可冲洗执行ref.current = handler 此时的闭包环境就变了。
handler所在的闭包closure就变了。就能访问到所有变量的最新值了。
在这里插入图片描述

useMemorizedFun

官网使用方式
源码

注意: useMemorizedFun与useCallback 的使用场景。useLayoutEffect 和 useEffect的使用场景。

/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react/button-has-type */
import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

function useEvent(handler: any) {
  const handlerRef = useRef(null);

  // In a real implementation, this would run before layout effects
  // 这行保证handlerRef一直处于,最新的闭包、拿到最新的数据
  /*
  首先,由于每次父组件更新,都会执行到 下面的代码。这个是首要条件!
   const changeFun_useEvent = useEvent(() => {
    console.log('useEvent closure 里的  State', State);
   });
  其次,每次执行都会传递过来一个新的handler,自然带来了新的闭包、新的数据。
  最后,useLayoutEffect的作用是每次数据发生更新,自动的去执行里面的方法。
  所以 handlerRef.current 就能拿到带着最新数据的一个handler
  */
  useLayoutEffect(() => {
    console.log('useLayoutEffect执行了');
    handlerRef.current = handler;
  });

  /*
  首先, 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
  */
  return useCallback((...args: any) => {
    // In a real implementation, this would throw if called during render
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}

function useMemorizedFn(fn: any) {
  const fnRef = useRef(fn);

  // 这行保证fn一直处于,最新的闭包、拿到最新的数据。
  /**
   * fn在这里做依赖的原来: 由于每次父组件重新执行,都会走到useMemorizedFn里并传过来一个新的箭头函数。所以fn每次地址都是新的。也就带来了新数据的闭包
   * */
  fnRef.current = useMemo(() => {
    console.log('useMemo执行了'); // 父级改变就执行
    return fn;
  }, [fn]);

  const memoizedFn = useRef<any>();
  if (!memoizedFn.current) {
    console.log('memoizedFn');
    // 这行保证fn引用地址不变、
    // 这里可以解释为: const obj = {A:{}} ; memoizedFn.current = obj;
    //当执行memoizedFn.current的时候也就是去访问了 obj.A
    memoizedFn.current = function (this: any, ...args: any) {
      return fnRef.current.apply(this, args);
    };
  }

  return memoizedFn.current;
}

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);

const Test: React.FC<any> = () => {
  const [State, setState] = useState(0);

  // const changeFun = useEvent(() => {
  //   console.log('useEvent closure 里的  State', State);
  // });

  const changeFun1 = useCallback(() => {
    console.log('useCallback closure 里的  State', State);
  }, [State]);

  const changeFun_usememrized = useMemorizedFn(() => {
    console.log('useMemorizedFn closure 里的  State', State);
  });
  // // 加载顺序问题 和  快照问题
  // useEffect(() => {
  //   console.log('加载了');
  // }, []);
  // useLayoutEffect(() => {
  //   console.log('State', State);
  //   console.log('加载了-useLayoutEffect');
  //   setTimeout(() => {
  //     console.log('State', State);
  //   }, 3000);
  // }, [State]);
  return (
    <div style={{ width: '100%', padding: '20px', backgroundColor: 'green' }}>
      父组件的state: {State}
      <br />
      <br />
      <br />
      <button onClick={() => setState(Math.random())}>
        click-change state
      </button>
      <br />
      <br />
      <br />
      <TestChild changeFun={changeFun_usememrized}></TestChild>
    </div>
  );
};
export default Test;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值