React中useEffect详细介绍

useEffect 是 React 中的一个钩子(Hook),它使你能够在函数组件中执行副作用操作。副作用是指那些不属于组件渲染过程的操作,例如数据获取、订阅或者直接操作 DOM。

使用 useEffect

基本语法如下:

Jsx 复制代码
useEffect(() => {
  // 执行副作用操作

  return () => {
    // 清理操作
  };
}, [/* 依赖项数组 */]);
  • 第一个参数是一个函数,这个函数会在组件渲染到屏幕之后执行。
  • 第二个参数是依赖项数组。如果数组中的任何依赖项自上次渲染以来发生了变化,React 将重新执行副作用函数。
  • 如果返回一个函数,则该函数将被视为清理函数,在组件卸载前或依赖项改变导致副作用重新运行前调用。

原理

当你调用 useEffect 时,你告诉 React 在完成对 DOM 的更改后运行你的"副作用"函数。由于副作用可能包含异步操作并且会延迟执行,所以它们不应该阻塞浏览器更新屏幕。

注意事项

  1. 避免在循环、条件或嵌套函数中调用 useEffect ------ 应该总是在组件顶层使用它们。
  2. 确保所有 useEffect 中使用到的变量都包含在依赖项列表中 ------ 这样可以确保当这些变量改变时你的代码不会引入 bug。
  3. 优化性能 ------ 如果你发现由于 useEffect 的执行导致性能问题,考虑是否正确设置了依赖项列表或者是否可以通过其他方式优化。

关于闭包

每次组件渲染时,都会生成新的 props 和 state。因此,在每一次渲染中,useEffect 中定义的所有内容(包括回调函数和其中引用的任何值)都是基于最新的 props 和 state 的"快照"。

如果 useEffect 被声明为只在特定值改变时运行(通过传递第二个参数------依赖项数组),那么它内部引用的任何值都将不会更新,直到再次触发该 effect。这就意味着如果 effect 内部有异步操作,并且在两次 effect 执行之间 state 或 props 发生了变化,effect 内部看到的仍然是旧值。

例如:

Jsx 复制代码
function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // 这里始终打印出 effect 执行时 count 的值
    }, 1000);
    
    return () => clearInterval(id);
  }, []); // 空数组表示 effect 只在挂载时运行一次

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

上面代码中,即使点击按钮增加了 count 值,在定时器回调里打印出来的 count 值始终为初始值0。这是因为 effect 捕获了首次渲染时 count 的值,并且由于依赖列表为空(只在挂载时运行一次),所以即使 count 更新了也不会影响已经设置好定时器里捕获的值。

要修复这个问题,可以将 count 添加到依赖列表中:

Jsx 复制代码
useEffect(() => {
  // ...
}, [count]); // 现在每当 count 改变时都会重设定时器

现在每当 count 改变时都会取消前一个定时器并创建一个新定时器。

总结来说,在 React 的 useEffect 中使用状态和属性会形成闭包,因为它们被定义那一刻所处环境(词法作用域)被保留下来。理解和正确处理闭包对避免错误和性能问题非常关键。

在每次函数组件渲染时,会产生多个同样的useEffect嘛?

在函数组件的每次渲染中,React 会重新执行整个组件函数,这包括其中定义的所有钩子(Hooks),如 useStateuseEffect 等。因此,对于 useEffect 来说,它的声明会在每次渲染时被重新执行。然而,这并不意味着 useEffect 中的副作用函数也会在每次渲染后都运行。

React 根据 useEffect 的依赖项数组来决定是否需要调用副作用函数:

  • 如果没有提供依赖项数组(即省略第二个参数),则副作用函数将在每次组件渲染后运行。
  • 如果提供了一个空数组 ([]) 作为依赖项,则副作用函数只会在组件挂载时运行一次,并且在卸载时运行清理。
  • 如果提供了具有变量的数组,则只有当数组中的任何变量自上次渲染以来发生变化时,副作用函数才会再次运行。

这意味着虽然 useEffect 的声明在每次渲染时都会被调用,但是 React 会根据依赖项来优化副作用函数的实际执行情况。

例如:

Jsx 复制代码
function MyComponent(props) {
  useEffect(() => {
    // 这个副作用将在 props.value 改变后运行
    console.log('Value changed:', props.value);
  }, [props.value]); // 只有当 props.value 改变时才重新执行

  return <div>{props.value}</div>;
}

在上面的例子中,尽管 MyComponent 在每次父组件重新渲染时可能被多次调用(从而导致 useEffect 被多次声明),但是 React 只有在 props.value 发生改变时才会实际调用该副作用内部的函数。

总结来说,在 React 函数组件中使用 useEffect 不会导致多个相同的副作用同时存在。React 使用依赖项数组来智能地确定何时需要调用或清理特定的副作用。

在 React 函数组件中使用 useEffect 不会导致多个相同的副作用同时存在。React内部对这里实现的原理是什么,如何做到的不导致多个相同的副作用?

React 使用一种称为 "Hooks" 的机制来处理函数组件中的副作用,这包括 useEffect 钩子。React 内部维护了一个钩子的状态列表,每个钩子在列表中都有固定的位置。当组件重新渲染时,React 会按照声明它们的顺序遍历这些钩子,并更新或重用它们的状态。

对于 useEffect 钩子来说,其原理和实现可以概括为以下几点:

  1. 跟踪副作用函数 :在组件首次渲染时,React 会将 useEffect 中定义的副作用函数放入一个内部队列中。这个队列确保了副作用函数能够在 DOM 更新完成后按预期执行。
  2. 依赖项数组比较:当组件重新渲染时,React 会比较新旧依赖项数组。如果依赖项没有变化(即数组中每一项都相等),则 React 不会再次调用该副作用函数。
  3. 清理和重新执行:如果依赖项发生了变化或者是首次渲染,则 React 将先运行上一次副作用返回的清理函数(如果有),然后再运行当前渲染周期中定义的副作用函数。
  4. 保持顺序:由于 React 要求 Hooks 在每次渲染中被调用的顺序必须相同,因此 React 可以通过这个规则来正确地关联当前渲染周期与上一个周期之间的副作用。
  5. 使用链表存储:React 内部可能使用链表或类似结构来存储挂载、更新、卸载等操作所需执行的所有副作用。这样可以有效地插入、移除和遍历需要执行的副作用。
  6. 批量处理 :React 还会对多个 useEffect 调度进行批量处理,以优化性能。例如,在浏览器空闲时段或下一个动画帧之前延迟执行非紧急更新。

通过以上机制,React 确保了即使在多次连续快速地重新渲染过程中也不会导致多个相同的副作用同时存在。每个 useEffect 只关心自己对应那一次特定渲染所产生的影响,并且根据依赖项是否改变来决定是否需要重新执行。

相关推荐
学习使我快乐013 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19953 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈4 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水5 小时前
简洁之道 - React Hook Form
前端
正小安7 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch9 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光9 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   9 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   9 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d