【精通react】(二)理解useState的实现原理---实现一个简易版的useState

172 阅读3分钟

在 React 中,useState 是函数式组件中管理状态的核心 Hook。它允许组件在不使用类组件的情况下,拥有可变的状态,并在状态变化时重新渲染 UI。以下是 useState 的简化实现和详细说明。


✅ useState 的核心机制

useState 的核心思想是通过 闭包 和 全局状态数组 来保存组件的状态,并在每次调用 useState 时返回当前状态和更新状态的函数。React 通过 调用顺序 来确保状态的正确性(即同一个组件中 useState 必须按相同顺序调用)。


🧩 简化版 useState 的实现(单组件场景)

以下是一个简化版的 useState 实现,适用于单个组件的状态管理:

// 状态数组和游标
let state = [];
let cursor = 0;

function useState(initialValue) {
  const currentIndex = cursor;

  // 如果当前索引超出数组长度,初始化新状态
  if (state.length <= currentIndex) {
    state.push(initialValue);
  }

  // 获取当前状态
  const currentValue = state[currentIndex];

  // 更新状态的函数
  const setState = (newValue) => {
    state[currentIndex] = newValue;
    cursor = 0; // 重置游标,确保下次渲染从头开始读取
    render();    // 触发重新渲染
  };

  // 游标递增
  cursor++;

  return [currentValue, setState];
}

// 模拟组件函数
function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("Hello");

  return (
    <div>
      <p>Count: {count}</p>
      <p>Text: {text}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setText("World")}>Change Text</button>
    </div>
  );
}

// 模拟渲染函数
function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
}

📌 实现的关键点

1. 状态数组与游标(cursor)

  • state:保存组件的状态值。
  • cursor:记录当前调用 useState 的位置。每次调用 useState 时,cursor 递增,确保状态按顺序读取。

2. 状态初始化

  • 当组件首次渲染时,state 数组为空,useState 会将初始值推入数组中。
  • 之后的渲染中,useState 会从 state 中读取对应位置的状态值。

3. 状态更新与重新渲染

  • 调用 setState 时,更新对应索引处的状态值。
  • 重置 cursor 为 0,确保下次渲染时能正确读取状态。
  • 调用 render() 重新执行组件函数,生成新的虚拟 DOM 并更新真实 DOM。

4. 组件调用顺序一致性

  • 组件中 useState 的调用顺序必须保持一致,否则会导致状态错位(例如,第一次渲染调用两次 useState,第二次渲染只调用一次,会导致状态读取错误)。
  • React 通过严格的调用顺序保证状态的一致性。

示例:运行简化版 useState


<!DOCTYPE html>
<html>
<head>
  <title>useState Demo</title>
  <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
  <div id="root"></div>

  <script>
    // 状态数组和游标
    let state = [];
    let cursor = 0;

    function useState(initialValue) {
      const currentIndex = cursor;

      if (state.length <= currentIndex) {
        state.push(initialValue);
      }

      const currentValue = state[currentIndex];

      const setState = (newValue) => {
        state[currentIndex] = newValue;
        cursor = 0;
        render();
      };

      cursor++;

      return [currentValue, setState];
    }

    function App() {
      const [count, setCount] = useState(0);
      const [text, setText] = useState("Hello");

      return React.createElement(
        "div",
        null,
        React.createElement("p", null, "Count: " + count),
        React.createElement("p", null, "Text: " + text),
        React.createElement("button", { onClick: () => setCount(count + 1) }, "Increment"),
        React.createElement("button", { onClick: () => setText("World") }, "Change Text")
      );
    }

    function render() {
      ReactDOM.render(React.createElement(App), document.getElementById("root"));
    }

    // 初始渲染
    render();
  </script>
</body>
</html>

🧠 深入理解

  • 为什么 useState 必须按顺序调用?
    因为 React 通过调用顺序来定位状态在数组中的位置,顺序不一致会导致状态错位。
  • 为什么不能在条件语句中使用 useState
    条件语句可能改变 useState 的调用顺序,导致状态错位。