目录

useEffect钩子确实可以模拟类组件中的生命周期方法:

1. ‌componentDidMount(组件挂载后)‌

2. ‌componentDidUpdate(组件更新后)‌

3. ‌componentWillUnmount(组件卸载前)‌

4. ‌shouldComponentUpdate(控制组件是否更新, 用于优化组件的渲染性能)‌

关键区别:

1、第一个参数:

 2、第二个参数:

2.1  不传值:无限循环

2.2  空数组作为依赖:执行一次

2.3  基本类型作为依赖:无限循环

2.4  引用类型

2.4.1  数组作为依赖:无限循环

  2.4.2  函数作为依赖:无限循环

  2.4.3  对象作为依赖:无限循环


useEffect钩子确实可以模拟类组件中的生命周期方法:

1. ‌componentDidMount(组件挂载后)‌

  • 在类组件中,componentDidMount会在组件第一次渲染后执行一次。
  • 在函数组件中,可以通过useEffect的空依赖数组([])实现类似效果:
useEffect(() => {
  console.log('组件挂载完成');
}, []);

2. ‌componentDidUpdate(组件更新后)

  • 在类组件中,componentDidUpdate会在组件更新后执行(除了初次渲染)。
  • 在函数组件中,可以通过useEffect的依赖数组(非空)实现类似效果:
useEffect(() => {
  console.log('count更新了:', count);
}, [count]); // 仅在count变化时执行

3. ‌componentWillUnmount(组件卸载前)

  • 在类组件中,componentWillUnmount会在组件卸载前执行清理操作。
  • 在函数组件中,可以通过useEffectreturn清理函数实现:
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => {
    clearInterval(timer); // 组件卸载时清理定时器
  };
}, []);

4. ‌shouldComponentUpdate(控制组件是否更新, 用于优化组件的渲染性能

  • useEffect(主要用于处理副作用(如数据获取、订阅等),并不直接控制渲染逻辑)。所以无法直接替代shouldComponentUpdate,但可以通过React.memo(记忆化组件)或useMemo(记忆化值)优化渲染性能。

关键区别:

  • 统一性‌:useEffect将挂载、更新和卸载的逻辑统一为一个API,通过依赖数组控制执行时机。
  • 灵活性‌:可以声明多个useEffect,按逻辑拆分副作用代码。
  • 异步性‌:useEffect的回调是异步执行的,不会阻塞浏览器渲染。

1、第一个参数:

        是一个函数,必传项。是组件要执行的副作用。可以看做componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合。

const [count, setCount] = useState(0);

useEffect(() => {
 // 普通函数,执行副作用,可以实现componentDidMount、componentDidUpdate
    console.log('执行副作用');   
 
 // return函数, 组件销毁时清除副作用,可以实现componentWillUnmount
    return () => {            
        console.log("清除副作用");
    };
}, [count]);

 2、第二个参数:

        可以不传或者是一个数组,非必传项。数组里面依赖改变时候副作用函数才会重新更新。

所谓依赖改变就是  对比 [ 之前值 === 之后值 ] ,如果为true不执行useEffect,为false重新执行useEffect

第二个参数类型:不传、[]、由基本类型或者引用类型组成的数组

示例:

  • 如果依赖数组为空([]),useEffect只会在组件第一次渲染后执行一次,类似于componentDidMount
  • return用于定义清理函数,在组件卸载或useEffect重新执行之前调用。
  • [ ]是依赖数组,决定了useEffect何时重新执行
useEffect(() => {
  console.log(`useEffect=>You clicked`);
  return () => {
    console.log('============================');
  };
}, []);

2.1  不传值:无限循环

【现象】: useEffectuseEffectuseEffect 会在第一次渲染以及每次更新渲染后都执行

原因

        第一次渲染后执行一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect没有比较值,useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环

注意:不传值是一种缺失依赖关系的情况,不建议这么做。

const [count, setCount] = useState<number>(1);
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1);
    }, 1000);
    console.log(`第二个参数: 不传值, 第 ${count} 次执行`);
});

// 打印log,无限循环
第二个参数: 不传值, 第 1 次执行
第二个参数: 不传值, 第 2 次执行
第二个参数: 不传值, 第 3 次执行
第二个参数: 不传值, 第 ... 次执行

2.2  空数组作为依赖:执行一次

【现象】: useEffect 会在第一次渲染后执行一次

【原因】: 第一次渲染后执行一次一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect中 [] 没有值,依赖没变,不触发useEffect,不执行回调函数, state 无更新,不触发组件重新渲染,至此结束

const [count, setCount] = useState<number>(1);
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1);
    }, 1000);
    console.log(`第二个参数: 空数组, 第 ${count} 次执行`);
}, []);

// 打印log,执行一次
第二个参数: 空数组, 第 1 次执行
 

2.3  基本类型作为依赖:无限循环

基本类型有:整型、浮点型、布尔型(true,false)、字符型、字符串、空值或null(Null)

【现象】: useEffect 会在第一次渲染以及每次更新渲染后都执行。

【原因】: 第一次渲染后执行一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect比较值(count)改变,useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环。

注意:传入第二个参数,只有一个值,比较该值有变化就执行,如果有多个值的数组,会比较每一个值,有一个变化就执行

const [count, setCount] = useState<number>(1);  // 基本类型以number为例
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1);
    }, 1000);
    console.log(`第二个参数: 基本类型, 第 ${count} 次执行`);
}, [count]);

// 打印log,无限循环
第二个参数: 基本类型, 第 1 次执行
第二个参数: 基本类型, 第 2 次执行
第二个参数: 基本类型, 第 3 次执行
第二个参数: 基本类型, 第 ... 次执行

2.4  引用类型

2.4.1  数组作为依赖:无限循环

【现象】useEffect 会在第一次渲染以及每次更新渲染后都执行。

【原因】:第一次渲染后执行一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect依赖项arr发生变化,此处依赖数组执行浅层比较[...] === [...] 为false)useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环

const [count, setCount] = useState(1);
const newArr = [4,5];
useEffect(() => {
    setTimeout(() => {
        setCount(count+1);
    }, 1000);
    console.log(`第二个参数: 数组, 第 ${count} 次执行`);
}, [newArr]);

// 打印log,无限循环
第二个参数: 数组, 第 1 次执行
第二个参数: 数组, 第 2 次执行
第二个参数: 数组, 第 3 次执行
第二个参数: 数组, 第 ... 次执行

【测试】去除setTimeout会出现什么情况?---无限循环

        因为useEffect频繁调用setState,state不断改变

const [count, setCount] = useState(1);
const newArr = [4,5];
useEffect(() => {
    setCount(count+1);
    console.log(`第二个参数: 基本类型, 第 ${count} 次执行`);
}, [newArr]);
    
// 打印log报错,说:超出最大更新深度

【解决】 使用useRefuseRef会在每次渲染时返回同一个ref对象,返回的ref在组件的整个生命周期内保持不变

const [count, setCount] = useState(1);
const refArr = useRef([4, 5, 6]);
useEffect(() => {
    setCount(count+1);
    console.log(`第二个参数: 数组, 第 ${count} 次执行`);
}, [refArr.current]);

// 打印log,执行一次
第二个参数: 数组, 第 1 次执行

  2.4.2  函数作为依赖:无限循环

【现象】useEffect 会在第一次渲染以及每次更新渲染后都执行。

【原因】:第一次渲染后执行一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect依赖项consoleFunction函数发生变化,此处依赖函数执行浅层比较(每次渲染都重新创建一个新的函数 function(前) === function(后)为false)useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环

const [count, setCount] = useState(1);
const consoleFunction = () => {
    console.log('consoleFunction');
};
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1);
    }, 1000);
    console.log(`第二个参数: 函数, 第 ${count} 次执行`);
}, [consoleFunction]);

// 打印log,无限循环
第二个参数: 函数, 第 1 次执行
第二个参数: 函数, 第 2 次执行
第二个参数: 函数, 第 3 次执行
第二个参数: 函数, 第 ... 次执行
 

【测试】去除setTimeout会出现什么情况?---无限循环

        因为useEffect频繁调用setState,state不断改变

        打印报错:“ 超出最大更新深度 ”

【解决】: 使用useCallback,useCallback返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新

const [count, setCount] = useState(1);
const consoleFunction = useCallback(() => {
    console.log('consoleFunction');
}, []);
useEffect(() => {
    setCount(count + 1);
    console.log(`第二个参数: 函数, 第 ${count} 次执行`);
}, [consoleFunction]);

// 打印log,执行一次
第二个参数: 函数, 第 1 次执行

  2.4.3  对象作为依赖:无限循环

【现象】useEffect 会在第一次渲染以及每次更新渲染后都执行。

【原因】:第一次渲染后执行一次useEffect,useEffect中回调函数改变state值,state值改变触发组件重新渲染,useEffect依赖项obj发生变化,此处依赖对象执行浅层比较( {...}=== {...} 为false)useEffect重新执行,useEffect中回调函数改变state值,state值改变触发组件重新渲染,无限循环

const [count, setCount] = useState(1);
const obj = {name: 'zhangsan'};
useEffect(() => {
    setTimeout(() => {
        setCount(count + 1);
    }, 1000);
    console.log(`第二个参数: 对象, 第 ${count} 次执行`);
}, [obj]);

// 打印log,无限循环
第二个参数: 对象, 第 1 次执行
第二个参数: 对象, 第 2 次执行
第二个参数: 对象, 第 3 次执行
第二个参数: 对象, 第 ... 次执行
 

【测试】去除setTimeout会出现什么情况?---无限循环

        因为useEffect频繁调用setState,state不断改变

        打印报错:“ 超出最大更新深度 ”

【解决】: 使用useMemo,useMemo该回调函数仅在某个依赖项改变时才会更新。此处使用[ ]依赖,组件重新渲染后对象不再重新定义

const [count, setCount] = useState(1);
const obj = useMemo(() => ({name: 'zhangsan'}), []);
useEffect(() => {
    setCount(count + 1);
    console.log(`第二个参数: 对象, 第 ${count} 次执行`);
}, [obj]);

// 打印log
第二个参数: 对象, 第 1 次执行

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐