useState执行机制

本文详细探讨了React Hooks中useState Hook的工作原理,通过一个具体的例子展示了当传递给useState的参数为函数和表达式时,其执行顺序和状态更新的差异。分析了setState的异步性质以及在更新过程中的执行流程,帮助读者深入理解React组件的状态管理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用 react hooks 很久了,对于 useState 的理解一直都是模糊不清,终于下定决心把它理清楚,看似简单的 useState 暗藏玄机,先来看一段代码

import { useState } from 'react';

console.log('函数外');
const HomePage = () => {
	console.log('函数内 顶部');
	const [num, setNumber] = useState(0);
	const [count, setCount] = useState(0);

	const handerNum = () => {
		for (let i = 0; i < 5; i++) {
			console.warn(`${i}次执行`);
			setTimeout(() => {
				console.log('set number 前', num);
				setNumber((state) => {
					console.log('set number 内', state, state + 1);
					return state + 1;
				});
				console.log('set number 后', num);
				console.log('---------------------------->');
			}, 1000);
			console.log('timer 后输出');
		}
	};
	
	const handerCount = () => {
		for (let i = 0; i < 5; i++) {
			console.warn(`${i}次执行`);
			setTimeout(() => {
				console.log('set count 前', count);
				setCount(count + 1);
				console.log('set number 后', count);
				console.log('---------------------------->');
			}, 1000);
			console.log('timer 后输出');
		}
	};
	
	console.log(`return前, num: ${num}, count: ${count}`);
	
	return (
		<div>
			<Button onClick={handerNum}>number: {num}</Button>
			<hr />
			<Button onClick={handerCount}>count: {count}</Button>
		</div>
	);
};

export default HomePage;

这就是一个普通的 jsx 文件,思考下面两个问题

  1. 当点击number按钮时输出什么
  2. 当点击count按钮时输出什么

下面我们揭晓答案
点击 number 按钮时输出如下
在这里插入图片描述
点击 count 按钮时输出如下
在这里插入图片描述
我们在 setNumber 中传入的是一个回调,在 setCount 中传入的是一个 表达式,同样是 setState 为什么传入表达式与传入函数的执行结果会有这么大差异(这里面的setState指的是setNumber 和 setCount)下面我们逐一解释handerNum的执行:

const handerNum = () => {
	for (let i = 0; i < 5; i++) {
		console.warn(`${i}次执行`);
		setTimeout(() => {
			console.log('set number 前', num);
			setNumber((state) => {
				console.log('set number 内', state, state + 1);
				return state + 1;
			});
			console.log('set number 后', num);
			console.log('---------------------------->');
		}, 1000);
		console.log('timer 后输出');
	}
};

点击 number 按钮如何执行的

我们知道 js 是顺序执行,当点击 number 按钮时,会调用 handerNum 方法,执行 for 循环,第一次for循环 执行,遇到console.warn(第${i}执行);直接输出 i 的值, 遇到setTimeout会将 setTimeout 放入定时器线程,并记录延迟时间,当延迟时间结束(1秒后),会把 setTimeout回调函数放入事件触发线程,主线程空闲时会调用事件触发线程中的回调函数,接着向下执行遇到console.log('timer 后输出'); 直接输出,第一次for循环结束,以此类推,for循环执行五次以后,定时器线程中就有5个 setTimeout ,定时器线程会根据延迟时间长短确定优先级,相同时间遵循先进先出原则,依次放入事件触发线程,等待主线程空闲调用。至此这个方法执行完毕,这里面有个疑点:为什么console.log('timer 后输出');的执行会在 setTimeout之前,因为 setTimeout是异步任务,总结下: 同步优先,异步靠边,回调垫底,这就是为什么下面红框输出会在一起
在这里插入图片描述
在说 setTimeout 回调之前我们先来说说 setState的执行顺序
当 react 工作流执行的时候(我们把react api 的执行叫react工作流),setState会判断传入的参数是函数还是值(值其实也是表达式,因为表达式最终会计算出具体值)参数类型不同执行的机制也有所差异,setState方法本身就是一个异步方法,分为两种 mountStateupdateState, 如果首次执行setState的时候,先执行 setState回调函数计算出值,然后立马调用组件方法(上面代码里组件方法为HomePage)在组件更新完成后,再执行 setState 后面的代码,这部分属于 mountState 再次执行setState时,react 会先调用组件方法(引起组件更新),接着执行setState的回调计算出更新值,执行渲染输出也就是return的reactDom,最后执行setState后面的代码
下面我们看控制台输出和上面描述是不是一样

接下来我们再看 setTimeout 回调的执行

() => {
	console.log('set number 前', num);
	setNumber((state) => {
		console.log('set number 内', state, state + 1);
		return state + 1;
	});
	console.log('set number 后', num);
	console.log('---------------------------->');
}

第一个 setTimeout 回调的执行,console.log('set number 前', num);控制台直接输出,往下遇到 setNumber 方法,(因为首次执行先执行setState)先执行传入的是回调,console.log直接输出set number 内 0 1 return 计算出值,调用组件函数(也就是HomePage)整个函数组件会重新执行,所以输出

函数内 顶部
return前, num: 1, count: 0

最后执行 setNumber 后面的代码 所以输出

set number 后 0
---------------------------->

第一个 定时器回调执行完成,也就是 mountState,完成
第二个 setTimeout 回调执行,console.log('set count 前', count);控制台输出

set number 前 0

setNumber 的回调会在调用组件方法之后,return之前执行,也就是在遇到 setNumber 会先重新调用下函数组件所以会输出

函数内 顶部

紧接着执行 setNumber 的回调,输出如下,return 最新state

set number 内 1 2

函数组件会使用最新的state做render输出,也就是执行函数组件中的 return 关键字,在执行 return 先执行 return 上的 console.log(return前, num: ${num}, count: ${count}); 输出如下

return, num: 2, count: 0

最后执行 setNumber 后面的代码输出

set number 后 0
index.tsx:35 ---------------------------->

剩下定时器回调执行同上
接下来我们整理下 setState 回调作为参数的执行顺序

mountState:
先调用回调方法计算出state ➞ 调用组件方法 ➞ return ➞ setState后面代码
updateState:
先调用组件方法 ➞ setState回调计算出新的state  ➞ return ➞  setState后面代码

接下来我们再看 setState 传入表达式的执行

### 手动实现 React 中的 `useState` 函数 为了更好地理解 `useState` 的工作原理,可以通过模拟其实现过程来深入了解其内部机制。以下是基于 React Fiber 架构的一个简化版本的手动实现。 #### 实现思路 1. **状态管理**:通过闭包保存组件的状态及其更新方法。 2. **调度器**:每次调用 `setState` 后重新渲染组件。 3. **依赖链**:维护一个数组存储多个 `useState` 调用的结果。 下面是具体的代码实现: ```javascript // 模拟 React 组件环境 let Hooks = null; function render(Component) { Hooks = { states: [] }; // 初始化 Hook 状态容器 const Comp = Component(); Comp.render(); // 渲染组件 } // 自定义 useState 方法 function useState(initialState) { if (Hooks.states.length === 0 || Hooks.index >= Hooks.states.length) { Hooks.states.push({ value: initialState }); // 初次渲染时设置初始值 } const state = Hooks.states[Hooks.index].value; // 获取当前状态 function setState(newValue) { Hooks.states[Hooks.index].value = newValue; // 更新状态 console.log('Re-rendering component...'); // 模拟触发重绘逻辑 } Hooks.index++; // 移动到下一个 Hook return [state, setState]; } ``` 上述代码展示了如何手动实现 `useState` 的核心功能[^1]。其中,`render` 函数用于初始化组件并执行其 `render` 方法;而 `useState` 使用了一个全局变量 `Hooks` 来跟踪所有的状态和索引位置。 #### 测试自定义 `useState` 下面是一个简单的测试案例,展示如何使用该自定义 `useState`: ```javascript function CounterComponent() { const [count, setCount] = useState(0); // 定义计数器状态 function increment() { setCount(count + 1); } this.render = () => { console.log(`Current Count: ${count}`); // 输出当前计数值 increment(); // 增加计数器 }; } // 首次渲染 render(CounterComponent); // 再次渲染以验证状态持久化 setTimeout(() => render(CounterComponent), 1000); ``` 此示例中,每当调用 `increment()` 方法时,都会触发一次状态更新,并打印新的计数值[^2]。 #### 关于源码分析 React 的官方 `useState` 是在其 Fiber 架构下实现的复杂逻辑的一部分。具体来说,在 React 的源码文件中(如引用提到的位置),可以找到更多关于上下文管理和副作用处理的内容[^3]。虽然这里仅提供了一种简化的实现方式,但它足以帮助开发者掌握 `useState` 的基本运作模式。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gleason.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值