作为一名react初学者,虽然之前学习了一遍hooks,但是学的十分仓促,在做项目的时候踩了不少坑,很多知识点没有学习到,故重新学习了一遍useState,写下这篇文章作为总结。
基本用法
首先简单回顾一下useState的基本知识:
- 它会接收唯一一个参数,在组件第一次调用时作为初始值,不设置的话初始值为
undefined - 返回值为数组,包含两个元素,分别为当前状态值:和设置状态的函数(一般通过数组解构赋值)
当状态被更改后,组件会被重新渲染且根据新的值返回dom结构
什么时候触发render?
const UseState = () => {
const [obj, setobj] = useState({ a: 2 });
const changeObj = () => {
// 给obj添加属性
obj.b = 3;
setobj(obj);
};
console.log(obj);
return <button onClick={changeObj}>useState</button>;
};
按照我最开始的理解,obj被更改了,那么setobj就会对组件进行重新渲染,那么在点击按钮之后,会再次打印obj的值。
但是事实上,点击了按钮之后,并没有打印任何值,也就是说,该组件并没有被重新渲染。
通过查询资料我得知:当执行了setobj函数之后,会将传入的值与上次的值进行一个浅比较,当比较发现不同了才会重新执行render,而观察上述代码得知,仅仅给obj添加一个属性,本质上没有更改obj的内存地址 。所以并不能对组件进行重新渲染。
所以,要实现以上效果,可以结合展开运算符,创建一个新的对象并传入其中。
const UseState = () => {
const [obj, setobj] = useState({ a: 2 });
const changeObj = () => {
setobj({...obj,b:3})
};
console.log(obj);
return <button onClick={changeObj}>useState</button>;
};
set函数传入参数可以为函数或者值
- 示例1:当传入参数是值时:
const [count,setCount]=useState(0);
const test = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
console.log(count);
return <button onClick={test}>增加count {count}</button>
//点击按钮后打印1
-
示例2:当传入参数为函数时:
const [count,setCount]=useState(0); const test = () => { setCount(preCount=>preCount + 1); setCount(preCount=>preCount + 1); setCount(preCount=>preCount + 1); }; console.log(count); return <button onClick={test}>增加count {count}</button> //点击按钮后打印3
通过点击按钮,发现实例1打印出来的值是1,而实例2打印了3。
对于示例1,在react中,存在一种叫做合并更新的机制,一次执行了三个setCount操作,它会对其进行合并,最终只执行最后一个,而此时的count暂未进行更新,仍为0,所以最终传入的值为 0+1=1;
而对于示例2,在React文档中提到:"如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值"。
所以说,当需要更新state的值时,直接传入值可以直接进行替换,而传入函数可以根据上次传入值进行更新。
异步还是同步?
通过上面的例子,我得知了想要触发render的条件是传入值与当前值完全不同时才能进行,set函数可以传入参数和函数对state进行更改。但是又有一个新的问题,那就是useState里的set操作是是异步的还是同步的?
const [count, setCount] = useState(0);
// 同步情况
const countIncrement = () => {
setCount((count) => count + 1);
console.log("同步打印1:" + count);
setCount((count) => count + 1);
console.log("同步打印2:" + count);
};
// 异步情况
const countIncrementPromise = () => {
Promise.resolve(() => {
setCount((count) => count + 1);
console.log("异步打印1:" + count);
setCount((count) => count + 1);
console.log("异步打印2:" + count);
});
};
分别执行上述两个函数,打印结果如下,
即可得出结论:无论是同步情况还是异步情况,执行useState 的set函数,都是异步的,并不会在执行set函数之后就立刻进行重新渲染。
react 18之前
以上结果都是基于react18之后的版本,但在react18之前,虽然同步情况下并无变化,但在异步情况下,执行set函数,结论却是不一样的。
//React18之前
const [count, setCount] = useState(1);
const [count2,setCount2]=useState(1);
console.log('组件渲染了‘)
const changeClick = () => {
setTimeout(() => {
setCount(count + 1)
setCount2(count2 + 1)
}, 0)
}
每次执行changeClick函数,组件都会渲染两次,也就是说,在异步事件中,多次执行useState的set函数,每一次的执行都会调用一次render, 它是同步执行的。
总结
useState触发render的条件是:传入的值与上次的值浅比较结果不同了才会进行,(简单理解为内存地址不同了即可触发render)。useState中的set函数可以利用传入函数的方式来更新值的状态,采用直接传入值的方式会覆盖旧值,相当于直接替换。- 在
React18之后,useState所有的setState操作都会是非同步的。而在React18之前:在正常的react的事件流里,set函数为异步执行的。而在异步事件中,set函数为同步执行的。
这篇文章本质上还是一篇学习笔记,有很多内容比较浅,可能有不少错误的地方,想要深入useState可以去查询react源码。