使用useCallback、useEffect的感悟

3,215 阅读3分钟

第一个栗子

import React ,{useEffect, useState} from "react";
function Hello(){
    const [name, setName] = useState('');
    const consoleLog = () => {
        console.log('没有被 useCallback 包裹的consoleLog执行了');
    };
    useEffect(() => {
        consoleLog();
        console.log("useEffect 执行了");
    },[consoleLog]);
    console.log("render 生命周期触发了");
    return (<div>
    <input value={name} onChange={(e) => {
      setName(e.target.value);
    }}/>
    </div>);
}
export default Hello;

背景: 当我们在input框中输入内容的时候,会通过 value 属性再次拿到我们输入的内容,其实我们看到输入框中的内容已经不是我们刚刚输入的时候的那个内容了,而是那个内容再次的重新渲染。虽然我们看不出什么变化,但是他触发了 React 生命周期中的 render 函数,在函数式组件中就相当于重新执行了一遍 Hello() 函数。

现象: 首先,页面第一次加载,执行 render 函数,render 完了执行 componentDidMount ,这个生命周期我们用useEffect来模拟,所以我们可以看到尽管我把 “ console.log("render 生命周期触发了");” 这句代码放在了useEffect的下面,但是执行顺序也是console.log比useEffect先执行的。之后,当我们每次在输入框中输入内容的时候,触发render生命周期,Hello()函数被调用,然后consoleLog函数被重新赋值,然后我们在useEffect中依赖了consoleLog函数,因为consoleLog函数变了,所以就会在 componentDidUpdate 生命周期执行useEffect中回调函数中的consoleLog(),之后执行并输出console.log("render 生命周期触发了"),等render完了,执行 componentDidUpdate 即useEffect里面的内容。

问题: 我们发现每次输入框的内容改变的时候,都会重复的执行consoleLog函数别赋值这一步操作,但是我们都知道,consoleLog函数没有更新,可以不用重新赋值,那咋办呢?

解决: 其实这个问题很容易想到缓存,因为缓存就是把数据暂时存储一下,达到提高性能的目的。所以在这里就体现出 useCallback 的价值了。useCallback 就是在第二个参数里面的依赖项不发生变化的时候,就不会返回一个新的函数,而是使用之前的函数,所以我们之前的函数可以重写为这样:

const consoleLog = useCallback(() => {
     console.log('useCallback 包裹的consoleLog执行了');
},[]);

在这里我把依赖项写成一个空数组,这样说明不依赖任何变量的变化,所以只会在初始化consoleLog的时候执行一次,之后页面重新渲染也不会执行了。我们也可以依赖于我们在consoleLog函数中用到的变量,通常我们也是这样做的,大概意思就是说我们用到的变量变了,那么就重新返回一个新的函数来覆盖掉之前的consoleLog函数。

再次举个栗子

import React, { useCallback, useEffect, useState } from 'react';

function Welcome () {
  const [name, setName] = useState('init');

  const consoleLog = useCallback(() => {
    console.log(name);
  }, [name]);

  useEffect(() => {
    consoleLog();
  }, [consoleLog]);

  return (<div>
    <input value={name} onChange={(e) => {
      setName(e.target.value);
    }}/>
    <div onClick={() => {
      consoleLog();
    }}>点击</div>
    </div>);
}
export default Welcome;

相信有了上面的那个栗子,大家在看这个栗子的时候就清晰许多了。这次我们的consoleLog函数输出的不是一个纯文本了,而是一个 name 变量。显然我们在div的onClick事件中想要看到最新的name是什么,这时候我们的 consoleLog 函数就要依赖于 name 的变化了。name变化导致consoleLog重新赋值,consoleLog重新赋值导致 useEffect 中第一个参数别再次执行。大概的逻辑就是这样。

今天就分享这么多吧,主要是在项目中遇到了,研究了一下,记录了下来。