React useEffect 从入门到实战:一文吃透核心用法

508 阅读5分钟

引言

在 React 函数式组件的开发进程里,useEffect 堪称极为关键的钩子,它赋予我们处理副作用操作的强大且灵活的手段。副作用操作于前端开发场景中随处可见,诸如数据获取、DOM 操作、事件监听等。接下来,本文将深度剖析 useEffect 的运行原理、使用场景以及常见的易错点,助力你全方位掌握这一核心要点。

什么是 useEffect?

useEffect 是 React 提供的 Hook 函数,其能让我们在函数式组件内执行副作用操作。这里所说的副作用,指的是与组件渲染本身无直接关联的操作,具体涵盖:

  • 数据获取(AJAX 请求)
  • DOM 操作(手动修改样式、添加事件监听等)
  • 定时器或 interval
  • 订阅事件
  • 清理无效操作

基本语法

useEffect(() => {
  // 副作用操作代码
  return () => {
    // 清理副作用的代码(可选)
  };
}, [依赖项数组]); // 依赖项数组是可选的

它接收两个参数:

  • 第一个参数为回调函数,其中包含要执行的副作用操作
  • 第二个参数是可选的依赖项数组,用于把控副作用的执行时机

依赖项数组的三种情况

依赖项数组是 useEffect 的核心机制,它决定了副作用函数的执行时机。下面通过具体实例来理解三种不同情形。

1. 没有依赖项数组

当不设置依赖项数组时,副作用函数会在每次组件渲染(包括初始渲染和更新渲染)完成后执行。

import { useEffect, useState } from "react";
function App() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log("副作用函数执行");
  });
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+{count}</button>
    </div>
  );
}

在此例中,每次点击按钮更新 count 的值,组件便会重新渲染,副作用函数也会随之执行,控制台将持续输出 "副作用函数执行"。

2. 依赖项数组为空

若依赖项数组为空,副作用函数仅会在组件初始挂载时执行一次,后续组件更新时不再执行。

import { useEffect, useState } from "react";
function App() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log("副作用函数只在挂载时执行一次");
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+{count}</button>
    </div>
  );
}

这种用法适用于仅需执行一次的操作,例如数据初始化。

3. 依赖项数组包含特定值

当依赖项数组中包含特定值时,副作用函数会在组件挂载时执行一次,并且在这些依赖项的值发生变化时再次执行。

import { useEffect, useState } from "react";
function App() {
  const [count, setCount] = useState(0);
  const [count1, setCount1] = useState(0);
  
  useEffect(() => {
    console.log("count 变化时执行");
  }, [count]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <button onClick={() => setCount1(count1 + 1)}>+{count1}</button>
    </div>
  );
}

在此例中,仅当 count 的值发生变化时,副作用函数才会执行,count1 的变化不会触发该函数执行。

数据获取示例

下面通过一个实际的数据获取案例,来看看 useEffect 在实际开发中的具体应用。

import { useEffect, useState } from "react";
const URL = "http://geek.itheima.net/v1_0/channels";
function App() {
  const [list, setList] = useState([]);
  
  useEffect(() => {
    // 获取频道列表
    async function getList() {
      await fetch(URL)
        .then((res) => res.json())
        .then((data) => {
          setList(data.data.channels);
        });
    }
    
    getList();
  }, []);
  
  return (
    <div>
      <h1>频道列表</h1>
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

在此例中,我们在组件挂载时发送 AJAX 请求获取频道列表数据。由于仅需获取一次数据,所以依赖项数组为空。

清理副作用

部分副作用操作会产生需要清理的资源,像定时器、订阅事件等。useEffect 支持我们通过返回一个清理函数来处理此类情况。

定时器清理示例

import { useEffect, useState } from "react";
function Son() {
  // 渲染时开启定时器
  useEffect(() => {
    const timer = setInterval(() => {
      console.log("定时器执行中");
    }, 1000);
    
    // 卸载时清除定时器
    return () => {
      clearInterval(timer);
    };
  }, []);
  
  return <div>这是子组件</div>;
}
function App() {
  const [show, setShow] = useState(true);
  
  return (
    <div>
      {show && <Son />}
      <button onClick={() => setShow(false)}>卸载子组件</button>
    </div>
  );
}

在此例中,Son 组件挂载时设置了定时器,每秒输出一次日志。为避免组件卸载后定时器持续运行造成内存泄漏,我们返回了一个清理函数,在组件卸载时清除定时器。

常见应用场景

1. 数据获取

这是最为常见的使用场景,通常在组件挂载时获取初始化数据 。

2. DOM 操作

我们可以在组件渲染完成后操作 DOM 元素。

import { useEffect, useRef } from "react";
function App() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    // 组件挂载后聚焦到输入框
    inputRef.current.focus();
  }, []);
  
  return (
    <div>
      <input ref={inputRef} type="text" />
    </div>
  );
}

3. 订阅事件

在组件挂载时订阅事件,卸载时取消订阅。

import { useEffect } from "react";
function App() {
  useEffect(() => {
    const handleResize = () => {
      console.log("窗口大小改变");
    };
    
    // 订阅窗口大小改变事件
    window.addEventListener("resize", handleResize);
    
    // 卸载时取消订阅
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);
  
  return <div>调整窗口大小查看效果</div>;
}

注意事项

  1. 依赖项数组的正确使用:务必确保依赖项数组中涵盖了所有需要监听变化的变量,否则可能致使副作用函数无法按预期执行。
  1. 避免无限循环:若副作用函数中修改了状态,且该状态又存在于依赖项数组中,可能会引发无限循环。
  1. 异步操作的处理:尽管可以在 useEffect 中使用异步操作,但要妥善处理异步逻辑,规避竞态条件的出现 。
  1. 清理函数的必要性:对于那些会产生需清理资源的副作用操作,一定要提供清理函数,防止内存泄漏。

总结

useEffect 是 React 中功能强大且灵活多变的钩子,它为我们在函数式组件中处理副作用操作提供了有效途径。通过合理运用依赖项数组和清理函数,我们能够精准控制副作用函数的执行时机,并确保资源得以正确释放。熟练掌握 useEffect 的使用,对开发高质量的 React 应用来说至关重要。