useState 的简单实现

83 阅读2分钟

State Hook 是什么

useState 是允许你在 React 函数组件中添加 state 的 Hook。它是一种新方法,与 class 里面的this.state提供的功能完全相同。

useState 定义了一个"state 变量",该变量即便在函数退出后,也会被 React 保留。

useState()方法里面唯一的参数就是初始 state,我们可以按需使用数字或字符串对其进行赋值,而不一定是对象(与 class 的不同)。 如果想要在 state 中存储两个不同的变量,那就调用useState()两次。

useState()返回值为:当前 state 以及更新 state 的函数。

简单实现 useState

1. 当只使用一次 useState 的实现

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

const rootElement = document.getElementById('root');

let _state // 声明在函数外面,这样不会被 myUseState 重置

const myUseState = initialValue => {
  _state = _state === undefined ? initialValue : _state
  const setState = newValue => {
    _state = newValue;
    render();
  };
  return [_state, setState];
};

const render = () => {
  ReactDOM.render(<App/>, rootElement); //简单粗暴的直接将整个组件 render
}

function App() {
  const [n, setN] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App/>, rootElement);

image.png

这样就简单实现了,点击 +1 按钮,显示的数字也 +1 的功能。

但如果一个组件,用了两个useState()的话,这样做就不行了,后面的 useState 会把前一项给覆盖掉。所以我们把_state做成数组。

2. 改进:使用数组存储_state

let _state = [];   // 数组
let index = 0;
const myUseState = initialValue => {
  const currentIndex = index;
  _state[currentIndex] =
    _state[currentIndex] === undefined ? initialValue : _state[currentIndex];
  const setState = newValue => {
    _state[currentIndex] = newValue;
    render();
  };
  index += 1;
  return [_state[currentIndex], setState];
};

const render = () => {
  index = 0;   // 重置 index
  ReactDOM.render(<App/>, rootElement);
};

function App() {
  const [n, setN] = myUseState(0);
  const [m, setM] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>n+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={() => setM(m + 1)}>m+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App/>, rootElement);

image.png

现在,也可以成功使用多个useState啦。

3. _state数组方案缺点

因为是数组,所以useState非常依赖调用顺序。每次渲染时的顺序必须保证和第一次渲染时顺序完全一致。

所以 React 中 Hook 只能在函数最外层调用,不能在循环、条件判断或者子函数中调用。

4. 给每个组件创建一个 _stateindex

可以将每个组件创建的属于自己的_stateindex,放在组件对应的虚拟节点对象上。

React 中,React虚拟节点是 FiberNode,_state 的真实名称为 memoizedState,index 的实现则是使用了链表。

总结

  • 每个函数组件对应一个 React 节点。
  • 每个节点保存着 state 和 index
  • useState 会读取 state[index]
  • index 由 useState 出现的顺序决定
  • setState 会修改 state,并触发更新