作者:梁瑞锋(晓玉)
React
重新渲染,指的是在类函数中,会重新执行 render
函数,类似 Flutter
中的 build
函数,函数组件中,会重新执行这个函数
React
组件在组件的状态 state
或者组件的属性 props
改变的时候,会重新渲染,条件简单,但是实际上稍不注意,会引起灾难性的重新渲染
为什么拿类组件先说,怎么说呢,更好理解?还有前几年比较流行的一些常见面试题
React
中的setState
什么时候是同步的,什么时候是异步的
React
setState
怎么获取最新的state
以下代码的输出值是什么,页面展示是怎么变化的
test = () => {
// s1 = 1
const {
s1 } = this.state;
this.setState({
s1: s1 + 1});
this.setState({
s1: s1 + 1});
this.setState({
s1: s1 + 1});
console.log(s1)
};
render() {
return (
<div>
<button onClick={
this.test}>按钮</button>
<div>{
this.state.s1}</div>
</div>
);
}
看到这些类型的面试问题,熟悉
React
事务机制的你一定能答出来,毕竟不难嘛,哈?你不知道React
的事务机制?百度|谷歌|360|搜狗|必应 React 事务机制
React
合成事件在 React
组件触发的事件会被冒泡到 document
(在 react v17
中是 react
挂载的节点,例如 document.querySelector(‘#app’)),然后 React
按照触发路径上收集事件回调,分发事件。
React
事件就没法触发了?确实是这样,没法冒泡了,React
都没法收集事件和分发事件了,注意这个冒泡不是 React
合成事件的冒泡。React
,就算是在合成捕获阶段触发的事件,依旧在原生冒泡事件触发之后reactEventCallback = () => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
console.log('after setState s1:', this.state.s1);
// 这里依旧输出 1, 页面展示 2,页面仅重新渲染一次
};
<button
onClick={
this.reactEventCallback}
onClickCapture={
this.reactEventCallbackCapture}
>
React Event
</button>
<div>
S1: {
s1} S2: {
s2} S3: {
s3}
</div>
setState
定时器回调执行 setState
是同步的,可以在执行 setState
之后直接获取,最新的值,例如下面代码
timerCallback = () => {
setTimeout(() => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
console.log('after setState s1:', this.state.s1);
// 输出 2 页面渲染 3 次
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
});
};
setState
异步函数回调执行 setState
是同步的,可以在执行 setState
之后直接获取,最新的值,例如下面代码
asyncCallback = () => {
Promise.resolve().then(() => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
console.log('after setState s1:', this.state.s1);
// 输出 2 页面渲染 3 次
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
});
};
原生事件同样不受 React
事务机制影响,所以 setState
表现也是同步的
componentDidMount() {
const btn1 = document.getElementById('native-event');
btn1?.addEventListener('click', this.nativeCallback);
}
nativeCallback = () => {
// s1 s2 s3 都是 1
const {
s1, s2, s3 } = this.state;
this.setState({
s1: s1 + 1 });
console.log('after setState s1:', this.state.s1);
// 输出 2 页面渲染 3 次
this.setState({
s2: s2 + 1 });
this.setState({
s3: s3 + 1 });
};
<button id="native-event">Native Event</button>
setState
修改不参与渲染的属性setState
调用就会引起就会组件重新渲染,即使这个状态没有参与页面渲染,所以,请不要把非渲染属性放 state
里面,即使放了 state
,也请不要通过 setState
去修改这个状态,直接调用 this.state.xxx = xxx
就好,这种不参与渲染的属性,直接挂在 this
上就好,参考下图
// s1 s2 s3 为渲染的属性,s4 非渲染属性
state = {
s1: 1,
s2: 1,
s3: 1,
s4: 1,
};
s5 = 1;
changeNotUsedState = () => {
const {
s4 } = this.state;
this.setState({
s4: s4 + 1 });
// 页面会重新渲染
// 页面不会重新渲染
this.state.s4 = 2;
this.s5 = 2;
};
<div>
S1: {
s1} S2: {
s2} S3: {
s3}
</div>;
setState
,页面会不会重新渲染几种情况,分别是:
setState
,无参数setState
,新 state
和老 state
完全一致,也就是同样的 state
sameState = () => {
const {
s1 } = this.state;
this.setState({
s1 });
// 页面会重新渲染
};
noParams = () => {
this.setState({
});
// 页面会重新渲染
};
这两种情况,处理起来和普通的修改状态的 setState
一致,都会引起重新渲染的
为什么要提上面这些,仔细看,这里提到了很多次渲染的 3
次,比较契合我们日常写代码的,异步函数回调,毕竟在定时器回调或者给组件绑定原生事件(没事找事是吧?),挺少这么做的吧,但是异步回调就很多了,比如网络请求啥的,改变个 state
还是挺常见的,但是渲染多次,就是不行!不过利用 setState
实际上是传一个新对象合并机制,可以把变化的属性合并在新的对象里面,一次性提交全部变更,就不用调用多次 setState
了
asyncCallbackMerge = () => {
Promise.resolve().then(<