快速了解 React Hooks 原理

阿里云最近在作活动,低至2折,有兴趣能够看看:
https://promotion.aliyun.com/...

为了保证的可读性,本文采用意译而非直译。html

咱们大部分 React 类组件能够保存状态,而函数组件不能? 而且类组件具备生命周期,而函数组件却不能?前端

React 早期版本,类组件能够经过继承PureComponent来优化一些没必要要的渲染,相对于函数组件,React 官网没有提供对应的方法来缓存函数组件以减小一些没必要要的渲染,直接 16.6 出来的 React.memo函数。react

React 16.8 新出来的Hook可让React 函数组件具备状态,并提供相似 componentDidMountcomponentDidUpdate等生命周期方法。git

类被会替代吗?

Hooks不会替换类,它们只是一个你可使用的新工具。React 团队表示他们没有计划在React中弃用类,因此若是你想继续使用它们,能够继续用。github

我能体会那种总有新东西要学的感受有多痛苦,不会就感受我们老是落后同样。Hooks 能够看成一个很好的新特性来使用。固然没有必要用 Hook 来重构原来的代码, React团队也建议不要这样作。数组

Go Go

来看看Hooks的例子,我们先从最熟悉的开始:函数组件。缓存

如下 OneTimeButton 是函数组件,所作的事情就是当咱们点击的时候调用 sayHi 方法。并发

import React from 'react';
import { render } from 'react-dom';

function OneTimeButton(props) {
  return (
    <button onClick={props.onClick}>
        点我点我
    </button>
  )
}

function sayHi() {
  console.log('yo')
}

render(
  <OneTimeButton onClick={sayHi}/>,
  document.querySelector('#root')
)

咱们想让这个组件作的是,跟踪它是否被点击,若是被点击了,禁用按钮,就像一次性开关同样。dom

但它须要一个state,由于是一个函数,它不可能有状态(React 16.8以前),因此须要重构成类。函数

函数组件转换为类组件的过程当中大概有5个阶段:

*否定:也许它不须要是一个类,咱们能够把 state 放到其它地方。

  • 实现: 废话,必须把它变成一个class,不是吗?
  • 接受:好吧,我会改的。
  • 努力加班重写:首先 写 class Thing extends React.Component,而后 实现 render等等 。
  • 最后:添加state。
class OneTimeButton extends React.Component {
  state = {
    clicked: false
  }

  handleClick = () => {
    this.props.onClick();

    // Ok, no more clicking.
    this.setState({ clicked: true });
  }

  render() {
    return (
      <button
        onClick={this.handleClick}
        disabled={this.state.clicked}
      >
        You Can Only Click Me Once
      </button>
    );
  }
}

这是至关多的代码,组件的结构也发生了很大的变化, 咱们须要多个小的功能,就须要改写不少。

使用 Hook 轻松添加 State

接下来,使用新的 useState hook向普通函数组件添加状态:

import React, { useState } from 'react'

function OneTimeButton(props) {
  const [clicked, setClicked] = useState(false)
  
  function doClick() {
    props.onClick();
    setClicked(true)
  }

  return (
    <button
      onClick={clicked ? undefined : doClick}
      disabled={clicked}
    >
      点我点我
    </button>
  )
}

这段代码是如何工做的

这段代码的大部分看起来像咱们一分钟前写的普通函数组件,除了useState

useState是一个hook。 它的名字以“use”开头(这是Hooks的规则之一 - 它们的名字必须以“use”开头)。

useState hook 的参数是 state 的初始值,返回一个包含两个元素的数组:当前state和一个用于更改state 的函数。

类组件有一个大的state对象,一个函数this.setState一次改变整个state对象。

函数组件根本没有状态,但useState hook容许咱们在须要时添加很小的状态块。 所以,若是只须要一个布尔值,咱们就能够建立一些状态来保存它。

因为Hook以某种特殊方式建立这些状态,而且在函数组件内也没有像setState函数来更改状态,所以 Hook 须要一个函数来更新每一个状态。 因此 useState 返回是一对对应关系:一个值,一个更新该值函数。 固然,值能够是任何东西 - 任何JS类型 - 数字,布尔值,对象,数组等。

如今,你应该有不少疑问,如:

  • 当组件从新渲染时,每次都不会从新建立新的状态吗? React如何知道旧状态是什么?
  • 为何hook 名称必须以“use”开头? 这看起来很可疑。
  • 若是这是一个命名规则,那是否意味着我能够自定义 Hook。
  • 如何存储更复杂的状态,不少场景不仅仅只有一个状态值这么简单。

Hooks 的魔力

将有状态信息存储在看似无状态的函数组件中,这是一个奇怪的悖论。这是第一个关于钩子的问题,我们必须弄清楚它们是如何工做的。

原做者得的第一个猜想是某种编译器的在背后操众。搜索代码useWhatever并以某种方式用有状态逻辑替换它。

而后再据说了调用顺序规则(它们每次必须以相同的顺序调用),这让我更加困惑。这就是它的工做原理。

React第一次渲染函数组件时,它同时会建立一个对象与之共存,该对象是该组件实例的定制对象,而不是全局对象。只要组件存在于DOM中,这个组件的对象就会一直存在。

使用该对象,React能够跟踪属于组件的各类元数据位。

请记住,React组件甚至函数组件都从未进行过自渲染。它们不直接返回HTML。组件依赖于React在适当的时候调用它们,它们返回的对象结构React能够转换为DOM节点。

React有能力在调用每一个组件以前作一些设置,这就是它设置这个状态的时候。

其中作的一件事设置 Hooks 数组。 它开始是空的, 每次调用一个hook时,React 都会向该数组添加该 hook

为何顺序很重要

假设我们有如下这个组件:

function AudioPlayer() {
  const [volume, setVolume] = useState(80);
  const [position, setPosition] = useState(0);
  const [isPlaying, setPlaying] = useState(false);

  .....
}

由于它调用useState 3次,React 会在第一次渲染时将这三个 hook 放入 Hooks 数组中。

下次渲染时,一样的3hooks以相同的顺序被调用,因此React能够查看它的数组,并发现已经在位置0有一个useState hook ,因此React不会建立一个新状态,而是返回现有状态。

这就是React可以在多个函数调用中建立和维护状态的方式,即便变量自己每次都超出做用域。

多个useState 调用示例

让我们更详细地看看这是如何实现的,第一次渲染:

  1. React 建立组件时,它尚未调用函数。React 建立元数据对象和Hooks的空数组。假设这个对象有一个名为nextHook的属性,它被放到索引为0的位置上,运行的第一个hook将占用位置0
  2. React 调用你的组件(这意味着它知道存储hooks的元数据对象)。
  3. 调用useState,React建立一个新的状态,将它放在hooks数组的第0位,并返回[volume,setVolume]对,并将volume 设置为其初始值80,它还将nextHook索引递增1。
  4. 再次调用useState,React查看数组的第1位,看到它是空的,并建立一个新的状态。 而后它将nextHook索引递增为2,并返回[position,setPosition]
  5. 第三次调用useState。 React看到位置2为空,一样建立新状态,将nextHook递增到3,并返回[isPlaying,setPlaying]

如今,hooks 数组中有3个hook,渲染完成。 下一次渲染会发生什么?

  1. React须要从新渲染组件, 因为 React 以前已经看过这个组件,它已经有了元数据关联。
  2. ReactnextHook索引重置为0,并调用组件。
  3. 调用useState,React查看索引0处的hooks数组,并发现它已经在该槽中有一个hook。,因此无需从新建立一个,它将nextHook推动到索引1并返回[volume,setVolume],其中volume仍设置为80
  4. 再次调用useState。 此次,nextHook1,因此React检查数组的索引1。一样,hook 已经存在,因此它递增nextHook并返回[position,setPosition]
  5. 第三次调用useState,我想你知道如今发生了什么。

就是这样了,知道了原理,看起来也就不那么神奇了, 但它确实依赖于一些规则,因此才有使用 Hooks 规则。

Hooks 的规则

自定义 hooks 函数只须要遵照规则 3 :它们的名称必须以“use”为前缀。

例如,咱们能够从AudioPlayer组件中将3个状态提取到本身的自定义钩子中:

function AudioPlayer() {
  // Extract these 3 pieces of state:
  const [volume, setVolume] = useState(80);
  const [position, setPosition] = useState(0);
  const [isPlaying, setPlaying] = useState(false);

  // < beautiful audio player goes here >
}

所以,我们能够建立一个专门处理这些状态的新函数,并使用一些额外的方法返回一个对象,以便更容易启动和中止播放,例如:

function usePlayerState(lengthOfClip) {
  const [volume, setVolume] = useState(80);
  const [position, setPosition] = useState(0);
  const [isPlaying, setPlaying] = useState(false);

  const stop = () => {
    setPlaying(false);
    setPosition(0);
  }

  const start = () => {
    setPlaying(true);
  }

  return {
    volume,
    position,
    isPlaying,
    setVolume,
    setPosition,
    start,
    stop
  };
}

像这样提取状态的一个好处是能够将相关的逻辑和行为组合在一块儿。能够提取一组状态和相关事件处理程序以及其余更新逻辑,这不只能够清理组件代码,还可使这些逻辑和行为可重用。

另外,经过在自定义hooks中调用自定义hooks,能够将hooks组合在一块儿。hooks只是函数,固然,函数能够调用其余函数。

总结

Hooks 提供了一种新的方式来处理React中的问题,其中的思想是颇有意思且新奇的。

React团队整合了一组很棒的文档和一个常见问题解答,从是否须要重写全部的类组件到钩Hooks是否由于在渲染中建立函数而变慢? 以及二者之间的全部东西,因此必定要看看。

代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

交流

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,便可看到福利,你懂的。

clipboard.png

相关文章
相关标签/搜索