zoukankan      html  css  js  c++  java
  • useMemo与useCallback使用指南

    上一篇文章介绍了useEffect的使用,接下来准备介绍useCallback和useMemo。

    回顾

    在介绍一下这两个hooks的作用之前,我们先来回顾一下react中的性能优化。在hooks诞生之前,如果组件包含内部state,我们都是基于class的形式来创建组件。当时我们也知道,react中,性能的优化点在于:

    1. 调用setState,就会触发组件的重新渲染,无论前后的state是否不同
    2. 父组件更新,子组件也会自动的更新

    基于上面的两点,我们通常的解决方案是:使用immutable进行比较,在不相等的时候调用setState;在shouldComponentUpdate中判断前后的props和state,如果没有变化,则返回false来阻止更新。

    在hooks出来之后,我们能够使用function的形式来创建包含内部state的组件。但是,使用function的形式,失去了上面的shouldComponentUpdate,我们无法通过判断前后状态来决定是否更新。而且,在函数组件中,react不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。

    对比

    我们先简单的看一下useMemo和useCallback的调用签名:

    function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T; function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

    useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。

    useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

    useMemo

    我们来看一个反例:

    1.  
      import React from 'react';
    2.  
       
    3.  
       
    4.  
      export default function WithoutMemo() {
    5.  
      const [count, setCount] = useState(1);
    6.  
      const [val, setValue] = useState('');
    7.  
       
    8.  
      function expensive() {
    9.  
      console.log('compute');
    10.  
      let sum = 0;
    11.  
      for (let i = 0; i < count * 100; i++) {
    12.  
      sum += i;
    13.  
      }
    14.  
      return sum;
    15.  
      }
    16.  
       
    17.  
      return <div>
    18.  
      <h4>{count}-{val}-{expensive()}</h4>
    19.  
      <div>
    20.  
      <button onClick={() => setCount(count + 1)}>+c1</button>
    21.  
      <input value={val} onChange={event => setValue(event.target.value)}/>
    22.  
      </div>
    23.  
      </div>;
    24.  
      }

    这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:

    1.  
      export default function WithMemo() {
    2.  
      const [count, setCount] = useState(1);
    3.  
      const [val, setValue] = useState('');
    4.  
      const expensive = useMemo(() => {
    5.  
      console.log('compute');
    6.  
      let sum = 0;
    7.  
      for (let i = 0; i < count * 100; i++) {
    8.  
      sum += i;
    9.  
      }
    10.  
      return sum;
    11.  
      }, [count]);
    12.  
       
    13.  
      return <div>
    14.  
      <h4>{count}-{expensive}</h4>
    15.  
      {val}
    16.  
      <div>
    17.  
      <button onClick={() => setCount(count + 1)}>+c1</button>
    18.  
      <input value={val} onChange={event => setValue(event.target.value)}/>
    19.  
      </div>
    20.  
      </div>;
    21.  
      }

    上面我们可以看到,使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值。

    useCallback

    讲完了useMemo,接下来是useCallback。useCallback跟useMemo比较类似,但它返回的是缓存的函数。我们看一下最简单的用法:

    const fnA = useCallback(fnB, [a])

    上面的useCallback会将我们传递给它的函数fnB返回,并且将这个结果缓存;当依赖a变更时,会返回新的函数。既然返回的是函数,我们无法很好的判断返回的函数是否变更,所以我们可以借助ES6新增的数据类型Set来判断,具体如下:

    1.  
      import React, { useState, useCallback } from 'react';
    2.  
       
    3.  
      const set = new Set();
    4.  
       
    5.  
      export default function Callback() {
    6.  
      const [count, setCount] = useState(1);
    7.  
      const [val, setVal] = useState('');
    8.  
       
    9.  
      const callback = useCallback(() => {
    10.  
      console.log(count);
    11.  
      }, [count]);
    12.  
      set.add(callback);
    13.  
       
    14.  
       
    15.  
      return <div>
    16.  
      <h4>{count}</h4>
    17.  
      <h4>{set.size}</h4>
    18.  
      <div>
    19.  
      <button onClick={() => setCount(count + 1)}>+</button>
    20.  
      <input value={val} onChange={event => setVal(event.target.value)}/>
    21.  
      </div>
    22.  
      </div>;
    23.  
      }

    我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

    知道useCallback有什么样的特点,那有什么作用呢?

    使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

    1.  
      import React, { useState, useCallback, useEffect } from 'react';
    2.  
      function Parent() {
    3.  
      const [count, setCount] = useState(1);
    4.  
      const [val, setVal] = useState('');
    5.  
       
    6.  
      const callback = useCallback(() => {
    7.  
      return count;
    8.  
      }, [count]);
    9.  
      return <div>
    10.  
      <h4>{count}</h4>
    11.  
      <Child callback={callback}/>
    12.  
      <div>
    13.  
      <button onClick={() => setCount(count + 1)}>+</button>
    14.  
      <input value={val} onChange={event => setVal(event.target.value)}/>
    15.  
      </div>
    16.  
      </div>;
    17.  
      }
    18.  
       
    19.  
      function Child({ callback }) {
    20.  
      const [count, setCount] = useState(() => callback());
    21.  
      useEffect(() => {
    22.  
      setCount(callback());
    23.  
      }, [callback]);
    24.  
      return <div>
    25.  
      {count}
    26.  
      </div>
    27.  
      }

    不仅是上面的例子,所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。

    多谈一点

    useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

    源代码:

    https://github.com/anymost/hooks-demo/blob/master/src/page/Memo.js​github.com

    https://github.com/anymost/hooks-demo/blob/master/src/page/Callback.js​github.com

    原文https://zhuanlan.zhihu.com/p/66166173

  • 相关阅读:
    Cookie与Session
    发布网站
    WCF服务寄宿Windows
    JQuery:各种操作表单元素方法小结
    setTimeout()与 setInterval()
    CSS样式
    循环获取<ul>下拉列表的的值。进行对比,正确的家样式
    js定时器 实现提交成功提示
    flask 实现登录 登出 检查登录状态 的两种方法的总结
    flask 状态保持session和上下文session的区别
  • 原文地址:https://www.cnblogs.com/makai/p/14202947.html
Copyright © 2011-2022 走看看