- Published on
React中的useEffect
- Authors

- Name
- Li WenKang
- https://x.com/liwenkang_space
- 在单次渲染的范围内,props和state始终保持不变,可以看成固定值,在任何地方读取都是一样的(Capture Value)
- React只会在浏览器绘制后运行effects。这使得你的应用更流畅因为大多数effects并不会阻塞屏幕的更新。Effect的清除同样被延迟了。上一次的effect会在重新渲染后被清除。
- useEffect 里的 return,清理的也是当前渲染的这一次的,它所在的上下文环境就在当前渲染这一次
- useEffect使你能够根据props和state_同步_React tree之外的东西(也就是"副作用"),比如接口请求,浏览器 tab 标题等。
- 针对依赖项,请保持诚实,最好在项目的一开始,就做出强制规定(通过 eslint 配置)
- 在 useEffect 中,对于依赖旧 state 数据做更新 state 操作时,可以把 state 放入依赖,也可以使用 setState 的函数写法(更推荐,因为可以避免反复执行 effect)
- 如果有多个依赖项?如何简化?
- 使用 useReducer,将多个依赖项,合成一个对象去处理
- dispatch的时候,React只是记住了action
- 它会在下一次渲染中再次调用reducer。在那个时候,新的props就可以被访问到,而且reducer调用也不是在effect里。
- 它可以把更新逻辑和描述发生了什么分开。结果是,这可以帮助我移除不必要的依赖,避免不必要的effect调用
- 函数要不要放在 useEffect 里?从减少依赖的角度这么做也不错,或者你只把函数中真正依赖的变量,放在 useEffect 的依赖项中,也没问题啦
- 如果你不想把函数放在 useEffect 里面,有两个方法:
- 这个函数不依赖这个组件里的任何东西(比如一个工具函数?),那就直接把它放在组件外,然后直接在 useEffect 中调用就可以
- 如果这个函数确实也依赖这个组件里的变量,那可以加一层 useCallback,并把这个函数的依赖补充完整,同时将这个函数作为依赖项放入 useEffect 的依赖项数组里面(useMemo 也可以做类似的事情)
- 可能涉及到的 竞态,比如我有一个输入框,里面可以根据我输入的内容实时搜索一些数据,当我搜索 A 时,假如接口返回很慢,当请求还在后端处理的时候,我把搜索内容改为了 B ,此时接口很快就返回了数据,前端也把它渲染在了页面上,那么等搜索内容 A 返回的时候,前端又一次把新数据渲染了一遍,最终看到的效果就是,我明明在搜索 B,但页面上展示的是 A 的搜索结果。
- 如果你使用的是 fetch 这种,支持请求取消的,那很好,直接取消上一次请求就可以
- 如果是 axios 这种不支持取消的,最简单的解决方法,就是在修改 state 数据之前加个判断,看看后来有没有触发过新的请求,或者自己所在的这一次上下文,是不是已经被销毁了,如果是的话,就把修改 state 数据的逻辑忽略掉。
请对比以下两段代码
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
setTimeout(() => {
console.log(`You clicked ${count} times`)
}, 3000)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
export default App
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
setTimeout(() => {
console.log(`You clicked ${count} times`)
}, 3000)
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
export default App
如果 useEffect 不指定依赖项时,跟直接放在外面也是有区别的
直接放在外面时,这段代码会在渲染期间同步执行,会阻塞渲染,每次渲染都会执行
放在 useEffect 里面时,会在渲染结束后,立即执行,不会阻塞渲染,在每次渲染完成后都会执行
TODO 扩展内容