编辑
2023-12-16
React
00
请注意,本文编写于 68 天前,最后修改于 68 天前,其中某些信息可能已经过时。

目录

基本使用
实现原理
最佳实践
总结

React Hooks是一种在函数组件中使用状态和生命周期等特性的方法。useEffect是其中一个常用的Hook,它可以让你在组件渲染后执行一些副作用操作,比如发送网络请求、订阅事件、修改DOM等。在本文中,我们将介绍useEffect的基本使用、实现原理、最佳实践,并给出一些代码示例。

基本使用

useEffect的基本语法如下:

js
useEffect(() => { // 在这里执行你的副作用操作 return () => { // 在这里执行你的清理操作,比如取消订阅、移除事件监听器等 }; }, [deps]);

useEffect接受两个参数,第一个是一个函数,第二个是一个依赖数组。

第一个函数会在每次组件渲染后执行,除非你指定了依赖数组。如果你指定了依赖数组,那么只有当数组中的任何一个值发生变化时,才会执行第一个函数。

如果你想让第一个函数只执行一次,那么你可以传递一个空数组作为依赖数组。第一个函数可以返回一个清理函数,这个清理函数会在组件卸载时或者下一次执行第一个函数之前执行,用于清理上一次的副作用。

下面是一个简单的例子,展示了如何使用useEffect来获取用户的地理位置,并在组件卸载时取消订阅:

js
import React, { useState, useEffect } from "react"; function Location() { const [position, setPosition] = useState({ latitude: 0, longitude: 0 }); useEffect(() => { // 创建一个订阅对象 const geo = navigator.geolocation.watchPosition( (pos) => { // 更新位置状态 setPosition({ latitude: pos.coords.latitude, longitude: pos.coords.longitude, }); }, (err) => { // 处理错误 console.error(err); } ); // 返回一个清理函数 return () => { // 取消订阅 navigator.geolocation.clearWatch(geo); }; }, []); // 传递一个空数组,表示只执行一次 return ( <div> <p>Latitude: {position.latitude}</p> <p>Longitude: {position.longitude}</p> </div> ); }

实现原理

要理解useEffect的实现原理,我们需要先了解一下React的渲染机制。

React使用了一种叫做Fiber的数据结构来表示组件树,每个Fiber节点都对应一个组件实例,包含了该组件的类型、状态、属性、子节点等信息。

React在渲染组件时,会遍历Fiber树,创建或更新对应的DOM节点,这个过程叫做渲染阶段。在渲染阶段,React可能会因为优先级或其他原因而中断或重启渲染,所以这个阶段是可以被打断的。在渲染阶段结束后,React会进行提交阶段,在这个阶段,React会将渲染的结果应用到DOM上,这个阶段是不可被打断的。

useEffect的执行时机就是在提交阶段之后,也就是说,当组件已经渲染到DOM上后,才会执行useEffect的回调函数。

这样做的好处是,避免在渲染阶段执行可能导致副作用的操作,比如修改DOM、发送网络请求等,这些操作可能会影响组件的渲染性能或造成不一致的状态。

另外,这样也可以保证useEffect的回调函数总是能获取到最新的状态和属性,因为它们已经被同步到DOM上了。

那么,React是如何实现useEffect的呢?其实,React在渲染组件时,会收集所有的useEffect回调函数,并将它们存放在一个队列中,然后在提交阶段结束后,依次执行这个队列中的回调函数。

同时,React也会收集所有的清理函数,并将它们存放在另一个队列中,然后在组件卸载时或者下一次执行相应的useEffect回调函数之前,依次执行这个队列中的清理函数。这样,React就实现了useEffect的基本功能。

但是,这还不够,因为useEffect还有一个依赖数组的参数,它可以控制useEffect的回调函数是否需要执行。为了实现这个功能,React在渲染组件时,会比较当前的依赖数组和上一次的依赖数组,如果它们不相同,那么就会将对应的useEffect回调函数放入队列中,否则就会跳过它。这里的比较是使用Object.is算法,也就是说,只有当依赖项的值严格相等时,才会认为它们没有变化。

这意味着,如果依赖项是一个对象或一个数组,那么即使它们的内容没有变化,但是每次都重新创建,也会导致useEffect重新执行。因此,建议使用useCallbackuseMemo来缓存这些依赖项,避免每次都重新创建。

这样,React就可以根据依赖数组来优化useEffect的执行效率,避免不必要的副作用操作。

最佳实践

使用useEffect时,有一些最佳实践可以遵循,以提高代码的可读性、可维护性和性能。下面列举了一些常见的最佳实践:

  • 尽量将不同的副作用操作分开,使用多个useEffect,而不是将所有的副作用操作放在一个useEffect中。这样可以让代码更清晰,也可以让React更好地优化useEffect的执行。
  • 尽量指定依赖数组,而不是省略它。如果省略了依赖数组,那么useEffect的回调函数会在每次渲染后都执行,这可能会导致性能问题或不一致的状态。如果你想让useEffect的回调函数只执行一次,那么你可以传递一个空数组作为依赖数组。
  • 尽量将依赖数组中的值保持稳定,而不是每次都重新创建。如果依赖数组中的值每次都变化,那么useEffect的回调函数也会每次都执行,这可能会导致性能问题或不一致的状态。如果你需要依赖一个函数或一个对象,那么你可以使用useCallback或useMemo来缓存它们,避免每次都重新创建。
  • 尽量在useEffect的回调函数中返回一个清理函数,用于清理副作用操作,比如取消订阅、移除事件监听器等。这样可以避免内存泄漏或其他问题。
  • 尽量避免在useEffect的回调函数中修改状态或属性,因为这可能会导致无限循环或其他问题。如果你需要根据副作用操作来修改状态或属性,那么你可以使用setState或useReducer来异步地更新它们,避免直接修改它们。

总结

useEffectReact Hooks中一个非常强大和灵活的Hook,它可以让你在函数组件中执行各种副作用操作,比如发送网络请求、订阅事件、修改DOM等。在使用useEffect时,你需要注意它的执行时机、依赖数组、清理函数等细节,以及遵循一些最佳实践。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:CreatorRay

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!