React Hook 学习笔记

React Hook 学习笔记

有了Redux从此不用Context,有了Hook从此不用HOC......

最近看完了《React.js 小书》,了解了使用高阶组件把不同组件从context上下文中取出来的方式,就构成了redux的核心内容。很多组件只需要用高阶组件包裹一下,就具备了使用某些状态的特性。

不过这样的话,需要把组件重新整理,《React.js》小书的最后,将所有组件都拆分成了无状态组件和为无状态组件附加状态的高阶组件。这样实际上就是把所有组件都重新整理,而且会使代码难以理解。

在我刚看完《React.js》的时候,问前端高手说我现在高阶组件终于可以运用自如了,大佬简简单单的说,去看一下Hook,比用HOC要简单方便。于是咱只能老老实实把React Hook加入到小本子上的To-Learn-List里,这里就来记录一下React Hook的自学心得。

函数式组件 VS 类组件

在看官网的教程时候,官网有句话是:Hook 使你在非 class 的情况下可以使用更多的 React 特性。这让我想起一直以来的函数式组件和类组件的使用方式。高程4中说JS的类实际上就是函数的语法糖,所以函数式组件和类组件本质上没有什么区别。只不过函数式组件不太好写state,只方便使用props,由于函数本身的特性,也易于写出无状态组件。类则一般都使用state,但是本质上二者没有区别。

State Hook - 在函数中引入state

根据前边那句粗体字,State Hook就是在非类组件中可以使用state特性,也就是钩入了Reactstate特性
官网的例子很简单但也很说明问题:

import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import './index.css'

function Test() {

    const [count, setCount] = useState(0);

    return (
        <div>
            <p> You clicked {count} times</p>
            <button onClick={() => {
                setCount(count + 1);
            }}>Click Me
            </button>
        </div>
    );
}

ReactDOM.render(
    <Test/>,
    document.getElementById('root')
);

这其中的useState函数就是一个Hook,使用一个初始值调用这个函数,函数会返回两个值,第一个是初始值对应的变量,第二个是修改这个变量的函数。在例子里,返回的count变量的值是0,而更改count变量的函数被命名为setCount

有了变量和修改变量的方式,就会发现这很像state与对应的setState方法,从表面上看确实如此,所以后边的代码中直接渲染了count变量,修改count变量的时候使用事件给count+1,不过这里实际上并没有一个state对象,也不会像state对象一样,把某个count变量“合并”到一个state对象中。初始的state参数只会在第一次渲染的时候被使用到。

例子里使用了一个0作为初始的参数,当然也可以是其他类型,或者就是一个对象,这个时候就能看出来区别:

function Test() {

    const [count, setCount] = useState({
        name: "cony",
        age: 6
    });

    return (
        <div>
            <p> You see {count.name}.</p>
            <p> {count.name} is {count.age} years old</p>
            <button onClick={() => {
                setCount({
                    name: "Jenny"
                });
            }}>Click Me
            </button>
        </div>
    );
}

点击按钮之后,会发现原来渲染的6消失了,这说明不是合并了到了count变量指向的对象中,而是直接取代了原来的count变量。
所以我们可以把这种通过useState生成的变量叫做state变量,操作这些变量就像是操作原本的this.state中某一个具体的属性一样。

使用多个state变量

那么很显然,只有一个state变量未必能满足需求,所以可以反复调用useState函数来生成多个state变量,这样就可以独立使用。

这里还有一个特点,官网原话:React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的。也就是会按照生成变量的顺序进行渲染。

这里要注意的是,生成变量之后,变量就彼此独立了,这个和之前说的useState函数的特性相同。

我自己写了段小代码来试验一下:

function Test() {

    const [first, setFirst] = useState(10);
    const [second, setSecond] = useState(first * 3);
    const [third, setThird] = useState(second + 10);


    return (
        <div>
            <p> At start {first} * 3 = {second}.</p>
            <p>Then {second} + 10 = {third}</p>

            <button onClick={() => {
                setFirst(first + 1);
            }}>Add 1 to first
            </button>
        </div>
    );
}

这里在生成三个变量的时候,在运行第二行程序时候,已经有了first变量,依次类推,所以显示的结果是正确的,但是通过按钮给first+1的时候,secondthird的值不会变动,这种实际上也和操作this.state的独立变量是一样的。

Effect Hook - 在函数中引入副作用

除了this.state之外,在没有Hook之前的React中,还有一个特性非常有利于使用类组件,那就是生命周期函数。此外还可能有一些其他的方法,用于进行某些运算,如果把函数式组件当成纯函数来说的话,那么副作用很显然就是除了渲染之外的其他作用,比如更改页面其他的属性,发送HTTP请求等等,对于一些组件来说这是必须的。

于是现在有了一个新的东西,可以在其中执行所有的副作用:

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

这里的副作用就是更新浏览器选项卡的标题,官方说法:如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。我自己理解这句话的意思是:

  1. 副作用函数是在React更新DOM之后才会执行。这样就无须在类中区分挂载和更新来编写冗余的代码。
  2. 对于componentWillUnmount中的操作,如果你的effect参数函数又返回一个函数,React 将会在执行清除操作时调用它,这样useEffect就同时也具备了componentWillUnmount的功能。

Context Hook 在函数中引入context

这个用于在函数中获取context:

const value = useContext(MyContext);

useContext的函数的参数是固定的,就是context对象本身。之后Provider组件中的context刷新的时候,这里的value也会一同刷新。

本来使用context API有特定的要求,类里必须存在两个东西:

  1. 类里需要定义一个静态变量:static childContextTypes
  2. getChildContext()函数,用于设置context内容

如此设置之后,在其他地方就可以使用this.context.attrName来取用context中的内容,或者在外层套一个Context.Provider组件。

有了context钩子之后,就可以省略上述的操作了,只需要在函数内之前定义一个context对象。这里放一个通向官方例子的链接,待以后需要用到context再回来看。

HOOK规则

这里把官方的规则复制过来:

  1. 只在最顶层使用 Hook
    不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确。(如果你对此感到好奇,我们在下面会有更深入的解释。)

  2. 只在 React 函数中调用 Hook
    不要在普通的 JavaScript 函数中调用 Hook。你可以:
    ✅ 在 React 的函数组件中调用 Hook
    ✅ 在自定义 Hook 中调用其他 Hook
    遵循此规则,确保组件的状态逻辑在代码中清晰可见。

好,现在有了若干新武器,Redux,Hook,剩下就是平时多使用一下。待到哪天需要使用自定义Hook再回来继续补完。总的来说,我觉得Hook的出现主要是解决一些为了在组件间共享状态,不想使用多层传递,又嫌使用Redux等额外工具太重型,于是就简单一些给函数组件添加上各种钩子来实现只有类组件才能实现的功能,目标还是为了让状态管理更加简便。

LICENSED UNDER CC BY-NC-SA 4.0
Comment