useState是在函数组件中使用,setState在类组件中使用。
二者参数对比
setState:
构造函数是唯一可以给 state 赋值的地方
setState( updater,callback )
updater:object/function - 用于更新数据
callback:function - 用于获取更新后最新的 state 值
useState:
const [ state , setState ] = useState(initState)
initState:状态的初始值
state:value(默认值/当前值)
setState(updater) :改变value的方法
updater:object/function - 用于更新数据
什么数据写入 State
如何判断哪些数据是需要写入state的?
- 是否由父组件通过props传递进来的?
- 是否值永远都不会变化?
- 是否可以通过其他state或props计算得出?
如果有以上情况是肯定回答,那么这个值就可以不用写入state中。
State 的更新是异步还是同步
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
countA: 0,
countB: 0,
};
}
handleClick = () => {
setTimeout(() => {
this.setState({ countA: this.state.countA + 10 });
this.setState({ countA: this.state.countA + 10 }); // 执行两次
}, 0);
};
handleClickAsync = () => {
this.setState({ countB: this.state.countB + 10 });
this.setState({ countB: this.state.countB + 10 }); // 两更新合并执行一次。
};
render() {
console.log("render");
console.log("countA: " + this.state.countA);
// console.log("countB: " + this.state.countB);
return (
<div>
<button onClick={this.handleClick}>同步执行{this.state.countA}</button>
<button onClick={this.handleClickAsync}>
异步执行{this.state.countB}
</button>
</div>
);
}
}
执行结果:
1、同步执行按钮:countA: 20,'render'打印两次
2、异步执行按钮:countB: 10,'render'打印一次
结论
只要进入了 react 的调度流程,那就是异步的,多次setState时进行批量更新处理。只要没有进入 react 的调度流程,那就是同步的。
什么时候不会进入 react 的调度流程呢? 在原生事件和 setTimeout、Promise.resolve().then 等情况下。
这些都不会走 React 的调度流程,在这种情况下调用 setState 就是同步的。 否则就是异步的。
而 setState 同步执行的情况下, DOM 也会被同步更新,也就意味着如果你多次 setState ,会导致多次更新,造成性能消耗。
注意
“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”。
scheduleUpdateOnFiber
setState 被调用后最终会走到 scheduleUpdateOnFiber 这个函数里面。
我们看看该函数中的这段代码:
executionContext 代表了目前 react 所处的阶段,而 NoContext 你可以理解为是 react 已经没内容执行的状态。而 flushSyncCallbackQueue 里面就会去同步调用我们的 this.setState ,也就是说会同步更新我们的 state 。所以,当 executionContext 等于 NoContext 的时候,我们的 setState 就是同步的。那什么地方会改变 executionContext 的值呢?
当 react 进入它自己的调度步骤时,会给这个 executionContext 赋予不同的值,表示不同的操作以及当前所处的状态,而 executionContext 的初始值就是 NoContext ,所以只要不进入 react 的调度流程,这个值就是 NoContext ,也就是说在setTimeout 、原生事件内调用 setState,那 setState 就是同步的更新。
拓展
flushSync, unstable_batchedUpdates
import React from "react";
import { flushSync, unstable_batchedUpdates } from "react-dom";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
countA: 0,
countB: 0,
};
}
handleClick = () => {
setTimeout(() => {
unstable_batchedUpdates(() => {
this.setState({ countA: this.state.countA + 10 });
this.setState({ countA: this.state.countA + 10 });
});
});
};
handleClickAsync = () => {
this.setState({ countB: this.state.countB + 10 });
this.setState({ countB: this.state.countB + 10 });
};
render() {
console.log("render");
console.log(this.state.countA);
// console.log(this.state.countB);
return (
<div>
<button onClick={this.handleClick}>同步执行{this.state.countA}</button>
<button onClick={this.handleClickAsync}>
异步执行{this.state.countB}
</button>
</div>
);
}
}
通过上面两种方式处理,可以实现本该是同步更新的setState,变成异步的批量更新。
执行同步执行按钮时效果和异步执行按钮一样。
setState和useState更新引用数据类型比较
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
countArray: [0, 1, 2, 3, 4],
};
}
changeArray = () => {
let array = this.state.countArray;
array[0] = new Date().getTime();
this.setState({
countArray: array,
});
}
render() {
console.log("render");
return (
<div>
<button onClick={this.changeArray}>
修改数组{this.state.countArray[0]}
</button>
</div>
);
}
}
看上面第一段代码中的changeArray,array[0] = 时间戳,然后调用setState,看下结果,重新刷新,结果也变为当前时间戳。
useState同样是赋值后,再去setState,看下结果,却没有更新。
import React, { useState } from "react";
export default function Component() {
const [countArray, setCountArray] = useState([0, 1, 2, 3]);
const changeArray = () => {
let array = countArray;
// let array = countArray.splice(0, countArray.length);
array[0] = new Date().getTime();
setCountArray(array);
};
return (
<div>
<div>hooks</div>
<button onClick={changeArray}>修改数组{countArray[0]}</button>
</div>
);
}
调试可以看到:这里判断了新setstate的数组和之前的数组比较,相同则return,不进行render。反之执行scheduleUpdateOnFiber函数。
React 使用 Object.is比较算法 来比较 state。如果相等则跳过渲染。
结语
react 本身已经做了很多优化措施,但是有时候也会因为代码不同的实现方式而导致 react 的性能优化失效,相当于我们自己做了反优化。所以理解 react 的运行原理对我们日常开发还是很有帮助的。 能力有限,如有不足请多指教。