React手册 Hooks 之 useEffect
描述
React 官网对 useEffect 的描述原文
useEffect
is a React Hook that lets you synchronize a component with an external system.useEffect 是一个 React Hook, 可以让你与一个外部系统保持同步.
首先需要知道, 这句话中的外部系统是指什么, 比如网络, 浏览器API, 第三方库等不受 React 控制的模块, 被称为外部系统(external).
useEffect 是一个相对比较复杂的 Hook, 其中包含两个概念: 设置代码(setup code) 和清理代码(cleanup code), 控制好这两部分代码的运行, 是使用好 useEffect 的关键.
场景
与 uesRef 类似 useEffect 也同样被 React 定义为一个应急方案(escape hatch), useEffect 的主要用途是面对 React 之外的系统时解决链接问题的一个方案, 如果不涉及外部系统, React 并不推荐使用 useEffect, 减少不必要的 useEffect 可以让你的代码更容易被理解, 运行的速度更快.
接口定义:
参数
setup: Function
一个定义了功能的函数, 当组件首次渲染的时候会执行, 后续每次依赖发生变化的时候也会被调用, 可选的返回一个清理函数 cleanup (如果 return 非函数会警告), 在每次依赖发生修改, 调用 setup 之前会先使用旧的 state 和 props 调用 cleanup, 然后使用新的 state 和 props 调用 setup 函数, 最后, 当组件从 DOM 中移除的时候, React 会调用 cleanup 函数.
dependencies: Array
setup 中使用的所有响应式的变量数组, React 使用 Object.is 对依赖项进行对比, 如果发生变化, 就先调用 cleanup (如果返回了话), 然后调用 setup, 如果没有传 dependencies, 那么组件每次重新渲染, useEffect 都会重新执行. 如果传递空数组, 那么只会在首次渲染执行
返回
useEffect 返回 undefined;
用法1
链接到外部系统, 有些组件在页面上显示时, 需要通过网络, 某些浏览器的API, 或第三方库, 这些不受 React 控制的系统, 想要和 React 链接在一起就需要使用 useEffect.
当组件加载完毕之后, 会先执行设置代码, 之后每次依赖变更之后, 会先执行清理代码, 紧接着执行设置代码, 当组件被销毁后, 还会再执行一次清理函数.
推荐将每个 useEffect 都编写成一个独立的过程, 每次只执行一个设置代码或清理代码.
用法2
自定义 Hooks 中使用, useEffect 是一个紧急方案, 对于真实的业务开发场景来说, 他有点偏抽象, 应该只作为某个功能的一部分实现, 所以如果你经常手动编写 useEffect, 那你应该考虑封装成自定义Hooks, 将 useEffect 作为独立功能的部分实现, 隐藏在其中.
比如上面的例子:
这样在使用时, 可以直接通过 useCustomHookRoom 这个自定义 Hook 直接使用,
用法3
控制非 React 的组件, 当 state 和 props 变化的时候, React 不能感知到, 并通知组件重新渲染, 但是如果要控制的组件并不是 React 系统中的部分, 对 state 和 props 不起任何反应, 这个时候也可以使用 useEffect 来链接, 这样的用法有点类似 Vue 中的 watch 方法的用法.
首先需要使用 useRef 保存一个被操作对象的句柄, 然后使用一个 state 作为控制变量作为 useEffect 的依赖, 当 React 操作 state 时, 依赖变更会触发 useEffect 的设置方法, 在设置方法中, 可以通过句柄操作被控制对象, 实现起来像这样:
在这个例子中, 并不需要清理函数, 因为组件销毁时, MapWidget 会自动被浏览器垃圾回收, 所以 useEffect 方法中并没有返回清理函数.
用法4
控制请求响应处理, 这个场景主要是你出发请求的速度要大于情求响应的速度, 此时上一次请求还没返回,但是后一次请求已经发出或已经响应, 那么上一次请求的结果就应该抛弃, 避免多次请求结果一起竞争渲染, 导致显示结果不准确.
fetchBio 作为一个响应时间不确定的请求方法, ignore 是一个闭包变量, 当下一次 useEffect 触发之前, 会先使用老的依赖, 执行清理函数, 将老的 effect 中的 ignore 闭包变量设置为 true, 让老的请求返回值不被设置, 作废之后, 等待新的请求返回值回来, 并设置上.
像这样直接在 useEffect 中发出请求的代码是有效的, 但是看起来会又些重复, 所以最好还是放在自定义 Hooks 中去封装成贴近业务的操作, 这样更方便服用和维护.
用法5
useEffect 使用的一些规范, 在 useEffect 的设置方法中使用的所有响应式值(state, props)都必须收集在 useEffect 的依赖数组中, 这并不是可选的, 而是必须的, 如果你要移除一个依赖想, 就必须让它变成一个非响应式的变量.
获取你还见到过类似这样的代码
在开发 React 的时候, React 会对你的代码进行代码检查, 使用这个 eslint-ignore-next-line react-hooks/exhaustive-deps 会让 React 代码检查失效, 应该避免使用这样能够抑制代码检查的工具, 因为当你的依赖项和设置方法不匹配时, 引入 BUG 的风险会很高.
此外, 还有在 useEffec 中更新 state, 那么 state 又是 useEffect 的依赖, 可能会导致 useEffect 的设置函数产生循环调用, 避免这样的风险, 可以使用下面这种写法来处理
总结
useEffect 包含两个概念: 设置代码(setup code) 和清理代码(cleanup code)
useEffect 是一个应急方案(escape hatch), 主要用途是面对 React 之外的系统时解决链接问题的一个方案
useEffect 应该尽量封装在自定义 Hooks 中
useEffect 的设置法中使用的所有响应式值(state, props)都必须收集在依赖数组中
在useEffect 的设置方法中修改 state, 要使用 setCount(c => c + 1) 的方式
不要使用抑制代码检查的工具
如果没有传递依赖项, 那么 useEffect 在每次组件渲染时执行设置方法, 如果传递空数组作为依赖项, 那么 useEffect 的设置方法只在组件第一次渲染时运行.