大碗宽面和 React Hooks
今年年初,React 团队发布了 React version 16.8.0,带来了一个新东西:Hooks。
如果把 React 比作一大碗宽面的话,Hooks 就是其中非常有嚼劲并且味道很好的那几根面。
所以,Hooks 到底是什么东西?它为什么值得我们花时间去了解呢?
介绍 | Introduction
在 React 中添加 Hooks 最主要的原因之一就是为编写(和共享)组件之间的功能提供一种更强大以及更富有表现力的方法。
In the longer term, we expect Hooks to be the primary way people write React components
React Team
如果 Hooks 真的会变得像 React 团队所期待的那样,我们何不以一种更有趣的方式学习它呢?
大碗宽面 | The Noodles Bowl
假设 React 是用漂亮的碗装着的一大碗宽面。
全世界的人民都觉得这一大碗宽面真香。
做这碗面的厨师意识到碗里的一些面条并没有让人们很满意。
大部分的面条吃起来确实不错,但在吃掉它们的同时,也为吃面的人带来了一些复杂的麻烦 —— 就比如渲染的 props 和 HOC?
那么,这些做面条的人搞了些什么事情呢?
他们做了正确的选择 —— 并不是把之前所有的面条都倒掉,而是扯了几条新的面。
这些新的面条就叫作 Hooks。
这些面条的存在只有一个原因:让开发者更容易的去做一些之前已经做过的事情。
这些新面条也没有很特别。当你开始去品尝它们的时候,你会惊觉:还是原来的配方 —— JavaScript Functions!
和先前所有的好面条一样,虽然这十根新面条都叫 Hooks,但它们也都有各自不同的名字。
它们的名字都以 use 开头,(就好比《大碗宽面》的作者经常会听到来自女性朋友的建议:useCondom),比如 useState / useEffect 等等。
这十根面条都有一些相同的原材料。在了解其中一根面条之后,也会帮助你理解其他的面条。
有点意思?来,吃面!
The State Hook
正如我在上面说到的,Hooks 就是函数,准确的说,Hooks 是十个函数。这十个 React 中新引入的函数会让你在编写和传递函数的功能时感到纵享丝滑。
我们首先来看第一个 Hook:useState。
曾几何时,我们无法在函数式组件中使用 state,现在好了,Hooks 宛如救世主一般出现在我们的视野里。
通过 useState,函数式组件也能够拥有(更新)自己的 state,这就有点厉害了。
假设我们需要实现一个如下的计时器:
我们可以这么写这个 Counter 组件:
是不是觉得简单到飞起?
我问一个简单的问题:我们为什么非要写一个 Class Component?
现在,我们用 useState 把这个 Class Component 重构为一个函数式组件:
看出来有什么不同了吗?
来,逐行对比一下。
函数式组件不需要 class / extends 之类的语法。
不需要 render 函数。
对于上面的代码,有两个需要注意的地方:
1. 在一个函数式组件中,不能使用 this 关键字
2. 还没有定义名为 count 的 state 变量
接下来,我们在这个组件内部定义一个 handleClick 函数。
在重构之前,count 变量来自于 Class Component 内部的 state 对象。而在函数式组件中,count 变量即将来自于对 useState 等 hooks 的调用。
在调用 useState 时,需要传入一个参数,这个参数代表 state 的初始值,比如:useState(0),数字 0 表示即将被“追踪”的 state 的初始值。
在执行这个函数之后,我们会得到一个由两个元素组成的数组。
数组的第一个元素就是我们一开始就想要的 state 变量,第二个参数是用来改变这个 state 变量的函数。
虽然有些不同,但还是可以把这两者类比作某一个 state 和 setState 函数。
至此,我们就得到了这个重构后的组件。
除了代码层面明显的简化之外,还有一些值得回味的地方。
由于 useState 函数将返回值包裹在一个数组中,因此可以通过解构赋值方便又快速地得到数组中的每个值:
再者,我们注意到在重构后的 handleClick 函数中,更新 state 时并不需要 prevState 或保存 prevState 的引用。直接在调用 setCount 时传入新的值 count + 1 即可(你也可以在给 setCount 传入一个函数,但这一般只建议在 class component 的 setState 中使用,举个栗子:setCount(prevCount => prevCount + 1))。
多次调用 useState | Multiple useState calls
在 Class Component 中,不论有一个 state 或多个 state,我们都会将其定义在一个对象中。但是对于 useState,这就有些不同了。
在上面的栗子中,我们直接将变量传入 useState,而不是将其定义在一个对象内。如果现在需要另一个 state 变量呢?就比如下面的这个组件,新加了一个展示当前点击时间的功能。
正如你看到的那样,除了又调用了一次 useState 之外,hooks 的用法是一样的。
那么问题又来了,我们能不能直接将一个对象作为初始值传入 useState 呢?(就像 setState 那样维护一个包含所有的 state 对象?)
当然可以!不过这里需要注意一个致命的不同:setState 会将传入对象中的属性和 state 对象进行合并,而 useState 会直接用新传入的对象取代之前的对象。
The Effect Hook
在 Class Component 中开发者们经常会做一些日志上报 / 获取数据 / 管理订阅之类的操作。这些操作,都可以对应为 useEffect 中的 “effects”。
那么它怎么用?
简单来说,在调用 useEffect 时,只要把需要做的操作包裹在一个函数中并将这个函数传入 useEffect 就可以了,比如这样:
下一个很关键的问题就是,这个被传入 useEffect 中的函数什么时候会被调用呢?
在传统的 Class Component 中,有一些被叫作生命周期的勾子函数,比如 componentDidMount / componentDidUpdate。而正因为函数式组件中没有这些生命周期方法,useEffect 就作为备胎顶上了。
因此,在上面的那一坨代码中,useEffect 中的 effect 函数会在函数式组件完成渲染(componentDidMount)和每次更后(componentDidUpdate)执行。
现在将这个 useEffect 函数直接加进 Counter 中,得到这个鸟东西:
在组件每次更新后 effect 函数都会被执行,这虽然挺有趣的,但这通常不是开发者们想要的。如果我只想在组件完成渲染后执行这个 effect 函数,怎么搞?
这时 useEffect 的第二个参数就派上用场了:一个定义 effect 函数依赖 state 的数组。
如果传入一个空数组,effect 函数就只会在组件渲染之后被执行,之后的 re-render 都不会触发 effect 函数的执行。
如果给这个数组中传入了值,那么 effect 函数会在组件完成渲染和每次任一传入的state变量更新时被执行
如上,effect 函数除了会在组件完成渲染时执行一次以外,还会在每次 count 更新时被执行一次。
那订阅事件又如何处理呢?
直接看下面这个:
在上面的这个 effect 中,在组件完成渲染后,一个点击事件会被加在整个 window 上。
那又如何在组件卸载后取消事件订阅呢?
useEffect 如果不能处理这种情况的话,是真的可以回炉重造了。
如果开发者在自己的 effect 函数中 return 一个函数,这个被 return 的函数就会在组件卸载时被执行,所以把取消事件订阅的步骤放在这里做是再合适不过的了:
你还可以在 useEffect 中做很多事情,比如后端接口的调用。
实现自己的 Hooks | Build Your Own Hooks
从这篇文章的开始到现在,我们都在吃 React 这以大碗宽面中自带的面条。除此之外,我们还可以撸起袖子自己去扯面条,而这些面条就叫作 Custom Hooks。所以应该怎么扯呢?
要明白一点,Custom Hooks 只不过就是一个常规的 Function 而已,但是它的名称必须以 use 开头。另外,如果需要的话,我们也可以在 Custom Hooks 内调用 React Hooks。
比如下面这个栗子:
Hooks 的规则 | The Rules of Hooks
有两条关于 Hooks 的规则需要强调一下:
1. 只能在组件的一开始调用 Hooks,不能在条件语句 / 循环 / 嵌套函数中调用。
2. 只能在 React 函数式组件和 Custom Hooks 中调用 Hooks。
其它面条 | Other Noodles
除了 useState 和 useEffect 之外还有八根 Hooks 的面条,欲知后事如何,请查阅 React 官方文档。
参考文章
1. https://react.docschina.org/docs/hooks-intro.html
2. https://medium.freecodecamp.org/learn-the-basics-of-react-hooks-in-10-minutes-b2898287fe5d
以上是 大碗宽面和 React Hooks 的全部内容, 来源链接: utcz.com/z/384008.html