1.在组件渲染后执行后运行,并且不会阻塞浏览器更新 UI。(异步)
2. 依赖数组控制执行时机
useEffect 接受两个参数:
useEffect(() => { // 副作用逻辑 }, [dependency]);
无依赖数组([] 省略):每次组件渲染后都会执行。
空依赖数组([]):仅在组件挂载(mount)时执行一次,不会在更新时执行。
依赖数组包含变量([dependency]):仅当依赖项发生变化时才会重新执行。
3.返回清理函数(Cleanup)
useEffect 可以返回一个函数,在组件卸载(unmount)或依赖项更新前执行清理操作。
useEffect 返回的回调函数,会在 useEffect 重新执行之前,执行一次;并且拿不到最新的值
// render
// |
// V
// 浏览器绘制
// |
// V
// 清理上一次的 effects(就是执行 useEffect 的返回值函数)
// |
// V
// 运行本次的 effects
// 首先执行组件的渲染(render)
// 然后浏览器完成绘制
// 接着执行清理工作(执行上一次effect的清理函数)
// 最后运行新的effects
// 父组件的render总是先执行
// 子组件的useEffect会在父组件的useEffect之前执行
// 当依赖项改变时,会先执行清理函数(return的函数),然后再执行新的effect
// 组件卸载时会执行相应的清理函数
function App() {
console.log('app render')
console.log('before useEffect')
const [visible, setVisible] = React.useState(true)
React.useEffect(() => {
console.log('app useEffect visible', visible)
return () => {
console.log('app useEffect return visible', visible)
}
}, [visible])
React.useEffect(() => {
console.log('app useEffect []', visible)
return () => {
console.log('app useEffect return []', visible)
}
}, [])
React.useEffect(() => {
console.log('app useEffect 空值', visible)
return () => {
console.log('app useEffect return 空值', visible)
}
})
const handleClick = () => {
setVisible((visible) => {
return !visible
})
}
const Child = () => {
React.useEffect(() => {
console.log('child useEffect')
return () => {
console.log('child useEffect return')
}
}, [])
console.log('child render')
return <div>I' m child</div>
}
// 为同步
const getCount = () => {
console.log('getCount', visible)
return visible
}
React.useEffect(() => {
console.log('useEffect', visible)
}, [getCount()])
// 等价于 }, [visible])
const getNaN = () => {
console.log('getNaN')
return NaN
}
React.useEffect(() => {
console.log('useEffect', visible)
}, [getNaN()])
React.useEffect(() => {
console.log('每次都执行')
})
React.useEffect(() => {
console.log('只在第一次执行')
}, [])
console.log('after useEffect')
return (
<div>
<button onClick={handleClick}>Click me toggle child1</button>
{visible ? <Child /> : null}
</div>
)
}
1.初始化输出:
![]()
2.点击handleClick,visible为false
![]()
3.点击handleClick,visible为true
![]()
4.简单实现useEffect
const preDepsCollect = []
let effectIndex = 0
function useMyEffect(callback, curDeps) {
if (Object.prototype.toString.call(callback) !== '[object Function]') {
throw new Error('The first argument must be a function')
}
if (typeof curDeps === 'undefined') {
setTimeout(callback)
} else {
if (Object.prototype.toString.call(curDeps) !== '[object Array]') {
throw new Error('The second argument must be an array')
} else {
const preDeps = preDepsCollect[effectIndex]
const hasChanged = preDeps
? curDeps.every((dep, index) =>
Object.is(dep, preDeps[index])
) === false
: true
if (hasChanged) {
setTimeout(callback)
}
preDepsCollect[effectIndex] = curDeps
effectIndex++
}
}
}
function App() {
// 重制下标
effectIndex = 0
useMyEffect(() => {
console.log('每次都执行')
})
useMyEffect(() => {
console.log('只在第一次执行')
}, [])
return (
<div></div>
)
}