简单实现 React hook useState

739 阅读2分钟

什么是Hook?

React Hook 是 React 16.8 版本引入的一种新特性,它是一种用于函数组件中添加状态和生命周期的方式。让开发者在不使用类组件的情况下,实现类似类组件的state、生命周期功能,可以提供更好的代码复用、代码可读性。

官方描述

Hook规则

  1. 不要在普通的 JavaScript 函数中调用 Hook
  2. 保证每一次渲染中都按相同顺序调用(不要在循环,条件或嵌套函数中调用 Hook)

官方描述

Hook运行过程

  1. 在函数组件中调用Hook函数时(例useState、useEffect等),React会在内部(Fiber的memoizedState上)维护一个Hook链表
  2. 当组件渲染时,React会按照Hook链表的顺序依次执行每个Hook函数,并根据每个Hook对象来更新对应的状态或执行对应的副作用等
  3. 当组件重新渲染时,React会再次按照相同的顺序执行每个Hook函数,并将返回的值和上次保存的值进行比较,如果有变化,则触发相应的更新操作

初步形态

因为自己写的 useState 无法控制 React 的重新渲染,所以我通过重新运行 ReactDOM 的 render 来模拟 React 的重新渲染。

import React from "react";
import ReactDOM from "react-dom";

let memorizedValue;

const useState = (initValue) => {
  memorizedValue = memorizedValue || initValue;

  const setState = (newValue) => {
    memorizedValue = newValue;

    render();
  };

  return [memorizedValue, setState];
};

const App = () => {
  const [number, setNumber] = useState(0);

  return (
    <>
      <p>number: {number}</p>
      <button onClick={() => setNumber(number + 1)}>add</button>
    </>
  );
};

function render() {
  ReactDOM.render(<App />, document.querySelector("#root"));
}

render();

支持多个setState

上述简单 useState 虽然可以正常运行,但是不能设置多个 useState,所以我在上述代码基础上,添加了 states 和 index 去控制多个 useState 的情况(在 React Hook源码中是用链表控制)。

import React from "react";
import ReactDOM from "react-dom";

const states = [];

let index = 0;

const useState = (initValue) => {
  const currentIndex = index;

  states[currentIndex] = states[currentIndex] || initValue;

  const setState = (newValue) => {
    states[currentIndex] = newValue;

    render();
  };

  index++;

  return [states[currentIndex], setState];
};

const App = () => {
  const [number1, setNumber1] = useState(0);
  const [number2, setNumber2] = useState(0);

  return (
    <>
      <p>number1: {number1}</p>
      <p>number2: {number2}</p>
      <button onClick={() => setNumber1(number1 + 1)}>add</button>
      <button onClick={() => setNumber2(number2 + 1)}>add</button>
    </>
  );
};

function render() {
  ReactDOM.render(<App />, document.querySelector("#root"));
  index = 0;
}

render();

在上述代码,用了 status index 代替了 React Hook 链表结构,真实的链表结构大概这样:

const hook: Hook = {
  memoizedState: null, // 保存 hook 数据,例如 useState 就是保存 state
  baseState: null, // 已处理的 update 计算出来的 state
  baseQueue: null, // 未处理的 update
  queue: null, // 当前处理的 update
  next: null, // 指向下一个 hook
};

总结

通过上面的例子我们可以得出,在定义、渲染、重新渲染都和Hook的调用顺序有关,所以不能使用条件、循环去调用Hook,因为这些调用方式都有可能改变Hook的调用顺序,所以React才会定义 “保证每一次渲染中都按相同顺序调用” 这个规则。

参考资料

legacy.reactjs.org/docs/hooks-…

medium.com/@ryardley/r…

juejin.cn/post/696882…