useEffect 是 React 中一个重要的 Hook,用来处理组件的副作用操作。它的基础知识包括两个方面:执行时机和参数。
执行时机:
useEff ect 的执行时机包括两种情况:
- 组件挂载时,即第一次渲染之后。
- 组件更新时,即组件的 props 或状态发生变化时。
参数:
useEffect 接收两个参数:一个函数和一个依赖数组。
- 函数:这个函数会在组件的挂载和更新时被调用,它可以包含一些副作用操作,比如修改 DOM、发送网络请求等等。
- 依赖数组:这个数组是一个可选参数,用来指定 useEffect 的依赖项。如果一个组件中需要多次调用 useEffect,那么每次调用都需要指定依赖数组。当依赖数组中的任何一个值发生变化时,才会触发 useEffect 的重新执行。如果依赖数组为空,则表示 useEffect 没有任何依赖,只会在组件挂载和卸载时执行。
写法
useEffect(callback)
:font color=red>这个参数表示只要组件发生更新,就会执行回调函数callback
,包括组件挂载时的初始化和后续状态或属性的更新。如果不需要依赖任何状态或属性,相当于类组件中的componentDidMount
和componentDidUpdate
的合并。useEffect(callback, [])
:这个参数表示只在组件挂载时执行一次回调函数callback
,不会在组件更新时执行。这个参数当中传入的空数组[]
表示没有任何依赖,因此只有在组件挂载时才会执行。useEffect(callback, [state1, state2, ...])
: 这个参数表示在组件挂载时执行一次回调函数callback
,之后只有state1
、state2
等指定的依赖状态发生改变时才会执行回调函数。如果多个状态都需要依赖,可以在数组中传入多个状态名称。
useEffect( ()=>{
return ()=>{
//返回的小函数,会在组件释放的时候执行
//如果组件更新,会把上一次函数执行,返回的小函数执行
console.[log( '@4' ,num );
});
需要注意的是,如果在 callback
中进行了一些异步操作,比如发起网络请求,需要在组件被销毁时主动取消这些异步操作,以防止内存泄漏。可以使用 useEffect
返回的清除函数来实现这个功能。例如:
useEffect(() => {
const fetchData = async () => {
// 发起异步请求
}
fetchData();
return () => {
// 取消异步请求
}
}, [state1])
在这个例子中,fetchData
函数是一个异步操作,会在组件挂载和 state1
发生改变时被调用。在 useEffect
的回调函数中返回了一个清除函数,该函数会在组件被销毁时调用,用来取消异步请求操作。
底层机制:
基于effect链表实行!!!是异步的,effect
链表是指在一个 useEffect
钩子中,可以有多个副作用函数(或称为 effect 函数)。这些副作用函数会被按照它们声明的顺序依次执行,形成一条链表。当组件更新时,这些函数也会被按照它们声明的顺序依次执行。
useLayoutEffect和useEffect的细节
1.需求:在num大于5的时候我们再ueseffect
if ( num > 5){
useEffect( ( ) => {
lconsole.log ( 'OK');
});
}
会报错useEffect必须在函数的最外层上下文中调用,不能把其嵌入到条件判断、循环等操作语句中,应该这样做
useEffect(() => {
if (num > 5) {
console.log('OK');
}
}, [num]);
2.模拟从服务器异步获取数据
错误写法:
在这个例子中,这里返回的是一个promise实例,而不是一个函数,我们试图在 useEffect
中使用 async/await
,然后在函数体内调用 queryData
函数获取数据并打印到控制台上。但是你需要注意,useEffect
钩子中的副作用函数必须是同步的函数(或者返回一个清除函数的函数),而 async/await
函数是异步的函数,因此不能直接在 useEffect
中使用。
可以将副作用函数的主体逻辑封装到一个单独的函数中,然后在 useEffect
中调用这个函数:
const fetchData = async () => {
let data = await queryData();
console.log('成功: ', data);
};
useEffect(() => {
fetchData();
return () => {
// 在组件卸载前,清除副作用
console.log('清除 fetchData 副作用');
};
}, []);
在这个例子中,我们将获取数据并打印到控制台的操作封装到了 fetchData
函数中,这个函数是一个异步的 async
函数。然后我们在 useEffect
中调用了这个 fetchData
函数。由于 useEffect
存在异步特性,因此不会阻塞其它的组件渲染,而副作用 fetchData
会在组件初次渲染时被调用,并且不会被重复执行(因为依赖列表是空的)。
需要注意的是,我们不能直接将 fetchData
函数放在 useEffect
中调用,而是需要先将它定义成一个变量。因为在 useEffect
中定义的函数会被作为闭包存储,并且它们的引用关系不会随着组件更新而改变。如果我们直接在 useEffect
中定义 async/await
函数,那么这个函数的引用关系会在每个组件更新时被重置,从而无法实现我们的预期效果。
3.什么是副作用函数??
根据上一个问题,fetchData
函数中包含了异步操作,比如网络请求,这些异步操作具有副作用。因此 fetchData
函数被视为具有副作用的函数。
在函数式编程语言中,副作用是指函数对外部环境造成的影响,这些影响可能会改变系统状态或返回一些不可预测的结果。在 JavaScript 中,一些常见的副作用包括修改全局状态、改变 DOM、读写本地存储、发出网络请求等等。
由于副作用的不确定性和可变性,React 为我们提供了钩子函数,例如 useEffect
,以及一些限制和规范,来管理和控制副作用。在 React 中,我们应该尽量避免直接操作 DOM,而应该通过钩子函数和状态管理来更新视图和响应用户操作。
useLayoutEffect
1.useLayoutEffect
和 useEffect
很相似,都是 React 提供的用于处理副作用的钩子函数。它们的主要区别在于执行时间不同。
useLayoutEffect
在第二步结束的时候渲染,useEffect
在第三步结束的时候渲染
2.useEffect
会在浏览器完成绘制后,即组件渲染到页面上后异步执行。也就是说,可能会在用户看到更新前看到旧的 UI。而 useLayoutEffect
会在浏览器绘制之前同步执行,useLayoutEffect会阻止浏览器渲染真实DOM,优先执行Effect链表中的callback;
这里的渲染是指绘制,useLayoutEffect 执行时,组件返回的 JSX 已经被渲染成 DOM,但 DOM 还没有完成 layout,也就是还没有完成测量和定位。相对于 useEffect,useLayoutEffect 触发的时机更早,可以拿到更快更准确的测量值,同时又避免了闪烁和触发重渲染的问题。因此可以在 useLayoutEffect 中进行对真实 DOM 的操作。,确保无论是 React 还是 DOM 视图都是最新的,即用户看到的 UI 是最新的。
如果你需要在更新 DOM 前同步更新某些组件或页面状态并且必须优先更新,可以使用 useLayoutEffect
。如果不需要优先更新或状态更新较慢也不会影响用户体验,可以使用 useEffect
。
需要注意,由于 useLayoutEffect
的同步执行,如果钩子函数的操作逻辑较耗时,则可能会导致页面卡顿或延迟加载。因此,应该谨慎使用 useLayoutEffect
,避免出现性能问题。
渲染次数问题:
如果在callback函数中又修改了状态值「视图又要更新」
1.useEffect:浏览器肯定是把第一次的真实已经绘制了,再去渲染第二次真实DOM
2.useLayoutEffect:浏览器是把两次真实DOM的渲染,合并在一起渲染的
总结
1. 视图更新的步骤
- 将 JSX 编译为
createElement
格式并调用,创建出 Virtual DOM。 - 基于
root.render
方法将 Virtual DOM 转换为真实 DOM 对象。 - 执行更新队列中已记录的所有组件的生命周期方法,包括
shouldComponentUpdate
、render
、getSnapshotBeforeUpdate
和componentDidUpdate
等方法。 - 基于 Virtual DOM 树比较上一次渲染时的 virtual DOM 树和本次渲染时的 virtual DOM 树,找出两棵树之间的差异,并记录需要更新的 DOM 对象的最小集合。
- 在浏览器空闲时间内执行更新和重绘操作,优化更新过的 DOM 对象,尽可能减少页面的重排和重绘的次数,提高页面的性能和响应速度。
2. useLayoutEffect 和 useEffect 的区别
- useLayoutEffect 与 useEffect 在实现上是类似的,不同之处在于 useLayoutEffect 的回调函数会在 DOM 更新之后,浏览器执行绘制之前被调用,而 useEffect 的回调函数则是在浏览器执行绘制之后被调用。
- useLayoutEffect 在执行回调函数时会产生阻塞效果,可能会导致页面感觉卡顿,而 useEffect 则不会产生这种阻塞效果。
- 因此,如果回调函数需要操作 DOM 或者进行某些与界面交互相关的操作,可以考虑使用 useLayoutEffect;如果不需要进行交互,或者交互操作可以忍受一定的延迟,可以使用 useEffect。
3. 视图更新如何影响性能和用户体验
- 视图更新是一种相对费时的操作,如果更新操作过于频繁或过于复杂,可能会影响页面的性能和响应速度,甚至引起页面卡顿等问题。
- 对于频繁进行更新的组件,可以考虑使用
React.memo
、useMemo
、useCallback
等优化方式,避免重复的计算和更新。 - 对于需要操作 DOM 或进行其他交互操作的组件,要特别注意使用
useLayoutEffect
和useEffect
时的延迟问题和阻塞效果,避免影响页面的流畅度和响应速度。同时,也要考虑好性能和用户体验之间的平衡,避免过度追求优化而影响用户的体验。