大碗宽面和 React Hooks

301 阅读8分钟
原文链接: mp.weixin.qq.com

当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 大碗宽面 吴亦凡 - 大碗宽面

今年年初,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