React函数组件Hooks-useMemo详解

4,426 阅读3分钟

image.png

Memo

使用场景:一个App组件里有m和n数据。还有一个Child子组件,它接受一个m作为它的外部数据。当点击把n+1的按钮时,我们知道,App会再次执行,那么Child子组件也会再次执行吗?

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };

  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
      <Child data={m}/>
    </div>
  );
}

function Child(props) {
  console.log("child 执行了");
  console.log('假设这里有大量代码')
  return <div>child: {props.data}</div>;
}

通过在Child函数组件里打log,我们发现,改变n,Child也会执行一次。可是明明Child这个组件只依赖m,n变化它为什么要跟着执行呢?怎么解决这个问题?

就可以使用 React.memo 封装Child

const Child2 = React.memo(Child);

把Child放在 React.memo 里,得到一个Child2,Child2是Child优化之后的函数。在App里渲染这个Child2

结果:改变n,Child不执行了,只有改变它依赖的外部数据m时,才会执行。

使代码更简洁:直接把匿名函数组件放在memo里作为参数,得到Child组件。

const Child = React.memo((props)=>{
  console.log("child 执行了");
  console.log('假设这里有大量代码')
  return <div>child: {props.data}</div>;
});

结论

React默认会有多余的render,当改变组件里的数据时,由于组件本身会再次执行,就导致这个组件的子组件也会再次执行。

React.memo 使得一个组件只有在它依赖的props变化时,才会执行

memo有一个bug

如果子组件接受了一个函数props,这个函数在父组件里定义,给子组件传进去。当我改变父组件里的n时,App()肯定会再次执行。就会导致定义在里边的这个函数也执行了。

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  const onClickChild=()=>{}     // 再次执行时,虽然函数内容一样,但是地址不同了
  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
      <Child data={m} onClickChild={onClickChild}/>
    </div>
  );
}

const Child = React.memo((props)=>{
  console.log("child 执行了");
  return <div onClick={props.onClickChild}>child: {props.data}</div>;
});

结果:点击按钮,改变n。Child明明不依赖n,但是Child也执行了。

就是因为App再次执行时,onClickChild 这个函数也被重新定义了,虽然内容不会变,但是地址不同了,React就认为这个函数变了。Child接受的这个props变了,自然就会再次执行。

怎么办呢?能不能让这个函数在某种情况下,不要重新生成,继续使用上一次的缓存结果

用useMemo,实现函数重用

const onClickChild = useMemo(() => {
    return () => {};
  }, [m]);

把这个函数经过 useMemo 优化。useMemo 接受两个参数,第一个参数是一个返回函数的函数,返回的就是该函数本来的逻辑。第二个参数是依赖,表示只有当m变化时,才重新生成函数。如果依赖的m没变,就复用上一次的缓存,不生成新的函数。

点击n+1的按钮后,发现Child组件没有执行了。因为我们使用 useMemo 规定了,只有当m变化时,才重新生成props函数。m没变,即使App再次执行,这个函数也不会生成一个新的。

useMemo 经常用来缓存一些在两次新旧组件迭代的时候,希望使用上一次的值。

有时要缓存的,也就是return 的不一定是函数,也可以是对象

useMemo的第一个参数:()=> value,没有形参,返回一个value。第二个参数:依赖 [m,n]。只有当依赖变化时,才会计算新的value。依赖没变,就重用之前的value

useMemo的特点:

  • 第一个参数是()=>value
  • 第二个参数是依赖[m,n]
  • 只有当依赖变化时,才会重新计算出新的value
  • 如果依赖不变,那么就重用之前的value
  • 类似Vue2的computed

注意:如果你的value是一个函数,那么就要写成

useMemo(()=>(x)=>console.log(x))

这是一个返回函数的函数

是不是觉得很难用,那么 useCallback 来了

useCallback的用法(useMemo的语法糖)

useCallback(x=>console.log(x),[m])

等价于

useMemo(()=>(x)=>console.log(x),[m]