React Hooks 最佳实践

1.前言

  • 存在 反应 16.8 以前,状态和生命周期函数之类的东西 反应 特性只适用于类组件,功能组件由于无法访问状态和生命周期功能,只能作为 UI 组件使用。 反应钩子 功能组件的出现使功能组件能够以新的方式编写、重用和共享 反应 代码。
  • 也开始在我们的几个项目中使用它 反应钩子 .但是,在实际开发过程中,我发现 反应钩子 除了带来干净的代码之外,还有不恰当使用的情况。
  • 在这篇文章中,我 反应钩子 总结了使用,并分享了我认为的最佳实践供大家参考。

2.一些错误示例

2.1 用 React.memo() 包装类组件

  • 现在有一个日期 <ShowDate/> 组件,每秒都会重新渲染,考虑到页面性能,我们需要控制 <Title/> 组件重新渲染。

image.png

  • 错误示例❌

image.png

  • 问题分析
  • 反应.memo() 是的 反应 顶层 API 并且是一个高阶组件,它的作用类似于 React.PureComponent ,它控制组件的重新渲染,但以下情况除外: 反应.memo() 是真的 功能组件 优化, React.PureComponent 在定义类组件时使用。
  • 相比 React.PureComponent , 反应.memo() 可以支持指定参数,相当于 应该组件更新 的作用,如果不传参数,只会默认执行 道具 浅比较。

image.png

  • 正确示范✅
  • 类组件:

image.png

  • 功能组件:

image.png

2.2 使用 useState 会导致不必要的重新渲染

  • 现在组件有两个按钮,点击【增加数量】按钮将 数数 加1,点击【提交数量】按钮,将数量提交到后台。
  • 错误示例❌

image.png

  • 问题分析
  • 反应 任何 状态 更新触发组件及其子组件的重新渲染。在上面的例子中,我们没有 使成为 部分使用 数数 这个 状态 ,我们每次设置计数器都会触发不必要的渲染,可能会影响性能或者有其他副作用。
  • 反应 提供了一个 使用参考挂钩 ,它返回一个可变的 参考 对象(这个 参考 只有一个对象 当前的 属性),它会在组件的整个生命周期中持续存在。 使用参考 更改不会自动呈现页面,使用此功能,我们可以对代码进行更改。
  • 正确示范✅

image.png

2.3 使用过时的状态

  • 现在有一个组件,目标是在单击按钮时增加状态变量 数数 .
  • 错误示例❌

image.png

  • 问题分析
  • 虽然按钮被点击, 反应 转移 setCount(计数 + 1) 3 次,但计数只会增加 1。这是因为状态值仅在下一次渲染时更新。
  • 这是一个避免遇到过时变量的好规则:如果您使用当前状态来计算下一个状态,请始终使用函数式方法来更新状态。
  • 正确示范✅

image.png

3. 最佳实践

  • 在第 2 章中,我们介绍了一些 反应钩子 错误演示,如果没有 恢复 , 可能会对业务代码造成意外结果。与前几章不同,本章将介绍一些 反应钩子 可以更好的在项目中实现。

3.1 使用 ESLint 的 React Hooks 插件

  • 虽然 反应 官方文档给出了两个 挂钩规则 ,但是新手和经验丰富的 React 开发人员都经常忘记关注 反应钩子 的规则。
  • 所以, 反应 该团队开发了一个 eslint-plugin-react-hooks ESLint 帮助开发人员在自己的项目中以正确方式编写的插件 反应钩子 .
  • 这个插件帮助我们在尝试运行应用程序之前捕获和修复 挂钩 错误,所以最好将此插件添加到我们使用的插件中 反应钩子 在项目中。
  • 必须注意的是, eslint-plugin-react-hooks 插件约定,当在以驼峰命名的函数(被认为是一个组件)或在 使用某物 功能(作为自定义 ) 在通话中 挂钩 小时, 皮棉 正常工作的规则:

image.png

  • 如以下代码所示:

image.png

  • 当我们在项目中使用 eslint-plugin-react-hooks 插件之后,它发生 埃斯林特 报错,说明我们不能有条件调用 挂钩

image.png

  • 当我们将组件更改为匿名默认导出时:

image.png

image.png

  • 因此,在实际开发过程中,在编写功能组件时,需要以大驼峰的方式对组件进行命名;编写自定义组件时 , 也跟着 使用某物 命名规则。

3.2 以正确的顺序创建功能组件

  • 在创建类组件时,遵循一定的顺序可以帮助我们更好的维护和改进 反应 申请代码:
  • 静止的 类属性以
  • 构造函数, 构造函数
  • 吸气剂 / 二传手
  • 组件生命周期
  • _ 开头的私有方法
  • 事件监听器方法, 处理*
  • 使成为* 开始的方式,有时 使成为() 方法中的内容会被分成不同的函数,这些函数分别是 使成为* 开始
  • 使成为() 方法
  • 尽管代码没有按上述顺序组织,但组件功能无关紧要。但是,如果所有组件都按照这种顺序编写,维护起来会容易很多,而且多人协作时,其他人一眼就能看懂代码。
  • 对于函数组件,没有所谓的构造函数和生命周期函数,只要按照代码编写即可 挂钩规则 实现,一般不会遇到什么问题:

image.png

  • 与类组件一样,虽然编写顺序对功能实现没有重大影响,但为功能组件创建定义的结构可以提高项目的可读性。
  • 推荐 在函数顶部使用 使用状态钩子 使用参考挂钩 ,然后使用 使用效果挂钩 编写订阅,然后编写组件作业相关的其他函数,最后返回浏览器要渲染的元素:

image.png

  • 通过强制使用一个结构,可以在很多组件之间保持代码流的一致,有利于组件的后期维护,提高团队的开发效率。

3.3 掌握useEffect中的异步用法

  • 我们都知道, 使用效果 用于引入具有副作用的操作,最常见的是从服务器请求数据。正确的 反应钩子 不熟悉的小白可能不会用 打字稿 项目中出现以下错误:

image.png

  • 跳转路由时(离开当前界面),会遇到如下错误:

image.png

  • 而我们都知道, 打字稿 可以帮助我们进行静态类型检查,如果使用 打字稿 项目的 使用效果 如果错误使用了异步函数,编译器会及时报以下错误(而不是在组件卸载时触发错误):

'() => Promise' 类型的参数不可分配给'EffectCallback' 类型的参数。

  • 这个错误的原因是使用异步函数导致我们使用 使用效果() 返回一个值而不是一个函数。通常, 效果 创建的资源,例如订阅或计时器 ID,需要在组件销毁之前进行清理。为此,传递给 使用效果 该函数应该返回一个清理函数。原本需要退回的是 清理 函数,而使用异步函数会使 打回来 返回 承诺 代替 清理 函数,所以当组件被销毁时,会发生错误。
  • 为了避免直接 承诺 作为 效果 对于返回值,我们可以分离异步函数:

image.png

  • 或者使用 IIFE(立即调用函数表达式)匿名函数表达式:

image.png

3.4 尽量避免使用useLayoutEffect

  • 使用效果 使用布局效果 ,是不是两者的工作方式非常相似 反应钩子 ,它们之间的区别在于执行时间:
  • 使用效果 渲染函数执行并绘制到屏幕后, 异步 实施
  • 使用布局效果 渲染函数执行后,屏幕重绘前 同步 实施
  • 因为 使用布局效果 同步执行,因此阻塞,直到 影响 页面只有在执行完成后才会重绘,如果 影响 里面的代码执行速度很慢,可能会导致性能问题。所以, 反应 官方,使用标准 使用效果 以避免阻止视觉更新。
  • 使用布局效果 它不是没用的,下面介绍它的一种使用场景。

image.png

  • 单击按钮时,页面会闪烁:

image.png

  • 原因是, 使用效果 触发时间将延迟到 DOM 绘图完成。所以我们点击按钮 设置状态 之后,其实 将高度设置为 50 -> 绘制屏幕 -> 将高度设置为 100 两次 使成为 过程,所以肉眼会看到中间的过渡 状态 由此产生的闪烁感。
  • 如前面提到的, 使用布局效果 将在所有 DOM 修改后同步调用。在浏览器运行绘图之前, 使用布局效果 内部更新将同步刷新。因为这 功能,我们可以用它来制作 DOM 渲染速度慢了一点,请稍等 状态 真正的更新完成后,渲染浏览器画面。
  • 我们将 使用效果 改成 使用布局效果

image.png

  • 如您所见,页面不再闪烁:

image.png

  • 在大多数场景中, 使用效果 是正确的选择。如果遇到闪烁的场景,可以切换到 使用布局效果 ,看看能不能解决问题。

3.5 避免使用 useContext 进行 prop-drilling

  • 螺旋钻 是的 反应 应用程序中的一个常见问题,即通过多个中间体从父组件传递数据 反应 组件层向下传递,直到它最终到达指定的子组件,而中间的组件实际上并不需要这些数据。如以下代码所示:

image.png

  • 即使可以看到 <Hello/> 组件不需要 道具 , <App/> 组件也会通过 <Hello/> 组件将 名字道具 传递给 <Greeting/> 成分。当组件层次比较深的时候,如果我们要放 名字道具 将属性名称更改为 用户名 ,需要改成多个组件,会产生很多多余的工作。
  • 反应上下文 是一种功能,它提供了一种将数据向下传递到组件树的方法,而无需在组件之间手动传递 道具 .在父组件中定义 反应上下文 的值可以由其子代传递 使用上下文挂钩 使用权。

image.png

  • 改造后, <App/> 的任何子组件 使用上下文挂钩 访问数据。

3.6 善用useMemo / useCallback

  • 如前所述,我们可以使用 反应.memo() 这个高阶组件控制功能组件的重复渲染。
  • 重复渲染的原因是 反应钩子 使用函数组件,对父组件的任何修改都会导致子组件的函数被执行,从而重新渲染。
  • 因此,出于性能原因,除了使用 反应.memo() 要包装函数组件,我们还可以使用 反应 它提供了 使用回调 使用备忘录 缓存函数和函数的返回值。
  • 必须注意的是, 使用回调 使用备忘录 结合 反应.memo() 为了避免子组件的无效渲染。

3.7 善用惰性初始化函数提高性能

  • 使用状态 函数的ts定义如下:

image.png

  • 看得见, 使用状态 支持我们在调用指定时直接传入一个值 状态 默认值,例如 使用状态(0) , 使用状态({名称:'汤姆'}) 等等,还允许我们传入一个函数来逻辑计算默认值:

image.png

  • 比如下面的例子:

image.png

  • 当功能组件更新时,功能组件中的所有代码都会重新执行。获得 初始状态 的初始值是一个相对昂贵的 IO 操作。而当每次函数组件 重新渲染 ,第一行代码会执行一次,造成不必要的性能损失。因此,我们可以修改代码如下:

image.png

  • 当我们最初 状态 当需要通过复杂的计算或者开销比较大的时候,可以传入一个函数(我们称之为惰性初始化函数),在函数中计算并返回初始值 状态 ,该函数只在初始渲染时调用,组件更新时不会再次调用,可以避免这种场景下不必要的性能问题。

3.8 善用自定义Hooks对逻辑及相关状态进行捆绑封装

  • 存在 反应钩子 在它出现之前,我们可以通过 渲染道具 高阶组件 (HOC) 两种实现方式 反应 组件的状态逻辑是共享的。
  • 现在,感谢 挂钩 有了逻辑封装的能力,我们就可以对常用的逻辑进行封装,降低代码复杂度。
  • 而且,一个页面上往往有很多状态,这些状态都有自己的处理逻辑。如果使用类组件,这些状态和逻辑会混在一起,不够直观:

image.png

  • 通过使用 反应钩子 之后,我们可以将状态与逻辑关联起来,拆分成多个自定义 挂钩 ,代码结构会变得更清晰:

image.png

  • 比如现在有多个页面,都需要用到 蚂蚁资本 将自己的表单嵌入到组件中,并且可以由组件自己控制 模态 显示:

image.png

  • 为了避免在每个页面上写重复的逻辑,我们实现了 使用ActionModal 风俗 挂钩 ,代码显示如下:

image.png

  • 在组页面上使用代码示例:

image.png

  • 可以看到,通过将多个页面共有的逻辑封装在 使用ActionModal 可以大大减少代码量和维护成本。
  • 需要强调的是,我们不能在类组件中使用 挂钩 ,所以如果项目中有老式的类组件,需要使用自定义 挂钩 ,您需要将它们转换为功能组件或使用其他可重用的逻辑模式( HOC 或者 渲染道具 ),如果 包装成 HOC

image.png

  • 使用时,将我们的目标组件与上面的一起使用 带钩子HOC 打包,然后我们可以 宽度 属性被传递给目标组件:

image.png

  • 自从官方 挂钩 远远不能满足我们的发展需要。目前社区其他人已经封装了一些自定义。 挂钩 图书馆,比如
    反应使用 等等,你可以学习定制 挂钩 实现。

3.9 将单元测试添加到自定义 Hooks

  • 作为比较流行的习俗 挂钩 图书馆, 反应使用 提供了声音单元测试。通过查看它的 包.json 该文件可以看到它使用 @testing-library/react-hooks 这个包自定义了它的实现 挂钩 做单元测试。
  • 本节重点介绍如何在项目中引入 @testing-library/react-hooks 这个库,实现我们的终极目标——写自定义 挂钩 添加单元测试。
  • 一开始,我跟着 反应钩子测试库 文档,安装 @testing-library/react-hooks 在那之后,我想我可以开始写一个测试,运行时报告结果。 (0 , _reactTestRenderer.act) 不是函数 报错,通过一番搜索,发现文档虽然写了 反应域 反应测试渲染器 只需安装其中之一。但是文档说的不是真的,即使项目已经安装 反应域 ,还需要安装 反应测试渲染器 要解决此错误问题:

image.png

  • 而且,非常重要的是,安装的版本 反应 / 反应域 反应测试渲染器 版本必须匹配,否则仍会报上述错误。
  • 安装时 反应测试渲染器 以后单次测试可以成功运行,但是在运行应用程序时,会出现提示。 解决React中“xxxx”不能作为JSX组件的问题 ,原因是 反应钩子测试库 文档中提到如果我们的项目同时安装 反应域 反应测试渲染器 , 将使用 反应测试渲染器 默认设置:
  • 反应测试渲染器 包.json 中间 依赖关系 已经设置 @types/反应 版本是 >=16.9.0 ,虽然在我们的项目中 包.json Zhongwei @types/反应 指定的版本是 ^16.9.23 ,但 反应测试渲染器 将安装自己的依赖项 @types/反应 迄今为止最新 18.x 版本并作为项目默认值。
  • 因为 反应 18 带来了很多新功能,也定义了很多组件类型,安装 @types/反应 版本和我们项目使用的一样 反应 版本不匹配,导致这样的TS类型错误。
  • 最终解决方案在 包.json 通过 决议 现场分配 @types/反应 版本,成功引入 反应测试渲染器 这个单元测试包:

image.png

  • 由于习俗 挂钩 它封装了一些常用的逻辑,会在多个组件中复用,所以自定义 挂钩 单元测试是必须的!

3.10 * 使用 Redux Hooks 而不是连接

  • 这篇文章是为 反应 + 还原 项目实践建议,如果项目使用 移动 或者其他状态管理库,可以跳过这个做法。
  • 反应还原 存在 7.1 一套 挂钩 作为现有的 连接() 高阶组件的替代方案:
  • 使用选择器 :代替 连接() mapStateToProps 方法。它将使用的函数作为参数 还原 存储的状态并返回所需的状态。
  • 使用调度 :代替 连接() mapDispatchToProps 方法。它所做的就是返回 店铺 派遣 方法,所以我们也可以手动调用 派遣 .
  • 下面的代码比较了两种写法的区别:
  • 利用 连接()

image.png

  • 利用 Redux 钩子

image.png

  • 可见,会 使用选择器 使用调度 作为 连接() 代码更干净,更有条理。

4.总结✍️

  • 反应钩子 作为 反应 库的一个很好的补充,它使功能组件能够以新的方式编写、重用和共享 反应 代码。 .
  • 随着 挂钩 开始改变开发人员编写的内容 反应 组件方式,你需要编写一组 反应钩子 团队成员之间更容易开发和协作的最佳实践。
  • 虽然本文肯定缺少一些东西,但我希望我在本文中分享的技巧能够帮助您在项目中编写正确的方式。 反应钩子 .

5.参考文章

  1. 挂钩规则
  2. 详细的 React Hooks
  3. React 的新上下文 API
  4. React高阶组件(HOC)介绍与实践
  5. 反应钩子测试库
  6. React.memo 和 useMemo
  7. 面试官:如何解决 React useEffect hook 造成的死循环问题

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。

这篇文章的链接: https://homecpp.art/4910/7545/1818

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/27328/15501110

posted @   哈哈哈来了啊啊啊  阅读(178)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示