学习 React Hook useState 快照机制

发布时间 2023-04-03 00:54:38作者: Himmelbleu

前言

本人不太了解 React 之前类组件中的 setState 函数,我是直接从 React Hook 入门的 React。网上查阅了其他文章以及视频,对于 setState 类组件函数,状态更新是异步的而不是同步的。

在最新文档(React Hook)中,useState 适用于函数组件,而这一个 API 有了一个新的概念(机制)——快照。具体请阅读:state 如同一张快照

通篇阅读文档之后,我认为打乱文档原本的顺序解读快照机制会更好。

解读快照机制

在函数组件中,useState 中的 state 是一个快照更新,既不是异步更新也不是同步更新。所谓快照就是 React 在更新函数组件内的 state 时会把那时作为一个节点,函数组件内的所有 state 都是基于那时的节点的。

React 文档的案例中,最能说明快照机制的是下面的例子:

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <h1>{number}</h1>
    <button onClick={() => {
      setNumber(number + 5);
      setTimeout(() => {
        alert(number);
      }, 3000);
    }}>+5</button>
  )
}

点击之后,在异步任务之前已经更新了 number,那么 alert(number) 打印出来的应该是 5,因为异步任务已经推迟了 3s 才运行:

经过 3s 之久 number 还是 0

很明显,不符合我们预想的那样。这已经充分说明,在函数组件中的 state 并不是类组件那样的异步更新,而是快照更新:一切基于那时的时间节点

仔细发现,UI 及时地更新了 state。这其中的内在逻辑,需要研究源码才可知晓。受水平限制,无法研究 React 源码进行解读快照机制的内核。

更形象的比喻

快照可以比作是一个相机,拍下那个时候的照片,所有的状态都停留在了照片上。因此,函数组件内的所有 state 也就停留在了 update 那个时候的节点上。

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  function update(n) {
    setNumber(n);
    alert(number);
  }

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        update(1);
        update(1);
        update(1);
      }}>+1</button>
    </>
  )
}

因为快照机制,此时的 number 已经停留在 update 之前的状态了(快照已经生成),即便是有三个 update 在执行,它们都是基于 number 为 0 的状态 +1 的。因此,UI 上只会出现 1。

快照更新,初次快照的 number 为 0

这一个例子是文档:渲染会及时生成一张快照

更新批处理

如上面的例子中,update 函数执行了三次,它们是批量更新的,不管有多少个,UI 呈现出来的结果是最后一次 update 执行的结果。

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  function update(n) {
    setNumber(n);
    alert(number);
  }

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        update(1);
        update(2);
        update(3);
      }}>+1</button>
    </>
  )
}

UI 呈现的是最后一次 update 执行的结果

状态更新函数

实现基于前一个函数执行的结果,打破快照机制,改用状态更新函数。

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>+3</button>
    </>
  )
}

setNumber 传递一个函数,箭头函数参数 n 是上一次执行的结果,而不是快照机制保留下来的固定的 number。

基于上一个 setNumber 的结果

综上所述

函数组件的 state 快照机制非常神奇,不是同步更新、也不是类组件的异步更新,而是一种快照更新。