6个React Hook最佳实践技巧

2020 年 11 月 16 日

6个React Hook最佳实践技巧


本文最初发布于 Medium 网站,经原作者授权由 InfoQ 中文站翻译并分享。


在过去,像状态和生命周期函数这样的 React 特性只适用于基于类的组件。基于函数的组件被称为哑(dumb)、瘦(skinny)或表示(presentational)组件,因为它们无法访问状态和生命周期函数。


但是自从 React Hooks 发布以来,基于函数的组件已升格为 React 的一等公民。它使函数组件能够以新的方式编写、重用和共享 React 代码。


在这篇文章中,我将分享 6 个关于 React Hooks 的技巧。你可以把它当作一份指南,在将 Hooks 实现到组件中时可以拿来参考。

遵守 Hooks 规则

这条规则看起来是句废话,但无论是新手还是经验丰富的 React 开发人员,都常常会忘记遵循 React Hooks 的规则。这些规则包括:

仅在顶级调用 Hooks

不要在循环、条件和嵌套函数内调用 Hooks。当你想有条件地使用某些 Hooks 时,请在这些 Hooks 中写入条件。


不要这样做:

if (name !== '') { useEffect(function persistForm() {   localStorage.setItem('formData', name); });}
复制代码


相比之下,你应该这样做:


useEffect(function persistForm() {  if (name !== '') {    localStorage.setItem('formData', name);  }});
复制代码


这条规则能确保每次渲染组件时都以相同的顺序调用 Hooks。这样一来,React 就能在多个 useState 和 useEffect 调用之间正确保留 Hooks 的状态。

仅从函数组件调用 Hooks

不要从常规 JavaScript 函数中调用 Hooks。仅从函数组件或自定义 Hooks 中调用 Hooks。


遵循这一条规则,可以确保组件中的所有状态逻辑在源代码中都能清晰可见。

使用 ESLint 的 React Hooks 插件

React 团队还创建了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,以帮助开发人员在自己的项目中以正确的方式编写 React Hooks。这个插件能够帮助你在尝试运行应用程序之前捕获并修复 Hooks 错误。


它有两条简单的规则:


  • react-hooks/rules-of-hooks

  • react-hooks/exhaustive-deps


第一条规则只是强制你的代码符合我在第一个技巧中说明的 Hooks 规则。第二个规则,exhaustive-deps 用于实施 useEffect 的规则:effect 函数中引用的每个值也应出现在依赖项数组中。


例如,下面这个 userInfo 组件会触发 exhaustive-deps 警告,因为 userId 变量在 useEffect 内部被引用,但未在依赖项数组中传递:

function UserInfo({userId}) {  const [user, setUser] = useState(null)  useEffect(() => {    getUser(userId).then(user => setUser(user))  }, []) // no userId here  return <div>User detail:</div>}
复制代码


尽管 exhaustive-deps 这条规则看起来很烦人,但它能帮助你避免由未列出的依赖项引发的错误。

以正确的顺序创建函数组件

当创建类组件时,遵循一定的顺序可以帮助你更好地维护和改进 React 应用程序代码。


首先调用构造器并启动状态。然后编写生命周期函数,接着编写与组件作业相关的所有函数。最后编写 render 方法:

const propTypes = {  id: PropTypes.number.isRequired,  url: PropTypes.string.isRequired,  text: PropTypes.string,};

const defaultProps = { text: 'Hello World',};class Link extends React.Component { static methodsAreOk() { return true; } constructor(props) { super(props) this.state = { user = null } } componentDidMount() { console.log('component did mount') } componentDidUpdate() { console.log('component did update') } componentWillUnmount() { console.log('component will unmount') } render() { return <a href={this.props.url}>{this.props.text}</a> }}Link.propTypes = propTypesLink.defaultProps = defaultPropsexport default Lin
复制代码


编写函数组件时并没有构造器和生命周期函数,因此你可能会犯糊涂,因为这种结构并不像类组件里那样是强制的:

function App() {  const [user, setUser] = useState(null);  useEffect(() => {    console.log("component is mounted");  }, []);  const [name, setName] = useState('');  return <h1>React component order</h1>;}
复制代码


但就像类组件一样,为函数组件创建定义的结构能够改善项目的可读性。


建议先使用 useState Hook 声明状态变量,然后使用 useEffect Hook 编写订阅,接着编写与组件作业相关的其他函数。


最后,你得返回要由浏览器渲染的元素:

function App() {  const [user, setUser] = useState(null);  const [name, setName] = useState('');  useEffect(() => {    console.log("component is mounted");  }, []);  return <h1>React component order</h1>;}
复制代码


通过强制一种结构,可以让代码流在众多组件之间保持一致,看起来也比较亲切。

useState 的用法可以和类组件的状态完全一致,不只用于单个值

许多 useState 示例会向你展示如何通过声明多个变量来声明多个状态:

const [name, setName] = useState('John Doe');const [email, setEmail] = useState('johndoe@email.com');const [age, setAge] = useState(28);
复制代码


但是 useState 实际上既可以处理数组也可以处理对象。你依旧可以将相关数据分组为一个状态变量,如以下示例所示:

const [user, setUser] = useState(  { name: 'John Doe', email: 'john@email.com', age: 28 });
复制代码


这里有一个警告。使用 useState 的更新函数更新状态时,以前的状态会替换为新状态。这与类组件的 this.setState 不同,后者的新类中,新状态会与旧状态合并:

const [user, setUser] = useState(  { name: 'John', email: 'john@email.com', age: 28 });setUser({ name: 'Nathan' });// result { name: 'Nathan' }
复制代码


为了保留以前的状态,你需要创建将当前状态值传递到自身中的回调函数来手动合并它。由于上面的示例已将 user 变量分配为状态值,因此可以将其传递给 setUser 函数,如下所示:

setUser((user) = > ({ ...user, name: 'Nathan' }));// result is { name:'Nathan', email: 'john@email.com', age: 28 }
复制代码


根据数据在应用程序生命周期中的变化情况,建议在各个值彼此独立时将状态拆分为多个变量。


但是对于某些情况,例如构建一个简单的表单,最好将状态分组在一起,以便更轻松地处理更改和提交数据。


简而言之,你需要在多个 useState 调用和单个 useState 调用之间保持平衡。

使用自定义 Hooks 共享应用程序逻辑


在构建应用程序时,你会注意到一些应用程序逻辑会在许多组件中一次又一次地使用。


随着 React Hooks 的发布,你可以将组件的逻辑提取到可重用的函数中作为自定义 Hooks,如我在以下文章中所展示的那样:


可扩展 React 项目的 6 个技巧和最佳实践:

https://blog.bitsrc.io/best-practices-and-tips-for-a-scalable-react-application-db708ae49227


你可以使用 Bit 之类的工具将 Hooks 发布到单个集合中,这样你就可以在不同的应用程序中安装和重用它们。它不需要你创建一个全新的“Hooks 库”项目,你可以一点点将新的 Hooks 从任何项目“推入”你的共享集合。



针对这个方法,唯一要强调的是你不能在类组件中使用 Hooks。所以如果你的项目中还有老式的类组件,就需要将它们转换为函数,或者使用其他可重用逻辑模式(HOC 或渲染 Props)。

使用 useContext 避免 prop drilling

prop-drilling 是 React 应用程序中的常见问题,指的是将数据从一个父组件向下传递,经过各层组,直到到达指定的子组件,而其他嵌套组件实际上并不需要它们。


考虑以下示例:


https://bit.dev/nsebhastian/tutorial-examples/prop-drill-example?example=5f941e4445728c001924150a


从示例中可以看到,即使 Hello 组件不需要 props,App 组件也会通过 Hello 组件将 name props 传递给 Greeting 组件。


React Context 是一项功能,它提供了一种通过组件树向下传递数据的方法,这种方法无需在组件之间手动传 props。父组件中定义的 React Context 的值可由其子级通过 useContext Hook 访问。


在下面的示例中,我将 name 数据(而非 props)传递给 Context Provider,给代码做了重构:


https://bit.dev/nsebhastian/tutorial-examples/prop-drill-example?example=5f941fae45728c001924150e


App 的任何子组件都可以通过 useContext Hook 访问数据。可以从文档中了解有关 useContext Hook 的更多信息:


https://reactjs.org/docs/hooks-reference.html#usecontext

总结

React Hooks 是 React 库的重要补充,因为它允许你用独一无二的方式编写、重用和共享 React 代码。


随着 Hooks 开始改变开发人员编写 React 组件的方式,需要一套新的编写 React Hooks 的最佳实践,以便多个团队之间更轻松地开发和协作。


虽然本文肯定还有遗漏的内容,但我希望以上分享的技巧能多少帮助你在项目中以正确的方式编写 React Hooks。


查看英文原文:


https://blog.bitsrc.io/best-practices-with-react-hooks-69d7e4af69a7


2020 年 11 月 16 日 10:191446

评论 2 条评论

发布
用户头像
满怀期待的点进来,面无表情的出去
2020 年 11 月 18 日 14:01
回复
用户头像
太基础了,没啥实践
2020 年 11 月 16 日 15:07
回复
没有更多评论了
发现更多内容

架构师训练营第 1 期 -- 第八周学习总结

发酵的死神

极客大学架构师训练营

第四周课后练习作业二

lithium

Architecture Phase1 Week8:HomeWork

phylony-lu

极客大学架构师训练营

架构师训练营 W04 作业

Geek_f06ede

极客大学架构师训练营

week4学习小结

幸福小子

互联网系统架构

第四周课后练习作业一

lithium

极客大学架构师训练营

架构师训练营第 1 期第 8 周作业

好吃不贵

极客大学架构师训练营

架构师训练营第 1 期 -- 第八周作业

发酵的死神

极客大学架构师训练营

Wi-Fi+BLE 通断器开发资料全开源!快速打造您的智能家居“改装神器”

智能物联实验室

人工智能 物联网 智能家居 通断器

架构师训练营第八周课后作业

Gosling

极客大学架构师训练营

推荐好书:《使用Python进行图像处理和采集》第二版(附下载方式)

计算机与AI

Python 图像处理

架构师训练营第 4 周学习总结

菜青虫

极客大学架构师训练营

架构师训练营第 4 周课后练习

菜青虫

极客大学架构师训练营

架构师训练营第四周作业

韩儿

架构师训练营第四周作业

丁乐洪

架构师训练第4周:作业一

leo

极客大学架构师训练营

为什么继承 Python 内置类型会出问题?!

Python猫

c Python 编程 程序员

一个典型的大型互联网应用系统使用了哪些技术方案和手段,主要解决什么问题?请列举描述

幸福小子

互联网系统架构

架构师训练第4周:作业二

leo

极客大学架构师训练营

Architecture Phase1 Week8:Summarize

phylony-lu

极客大学架构师训练营

「八大排序算法」16张图带你彻底搞懂基数排序

bigsai

排序算法 基数排序

架构师训练营第四周作业2

韩儿

极客时间架构师训练营 1 期 - 第 8 周总结

Kaven

为什么说 Pulsar 是云原生的消息平台?

tison

云原生 消息队列 Apache Pulsar

Week 8总结

黄立

亚新资本开创金融理财新征程

Geek_459987

架构师系列之5:互联网大数据分析系统架构例子解析

桃花原记

架构师训练营-week08

睁眼看世界

极客大学架构师训练营

产品发布 | 准备好提升你的 ITSM 了吗?

Atlassian速递

DevOps Atlassian ITSM ITIL

架构师入门学习感悟四

莫问

系统架构总结

Mars

系统架构

6个React Hook最佳实践技巧-InfoQ