等一下...React.useState 是如何工作的?

2025-06-11

等一下...React.useState 是如何工作的?

React Hooks 已经发布了一段时间了,它真的很棒!我已经在生产代码中使用过它们了,它让一切看起来都更美观了。随着我不断使用 Hooks,我开始好奇它究竟是如何运作的。

显然我不是唯一一个,因为波士顿有一个关于这个话题的 React 聚会。非常感谢 Ryan Florence 和 Michael Jackson(不是 Moonwalking 的传奇人物)就这个主题发表了如此精彩的演讲。继续观看,你将学到更多关于它的知识useEffect以及它的工作原理!

它是如何工作的?

您创建一个功能组件并向其抛出一些可跟踪状态的 React hook,还可以更新它,然后它就可以工作了。

我们中的许多人以前都见过这个例子的一些变体:

useState



import React from "react";

const App = () => {
  const [count, setCount] = React.useState(1);

  return (
    <div className="App">
      <h1>The infamous counter example</h1>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span style={{ margin: "0 16px" }}>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
};

export default App;


Enter fullscreen mode Exit fullscreen mode

👏 👏 👏 有效!
计数器图1

好的,很棒,但它是如何实现这种魔力的呢?看看这一React.useState行代码。它读起来非常简单,我从未质疑过它。我有一个析构数组,它提取count值,然后调用某个函数setCount,它会用我传入的默认值初始化 count useState。当我React.useState向图片中添加另一个时会发生什么?

useState,哈哈哈

有谁是德古拉伯爵吗?



const App = () => {
  const [count, setCount] = React.useState(1);
  const [message, setMessage] = React.useState("");

  const adder = () => {
    if (count < 10) {
      setCount(count + 1);
      setMessage(null);
    } else {
      setMessage("You can't go higher than 10");
    }
  }

  const subtracter = () => {
    if (count > 1) {
      setCount(count - 1);
      setMessage(null);
    } else {
      setMessage("You can't go lower than 1, you crazy");
    }
  }

  return (
    <div className="App">
      <h1>The infamous counter example</h1>
      <button onClick={subtracter}>-</button>
      <span style={{ margin: "0 16px" }}>{count}</span>
      <button onClick={adder}>+</button>
      <p>{message}</p>
    </div>
  );
};


Enter fullscreen mode Exit fullscreen mode

现在,每当用户试图超出 1 - 10 的范围时,我们都会显示一条消息
计数器图2

在我们的组件中,我们有两个解构数组,它们使用同一个React.useState钩子,但默认值不同。哇,现在我们开始探索它的奥妙了。

好吧,让我们删除它ReactReact.useState我们应该得到一个引用错误,说“useState 未定义”
由于 React 消失导致引用错误

让我们实现我们自己的useState功能。

逆向工程useState函数

函数useState具有一个值和一个设置该值的函数

像这样:



const useState = (value) => {

  const state = [value, setValue]
  return state
}


Enter fullscreen mode Exit fullscreen mode

我们仍然会得到referenceErrors,因为我们还没有定义setValue。我们知道setValue是一个函数,因为我们在useState
count中是如何使用它的useStateconst [count, setCount] = React.useState(1);

致电setCountsetCount(count + 1);

创建该setValue函数不会再导致错误,但-+按钮不起作用。



const useState = (value) => {
  const setValue = () => {
    // What do we do in here?
  }

  const state = [value, setValue]
  return state
}


Enter fullscreen mode Exit fullscreen mode

如果我们尝试更改默认值,useState它会更新count👍🏽。至少有些功能还在运行😂。
计数器图3

继续弄清楚到底setValue是干什么的。

我们看到setCount它正在执行某种类型的值重新赋值,然后导致 React 重新渲染。这就是我们接下来要做的。



const setValue = () => {
  // What do we do in here?
  // Do some assigning
  // Rerender React
}


Enter fullscreen mode Exit fullscreen mode

我们将向我们的函数传递一个新的值参数setValue



const setValue = (newValue) => {
  // What do we do in here?
  // Do some assigning
  // Rerender React
}


Enter fullscreen mode Exit fullscreen mode

newValue但是我们在函数内部做什么呢setValue



const setValue = (newValue) => {
  // Do some assigning
  value = newValue // Does this work?
  // Rerender React
}


Enter fullscreen mode Exit fullscreen mode

value = newValue有道理,但这并没有更新计数器的值。为什么?当我console.log在里面setValue和外面的时候setValue,我们看到的就是这样的。

单击“+”时计数器不更新

刷新页面后,计数初始化为 1,消息初始化为 null,一切正常。点击按钮+后,计数值增加到 2,但屏幕上并没有更新计数。🤔 也许我需要手动重新渲染浏览器来更新计数?

实现一种手动重新渲染浏览器的糟糕方式



const useState = (value) => {
  const setValue = (newValue) => {
    value = newValue;
    manualRerender();
  };
  const state = [value, setValue];
  return state;
};
.
.
.
const manualRerender = () => {
  const rootElement = document.getElementById("root");
  ReactDOM.render(<App />, rootElement);
};

manualRerender();


Enter fullscreen mode Exit fullscreen mode

浏览器中的计数仍然没有更新。怎么回事?

我卡在这上面一段时间了,现在知道原因了。console.log创建完成后就说明吧。



const state = [value, setValue];
console.log(state)


Enter fullscreen mode Exit fullscreen mode

我们的调用useState引发了第一次渲染,我们得到:
[1, setValue()]

在我们第二次调用时,useState我们渲染:
[null, setValue()]

导致:
第一次渲染

为了更好地将其形象化,让我们添加一个渲染跟踪器来计算渲染屏幕的次数。



let render = -1

const useState = (value) => {
  const setValue = (newValue) => {
    value = newValue;
    manualRerender();
  };
  const state = [value, setValue];
  console.log(++render)
  console.log(state)
  return state;
};


Enter fullscreen mode Exit fullscreen mode

调用 useState 时跟踪渲染

我们的函数如何setValue知道要更新哪个值?它不知道,因此我们需要一种方法来跟踪它。您可以使用数组或对象来实现这一点。我选择了红色药丸类型的对象。

在函数之外useState,我们将创建一个名为states



const states = {}


Enter fullscreen mode Exit fullscreen mode

在函数内useState初始化states对象。我们使用括号来分配键/值对。

states[++render] = state

我还将创建另一个变量,用于id存储渲染值,以便我们可以取出++render括号内的。

你应该得到类似这样的结果:



let render = -1;
const states = {};

const useState = (value) => {
  const id = ++render;

  const setValue = (newValue) => {
    value = newValue;
    manualRerender();
  };
  const state = [value, setValue];
  states[id] = state;
  console.log(states);
  return state;
};


Enter fullscreen mode Exit fullscreen mode

我们的物体是什么states样子的?



states = {
  0: [1, setValue],
  1: [null, setValue]
}


Enter fullscreen mode Exit fullscreen mode

所以现在我们点击加减按钮时……又什么也没有了。哦,对了,因为value = newValue仍然没有任何反应。

但是确实发生了一些事情。如果你查看控制台,你会发现每次我们点击其中一个按钮时,它都会不断地将相同的数组添加到我们的states对象中,但count不会递增,并且消息仍然为空。

所以setValue需要去寻找value,然后将其分配newValuevalue



const setValue = (newValue) => {
  states[id][0] = newValue;
  manualRerender();
};


Enter fullscreen mode Exit fullscreen mode

然后我们要确保我们只更新键:0 和 1,因为它们将是我们的两个useState位置。

因此,转到该manualRerender函数并添加对它的调用render并将其重新分配给 -1



const manualRerender = () => {
  render = -1;
  const rootElement = document.getElementById("root");
  ReactDOM.render(<App />, rootElement);
};


Enter fullscreen mode Exit fullscreen mode

我们这样做是因为每次我们调用 setValue 时它都会调用manualRerender函数设置render回 -1

最后,我们将添加一个检查来判断对象是否存在。如果存在,我们就返回该对象。



if (states[id]) return states[id];


Enter fullscreen mode Exit fullscreen mode

现在我们又开始工作了!
柜台工作

呼。这可真是费劲,而且这只是一个非常简单的实现方法useState。幕后还有很多事情要做,但至少我们对它的工作原理有了大致的了解,并且稍微揭开了它的神秘面纱。

查看所有代码并尝试在脑海中构建一个模型来了解它的工作原理。

希望这有帮助😊

鏂囩珷鏉ユ簮锛�https://dev.to/rembrandtreyes/wait-how-does-react-usestate-work-44np
PREV
Web Worker 的工作原理(实际示例)
NEXT
TDD 与 BDD - 详细指南 TDD 与 BDD 结论