在 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的调用顺序,导致状态错位。