useState和setState使用比较
首先最明显的区别:useState是在函数组件中使用,setState在类组件中使用
二者参数对比
setState( updater,callback )
updater:object/function - 用于更新数据
callback:function - 用于获取更新后最新的 state 值
useState(initState)
const [ state , setState ] = useState(initState)
state:value
setState(updater) :改变value的方法
updater:object/function - 用于更新数据
initState:状态的初始值
setState:
首先看一下setState的用法,构造中给state中定义了三个变量count, status,countArray,同时启动定时器,3s后改变status的值,render并绘制变量结果
import React, { Component } from "react";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
status: 0,
countArray: [0, 1, 2, 3, 4],
};
setTimeout(() => {
this.setState({
status: 1,
});
console.log(this.state.status);
}, 3000);
}
onClick() {
this.setState(
{
count: this.state.count + 3,
},
() => {
console.log("callback this.state.age = ", this.state.count);
}
);
console.log("this.state.age = ", this.state.count);
}
changeArray() {
let array = this.state.countArray;
array[0] = 10;
this.setState({
countArray: array,
});
}
render() {
console.log("render Home ", new Date().getTime());
return (
<div className="container">
<div>{`count is ${this.state.count}-${this.state.status} -${this.state.countArray[0]}`}</div>
<button
onClick={() => {
this.onClick();
}}
className="btn btn-primary"
>
Click me1
</button>
<button
onClick={() => {
this.changeArray();
}}
className="btn btn-primary"
>
Click me2
</button>
</div>
);
}
}
浏览器中运行,渲染了两次,并且可以看到status值同步变化了,先render,再打印出结果值1,见下图
setState是同步的么?接着往下看,点击第一个按钮,调用this.setState,使count+3,输出结果如下:
点击后,显示为3,但log却同步打印出count值为0,然后render,callback回调中打印出count值为3
这是异步?
对比调试,这里的executionContext,在setTimeout时,context = NoContext,直接执行,对应官方解释
// Flush the synchronous work now, unless we’re already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
executionContext 代表了目前 react 所处的阶段,而 NoContext 我们可以理解为是 react 已经没要执行的内容。而 flushSyncCallbackQueue 里面就会去同步调用我们的 this.setState ,也就是说会同步更新我们的 state 。所以,当 executionContext 为 NoContext 的时候,我们的 setState 就是同步的。那什么地方会改变 executionContext 的值呢?
当 react 进入它自己的调度步骤时,会给这个 executionContext 赋予不同的值,表示不同的操作以及当前所处的状态,而 executionContext 的初始值就是 NoContext ,所以只要不进入 react 的调度流程,这个值就是 NoContext ,也就是说在setTimeout 、原生事件内调用 setState,那 setState 就是理解成同步的。
useState
import React, { useState, useEffect } from "react";
function Home2() {
const [count, setCount] = useState(0);
const [countArray, setCountArray] = useState([0, 1, 2, 3]);
const onClick = () => {
setCount(count + 1);
console.log("onClick count ", count, "time = ", new Date().getTime());
setTimeout(() => {
setCount(count + 1);
console.log("onClickTimeout count = ",count,"time = ",new Date().getTime());
}, 3000);
};
const onClick2 = () => {
let array = countArray;
array[0] = 10;
setCountArray(array);
// let array = [10,1,2,3]
// setCountArray(array)
};
useEffect(() => {
console.log("useEffect = ", count); // 这里是监控到的最新值
}, [count]);
console.log("render Home2");
return (
<div>
<div> {`now count = ${count}- array = ${countArray[0]}`}</div>
<button className="btn btn-primary" onClick={onClick}>
Click me1
</button>
<button className="btn btn-primary" onClick={onClick2}>
Click me2
</button>
</div>
);
}
export default Home2;
onClick后,先打印count为0,然后render,绘制时count的值就是+1后的值了。3s后的定时器,跟setState一致,先调用了render,再打印结果,但是这时候打印count的值却是点击前的0,这是因为必包问题。根据这个打印顺序,我们其实也可以推理出跟setstate一样的结论。
闭包问题,我们可以通过useRef来验证一下,看如下代码
import React, { useState, useEffect } from "react";
function Home2() {
const [count, setCount] = useState(0);
const [countArray, setCountArray] = useState([0, 1, 2, 3]);
const countRef = useRef();
countRef.current = count;
const onClick = () => {
setCount(count + 1);
console.log("onClick count ", count, "time = ", new Date().getTime());
setTimeout(() => {
console.log(
"onClickTimeout before count countRef.current = " + countRef.current
);
setCount(countRef.current + 1);
console.log(
"onClickTimeout after count countRef.current = " + countRef.current
);
}, 3000);
};
....
}
export default Home2;
验证结果如下:
闭包问题陷阱参考:
https://blog.csdn.net/weixin_38080573/article/details/115178502
当setState和useState使用数组和对象时,有什么区别么?
看上面第一段代码中的changeArray,array[0] = 10,然后调用setState,看下结果,重新刷新,结果也变为10
再看看第二段代码:
同样是赋值后,再去setState,看下结果,却没有更新
调试可以看到:这里判断了新setstate的数组和之前的数组比较,相同则return,不进行render
JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。
那我们在使用setState修改数组时,需要做深拷贝。
let array = countArray.slice(0, countArray.length);
array[0] = 10;
setCountArray(array);
这样结果有变化并更新。对object的处理同样如此。