Skip to main content

useEffect

概念

useEffect 可以让使用者在函数组件中执行副作用操作。

在 React 中,由 state 的变化导致 UI 发生变化的过程是正常操作。

其他操作行为:例如数据请求、直接手动修改 DOM 节点、直接操作页面「修改页面标题等」、记录日志等都是副作用操作。

副作用操作是相对于操作 state 而言的。

每一次因为 state 的改变,都有一次对应副作用函数的执行时机。如果 state 多次改变,那么就有多次对应副作用的执行时机。

graph LR

A[改变 state] --> B[UI Render] --> C[执行副作用]

清除副作用

effect 和 clear effect 都有可能执行多次。

clear effect 在下次 effect 执行之前执行,也会在组件销毁之前执行一次。

我们可以借助该特性实现一个防抖的案例:

例如我们要实现一个搜索框的功能。文字输入过程中会自动发起搜索请求。 为了防止请求发送过于频繁,在高频输入时,不发送接口请求,如果超过了 500ms 下一次输入事件还没有发生,那么就自动请求一次。

import { useEffect, useState } from "react";

export default function EffectDemo() {
const [text, setText] = useState("");

useEffect(() => {
let timer = setTimeout(() => {
console.log("发送搜索请求");
}, 500);

return () => {
console.log("清除定时器");
clearTimeout(timer);
};
}, [text]);

return (
<div>
<input
type="text"
placeholder="请输入内容..."
onChange={(e) => setText(e.target.value)}
/>
</div>
);
}

如果下一次 text 快速发生变化,clear effect 执行会清除掉上一次定义的定时器任务,那么请求逻辑就不会执行。

上面代码的执行顺序是:

graph LR

A[输入] --> B[改变 text] --> C[UI Re-Render] --> D[clear effect] --> E[effect]

在真实实践时,我们往往是知道自己要执行的副作用逻辑是什么,

难的是需要我们自己去设计合理的 state。不合理的设计会让程序变得复杂。

闭包

如何简化依赖项

当组件函数重新执行时,useEffect 函数本身也会重新执行。

useEffect 接受的第一个参数 effect 函数是一个匿名函数,它总是会被重新定义。

因此,不管依赖项如何,effect 函数总能访问到最新的 state。

不过 React 会利用闭包来缓存 effect 函数,真正被执行的并非每次重新定义的 effect 函数,而是被缓存起来的 effect 函数。

在初始化和任意依赖项发生变化时,被缓存的 effect 函数会被重新赋值。

理解了上面的内容,我们就有了简化依赖项的前置知识。

我们只需要确保被执行的 effect 函数中总是能访问到正确的值,而不需要添加冗余的依赖项。

实战

我们应该尽量避免让引用数据类型成为依赖项。