React Hooks 基础教程

最近在写一个前端页面,看了一个 UI 框架的 Example,发现已经看不懂 React 了。上次写 React 的时候,还都是用 Class Based Component, 我还花了一些时间弄明白一个组件的“生命周期”,没想到这么短的时间,已经不流行使用 Class 写组件了。现在用一个叫 React Hooks 的东西,可以通过函数写出来组件。为了看懂现在的 React 代码,我又去学了 Hooks,这边文章来总结我对 Hooks 的理解。

Hooks 就是一些 React 提供的内置函数,通过 Hooks 就可以在 Function 中操作组件的状态(state)了。在我看来解决了两个问题:

  1. Function 中是不可以定义 state 的,所以以前 Function 只能用来写 stateless 的组件,如果有一天你觉得这个 stateless 的组件要加入状态了,那就必须把它先变成 Class Base Component 才行;
  2. Class 组件本身也有问题,它这个设计是要求开发者按照组件的生命周期来写代码,constructor() -> componentDidMount() -> componentWillUpdate() 这种方式,按照 React 的逻辑来组织代码,而不是按照代码本身表达的业务逻辑来组织代码。以前每次写一个新的组件的时候,我都要依靠 Vim 的模板功能生成一个代码模板,但是现在不需要了。写 Hooks 几乎没有模板代码。

通过例子认识 Hooks

下面这个例子来自官方的文档。

在这个例子中,使用 useState() ,让这个组件 “hook” 了一个 React 的 state:count 。count 的初始值就是 useState() 的参数,即0. 如果要改变 count 的值,就使用 setCount 这个函数。(之前是使用 this.setState() )。

可以这么理解,useState() 函数就关联了一个 React 的 state,调用 useState() 会给你 state 的应用,以及更新它的方法。(Hooks 都使用 “use” 开头,为什么不使用 “create” 呢?因为它只是和一个 state “关联”起来了,只是告诉 React “使用”这个 state。只有在组件第一次 render 的时候才会创建这个 state,在后续的更新中并不会创建了。)

另一个 React 提供的 hook 是 useEffect() 。如它的名字,这个 hook 不是和 state 有关的,而是产生 “effect” 的。类似于 React Class 中的 componentDidMount() 或者 componentWillUpdate() 中。

比如下面这段代码:

组件第一次更新的时候会调用 useEffect() 收到的函数,并且以后这个组件每次 render 的时候也会调用:更新页面的标题。

可以看到这里面没有和 React 组件生命周期相关的函数名字。这意味着我们可以根据业务逻辑来组织代码,可以将 effect state 等相关的逻辑放在一起,而不是把多个不相关的业务逻辑,都放到 componentDidMount() 里面去。

之前的这种按照组件生命周期来组织代码的方式,很容易出 Bug。比如,下面是一段正确的代码。这个组件订阅了朋友的在线状态,当组件 Unmount 的时候,会需要取消对这个朋友的状态订阅:

但其实 componentWillUnmount 这个函数非常容易忘记(尤其是在逻辑越来越多的情况下),造成内存泄漏。

如果用 useEffect() 来实现的话,就比较清晰了,因为这个函数接受的参数是 Effect 的函数,Effect 函数的返回值可以是一个 clean up 的函数。(好像比较绕,这个设计确实有些奇怪,为什么不将 clean up 的函数作为 useEffect 的第二个参数呢?)

useEffect() 来写的话,就是下面这种形式:

把更新的函数传给 useEffect() ,然后在自己的函数里面返回一个 callback 用来 cleanup. 因为这些逻辑都是在一起的,所以更加不容易忘记。

另外,useEffect() 会在组件 Mount 以及每次更新的时候都运行,相当于 componentDidMount() 和 componentWillMount() 两个函数合起来了。

Hooks 的原理

一个函数中可以使用 Hooks 多次,用来关联不同的 state,比如下面这段代码:

但是在调用 useState() 的时候并没有告诉 React name 是和哪一个 State 来关联,React 是怎么知道的呢?

答案是调用顺序,你按照这个顺序使用 Hooks,React 就按照这个顺序给你赋予这些 state 的值,“绑定”的过程类似下面这样:

所以顺序至关重要,知道这一点,就可以避免一些错误的使用方法。比如,一个原则是,只在 Component 的最顶层使用 Hooks,假如你没有函数的最顶层使用 Hooks,而是在嵌套结构(比如循环或者 if block 中)使用,那么绑定的时候就会出问题。

比如像下面这样:

那在 React 实际绑定组件的内部状态的时候,就会乱掉:

另一个原则是,只在 React Function Components 里面使用 Hooks。如果只在 Function Component 里面调用 Hooks 的话,你看到一个 Component 就会知道里面的 State 的变化,但是如果状态还在 Component 外面被控制,那么就很难管理了。并且 React Hooks 应该也不会在 Component 之外去帮你管理这些状态。

定义自己的 Hooks

没有 Hooks 以前,如果要抽象一部分涉及 state 的代码出来复用的话,只能再写一个 Class Component,现在可以用 Hooks 了。我们可以定义一个自己的 Hook。

比如,重用一段 Friend 订阅上下线的逻辑:

Hooks 里面会调用其他的 Hook,当然,也只能在最顶层来调用

我们自己定义的 Hooks 不像是 React 内置的那些一样参数都是固定的,Hooks 本质上就是调用了其他的 Hooks 的函数,所以我们可以自定义自己的参数和返回值:

  • 订阅一个朋友的状态:所以接收的参数是 FriendId
  • 提供的是朋友的状态,所以返回一个 onlineStatus

在 React 的视角,即使你 call 的是你自己定义的 Hooks,但是最终里面,还是调用的 React 定义的 Hooks。所以最终,你都只调用了 React 提供的 Hooks。就像我们所有的程序调用的函数最终只调用到了系统提供给我们的函数一样。

参考资料:

  1. https://reactjs.org/docs/hooks-intro.html

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注