自 React16.8 以来才真正被推而广之
它是 React 团队在真刀真枪的 React 组件开发实践中逐渐认知到的一个改进点,背后涉及对类组件函数组件两种组件形式的思考和侧重

何谓类组件:包裹在面向对象思想下的“重装战舰”

基于 ES6 Class 的写法,通过继承 React.Component 得来的 React 组件

  • 面向对象编程思想的一种表征
    • 封装:将一类属性和方法,“聚拢”到一个 Class 里去
    • 继承:新的 Class 可以通过继承现有的 Class 实现对某一类属性和方法的复用

“多”就是“好”吗?
把一个人塞进重装战舰里,他就一定能损人这台战舰吗?
React 类组件提供了多少东西,就需要学多少东西。“大而全”的背后,是不可忽视的学习成本

  • 对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势必然速写为高昂的理解成本
  • 开发者编写的逻辑在封装后是和组件粘在一起的,就使得类组件内部的逻辑难以实现拆分和复用

类组件固然强大,但绝非万能

何谓函数组件:呼应 React 设计思想的“轻巧快艇”

以函数的形态存在的 React 组件,早期没有 Hooks 的加持,其内部无法定义和维护 state,因此有一个别名叫无状态组件

函数组件会捕获 render 内部的状态,这是两类组件最大的不同

  • 类组件和函数组件之间,纵有千差万别,但最不能忽视的是心智模式层面的差异
  • 是面向对象与函数式编程的差异

函数组件更加契合 React 的设计理念
image.png
React 组件本身的定位就是函数,一个吃进数据、吐出 UI 的函数

  • 把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去

    函数组件与类组件的对比:无关“优劣”,只谈“不同”

  • 类组件需要继承 class,函数组件不需要

  • 类组件可以访问生命周期方法,函数组件不能
  • 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以
  • 类组件中可以定义并给拉 state(状态),而函数组件不可以

在 React-Hooks 出现之前,类组件的能力边界明显强于函数组件

函数组件会捕获 render 内部的状态,这是两类组件最大的不同

image.png

  1. class ProfilePage extends React.Component {
  2. showMessage = () => {
  3. alert('Followed' + this.props.user);
  4. }
  5. handleClick = () => {
  6. setTimeout(this.showMessage, 3000);
  7. }
  8. render() {
  9. return <button onClick={this.handleClick}>Follow</button>;
  10. }
  11. }

在点击 follow 后 3 秒内把用户切换为 Sophie,之后会见到显示 Sophie。

虽然 props 本身是不可变的,但 this 却是可变的,this 上的数据是可以被修改的
函数组件真正地把数据和渲染绑定到一起

通过 setTimeout 将预期中的渲染推迟了 3s,打破了 this.props 和渲染动作之间的这种时机上的凑聚

  1. function ProfilePage(props) {
  2. const showMessage = () => {
  3. alert('Followed' + props.user);
  4. }
  5. const handleClick = () => {
  6. setTimeout(showMessage, 3000);
  7. }
  8. return <button onClick={handleClick}>Follow</button>;
  9. }

在任何时机下读取到的 props 都是最初捕获到的,当父组件重新传入 props 来尝试重新渲染 ProfilePage 时,本质是基于新的 props 为入参发起的一次全新的调用。不会影响上一次调用对上一个 props 的捕获。这样确保渲染结果是符合预期。

函数组件是一个更加匹配其设计理念、也更利于逻辑拆分与重用的组件表达形式

Hooks 的本质

一套能够使函数组件更强大、更灵活的“钩子”
函数组件比起类组件“少”了很多东西,给函数组件的使用带来非常多的局限性

如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱,允许自由选择和使用需要的能力

useState 引入状态

早期函数组件相比类组件,其一大劣势是缺乏定义和维护 state 的能力,useState 正是这样一个能够为函数组件引入状态的 API

函数组件真的很轻

  • 过往为使用 state 不得不编写类组件,有了 useState 就可以在函数组件引用 state,而代码量几乎只为类组件的一半。
  • 同样逻辑的函数组件相比类组件而言,复杂度要低得多得多

调用 React.useState 的时候,实际上是给这个组件关联了一个状态

useEffect 允许函数组件执行副作用操作

useEffect 则在一定程度上弥补了生命周期的缺席
useEffect 能够为函数组件引入副作用,过去我们习惯放在 componentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期里来做的事,现在可以放在 useEffect 里来做

useEffect 和生命周期函数之间的“替换”关系

有时候必须学会忘记旧的知识,才能够更好地拥抱新的知识
当真正抛却类组件带来的刻板印象、拥抱函数式编程之后,会更加认同“useEffect 是用于为函数组件引入副作用的钩子”这个定义

  • 每一次渲染后都执行的副作用:传入回调函数,不传依赖数组
    image.png
  • 仅在挂载阶段执行一次的副作用:传入回调函数,且这个函数的返回值不是一个函数,同时传入一个空数组
    image.png
  • 仅在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组
    image.png
    • useEffect 回调中返回的函数被称为“清除函数”,这个规律不会受第二个参数或者其它因素的影响,只要在 useEffect 回调中返回一个函数,它就会被作为清除函数来处理
  • 每一次渲染都触发,且制裁阶段也会被触发的副作用:传入回调函数,且这个函数的返回值是一个函数,同时不传第二个参数
    image.png
  • 根据一定的依赖条件来触发的副作用:传入回调函数,同时传入一个非空的数组
    image.png

    Hooks 是如何帮助我们升级工作模式

    函数组件相比类组件来说,有着不少能够利好 React 组件开发的特性,而 React-Hooks 的出现正是为了强化函数组件的能力

    面试题:为什么需要 React-Hooks?

  1. 告别难以理解的 Class
    • this
    • 生命周期
    • Hooks 能够帮助实现业务逻辑的聚合,避免复杂的组件和冗余的代码
  2. 解决业务逻辑难以拆分的问题
    • 复用状态逻辑靠的是 HOC 和 Render Props 这些组件设计模式,但实现逻辑复用同时,也破坏组件的结构,出现“嵌套地狱”现象
    • Hooks 可以视作为 React 为解决状态逻辑复用这个问题所提供的一个原生途径
  3. 使状态逻辑利用变得简单可行
  4. 函数组件从设计思想上来看更加契合 React 的理念

Hooks 并非万能,Hooks 的局限性
Hoos 暂还不能完全地函数组件补齐类组件的能力

  • getSnapshotBeforeUpdate、compoentDidCatch 这些生命周期

“轻量”几乎是函数组件的基因,这可能会使它不能够很好地消化“复杂”
Hooks 在使用层面有着严格的规则约束

React-Hooks 的使用原则

  • 只在 React 函数中调用 Hook
  • 不要在循环、条件或嵌套函数中调用 Hook

    • 要确保 Hooks 在每次渲染时都保持同样的执行顺序

      Hooks 的正常动作在底层依赖于顺序链表

      以 useState 为例,分析 React-Hooks 的调用链路
  • mountState 首次渲染,构建链表并渲染

image.png

  • updateState 依次遍历链表并渲染

image.png

hooks 的渲染是通过“依次遍历”来宝位每个 hooks 内容的。如果前后两次讲到的链表在顺序上出现差异,那么渲染的结果自然是不可控。

Hooks 的本质是链表