逛了一大圈发现 React 官网的文档写 useEffect 已经很好懂了,那直接就去看官网吧(笑)!!!哈哈哈,自个儿还是做个总结,官网的文档我也有些细节没能理解,梳理一下,加深下印象。
一、基本使用
如果你熟悉 React class 的生命周期函数,你可以把
useEffect
Hook 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。
1.无需清理副作用
使用生命周期函数,实现 count
发生变化就改变 document 的 title 属性的功能,要在挂载和更新的生命周期的钩子都要执行一段相同的代码。
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
复制代码
而使用 Hook 的话
useEffect(() => {
document.title = `You clicked ${count} times`;
});
复制代码
可以这样记住:effect 发生在“渲染之后”,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
2.需要清理副作用
而对于一些需要清除的副作用,例如订阅,监听,可以在 effect 返回一个函数,React 将会在执行清除操作时调用它,具体生命时候清除后面会详细谈。
import {useEffect,useState} from 'react'
export default function App () {
const [ width, setWidth ] = useState(document.body.clientWidth)
const onChange = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {//监听resize事件
window.addEventListener('resize', onChange, false)
return () => {//取消监听
window.removeEventListener('resize', onChange, false)
}
})
return (
<div>
页面宽度: { width }
</div>
)
}
复制代码
二、第二个参数
在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题
传递数组作为 useEffect
的第二个可选参数,
- 不传:默认每一个渲染,useEffect 都会执行
- 传空数组:只执行一次,仅在组件挂载和卸载时执行
- 传一个数组,包含变量,变量发生变化时执行,注意哦,这里用的是浅比较
1.浅比较
浅比较就是只比较第一级,对于基本数据类型,只比较值;对于引用数据类型值,直接比较地址是否相同,不管里面内容变不变,只要地址一样,我们就认为没变。
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
复制代码
React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 setState
三、回调函数的相关理解
1.执行每一个 effect 前会对上一个effect进行清除
effect 发生在每次渲染之后,就相当于componentDidMount
,componentDidUpdate
,effect 的清除阶段,在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。(它会在调用一个新的 effect 之前对前一个 effect 进行清理)
import {useEffect,useState} from 'react'
export default function App () {
const [ count, setCount ] = useState(0)
const [ width, setWidth ] = useState(document.body.clientWidth)
const onChange = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {
window.addEventListener('resize', onChange, false)
console.log('执行副作用1')
return () => {
window.removeEventListener('resize', onChange, false)
console.log('清除副作用1')
}
})
useEffect(() => {
document.title = count
console.log('执行副作用2')
})
return (
<div>
页面名称: { count }
页面宽度: { width }
<button onClick={() => { setCount(count + 1)}}>点我+1</button>
</div>
)
}
复制代码
当我点击按钮加一时:控制台输出如图:请忽略工具警告(狗头)
其实这里边的细节有很多呢!
2. React只会在浏览器绘制后运行effects
这使得你的应用更流畅因为大多数effects并不会阻塞屏幕的更新。Effect的清除同样被延迟了。上一次的effect会在重新渲染后被清除:(将窗口宽度从300拉到600的大致步骤如下:)
-
React 渲染
页面宽度: 600
的UI。 -
浏览器绘制。我们在屏幕上看到
页面宽度: 600
的UI。 -
React 清除
页面宽度: 300
的effect。 -
React 运行
页面宽度: 600
的effect。
当 React 渲染组件时,会保存已使用的 effect,并在更新完 DOM 后执行它。这个过程在每次渲染时都会发生,包括首次渲染。也就是说: React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect
,useEffect是异步执行的
使用 useEffect
调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快
你可能会好奇:如果清除上一次的effect发生变成 width
变成600之后,那它为什么还能“看到”旧的width
为300?
3.Effect 会捕获定义它们的那次渲染中的props和state。
组件内的每一个函数(包括事件处理函数,effects,定时器或者API调用等等)会捕获定义它们的那次渲染中的props和state。
现在答案显而易见。effect的清除并不会读取“最新”的props。它只能读取到定义它的那次渲染中的props值:
函数组件每一次渲染的时候都会有自己的 Props 和 State,并且每一次的渲染中 props 和 state 是始终保持不变的,使用到了 props 和 state 的任何值也是独立的,包括(异步)事件处理函数
function Counter() {
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>
);
}
复制代码
类组件确实不一样哦:
componentDidUpdate() {
setTimeout(() => {
console.log(`You clicked ${this.state.count} times`);
}, 3000);
}
复制代码
并不是count
的值在“不变”的effect中发生了改变,而是effect 函数本身在每一次渲染中都不相同。每一个effect版本“看到”的count
值都来自于它属于的那次渲染.
弄一篇这玩意可真不容易,欢迎各位大佬指点!!!
参考: