useState执行机制

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

使用 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 传入表达式的执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Gleason.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值