React-useState在不同模式下的同步/异步问题
前言
React有三种不同的模式,concurent模式、legacy模式,以及处于concurrent和legacy之间的过渡模式blocking。
这里主要讲下state 处于 Concurrent模式与 Legacy模式 差异性。
大概对应的React版本号:
模式 | 版本 |
---|---|
Concurrent | React18以及之后的版本 |
Legacy | React17以及之前的版本 |
React未来的版本都将采用 Concurrent 模式,其采用渐进增强的方式向后兼容。
以下皆在函数组件举例,state的行为 类组件与函数组件 中表现一致。
legacy 模式
在钩子函数与合成事件中,state的表现是:批量的、异步的
import { Button } from "antd"
import React, { useState } from "react"
export const Index = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(1)
console.log('111--', count)
setCount(2)
console.log('222--', count)
setCount(3)
console.log('333--', count)
}
console.log('render --', count)
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
)
}
打印的结果:
批量的:调用了三次setCount,但是最后函数组件只渲染一次(只打印一次 render ---3),也就是将批量的更新合并成一次并进行更新的。
异步的:同时打印了三次 count
并且count值打印三次都是0,这是因为函数组件的更新,本质上是重新执行了一次函数,也就是说通过setCount更新的count值,需要在下次函数执行的时候,可以拿到更新后的值。
在异步操作(setTimeout、setInterval)中,state的表现是:非批量的、同步的
import { Button } from "antd"
import React, { useState } from "react"
export const Index = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setTimeout(() => {
setCount(1);
console.log("count读取的是闭包值 111--", count);
setCount(2);
console.log("count读取的是闭包值 222--", count);
setCount(3);
console.log("count读取的是闭包值 333--", count);
});
}
console.log('render --', count)
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
)
}
打印结果:
打印结果可以看到,代码是同步执行的,并且每执行一次setCount,都会更新一次函数组件, 也就是说更新是非批量的
unstable_batchedUpdates
在异步操作中,可以使用 unstable_batchedUpdates 实现手动批量更新。
import { Button } from "antd"
import React, { useState } from "react"
import { unstable_batchedUpdates } from "react-dom"
export const Index = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setTimeout(() => {
unstable_batchedUpdates(() => {
setCount(1);
console.log("count读取的是闭包值 111--", count);
setCount(2);
console.log("count读取的是闭包值 222--", count);
setCount(3);
console.log("count读取的是闭包值 333--", count);
})
});
}
console.log('render --', count)
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
)
}
打印结果:
可以看到,在引入了 unstable_batchedUpdates 改造异步操作的代码,setCount从非批量、同步 变成了 批量、异步。
场景
unstable_batchedUpdates 有一个经常遇到的使用场景,那就是在发起请求,获取结果后需要更新多次的state
import { Button } from "antd";
import React, { useState } from "react";
export const Index = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const [age, setAge] = useState(18);
const getUserInfoHttp = () => {
return new Promise((resolve) => {
setTimeout(() => {
const obj = {
name: "jack",
age: 25,
pageCount: 21,
data: []
};
resolve(obj);
}, 1000);
});
};
const handleClick = () => {
getUserInfoHttp().then((res) => {
setName(res?.name);
setAge(res?.age);
setCount(res?.pageCount);
});
};
console.log("render name--", name);
console.log("render age--", age);
console.log("render count--", count);
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
);
};
打印结果:
可以看到打印了三次,也就是页面刷新太频繁了,可以使用 unstable_batchedUpdates 做下优化
import { Button } from "antd";
import React, { useState } from "react";
import { unstable_batchedUpdates } from "react-dom";
export const Index = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const [age, setAge] = useState(18);
// 发起请求获取信息
const getUserInfoHttp = () => {
return new Promise((resolve) => {
setTimeout(() => {
const obj = {
name: "jack",
age: 25,
pageCount: 21,
data: []
};
resolve(obj);
}, 1000);
});
};
const handleClick = () => {
getUserInfoHttp().then((res) => {
// 批量更新优化
unstable_batchedUpdates(() => {
setName(res?.name);
setAge(res?.age);
setCount(res?.pageCount);
});
});
};
console.log("render name--", name);
console.log("render age--", age);
console.log("render count--", count);
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
);
};
打印结果:
使用了 unstable_batchedUpdates 优化之后,页面只刷新了一次
concurrent 模式
不管是在异步操作中还是在合成事件中,state都表现出:批量的、异步的
1. 合成事件中
import { Button } from "antd"
import { useState } from "react"
export const Index = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(1)
console.log('111--', count)
setCount(2)
console.log('222--', count)
setCount(3)
console.log('333--', count)
}
console.log('render --', count)
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
)
}
打印结果:
2. 在异步操作中
import { Button } from "antd"
import { useState } from "react"
export const Index = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setTimeout(() => {
setCount(1);
console.log("count读取的是闭包值 111--", count);
setCount(2);
console.log("count读取的是闭包值 222--", count);
setCount(3);
console.log("count读取的是闭包值 333--", count);
});
}
console.log('render --', count)
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
)
}
打印结果:
flushSync 设置优先级
flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中
使用flushSync 设置优先级
import { Button } from "antd"
import { useState } from "react"
import { flushSync } from "react-dom"
export const Index = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(1);
// 设置优先级
flushSync(() =>{
setCount(2);
console.log("count 111--", count);
})
setCount(3);
}
console.log('render --', count)
return (
<div>
<Button onClick={handleClick}>test</Button>
</div>
)
}
打印结果:
可以看到,先更新了setCount(2),后更新setCount(3)
结论
state 的同步/异步问题,需要看场景、React当前模式
-
1.Legacy 模式下
a.在钩子函数和合成事件中,state表现为批量的、异步的
b.在异步操作中(setTimeout、Promise),state表现为非批量的、同步的
-
2.Concurrent 模式下
state表现都是 批量的、异步的
useState 需要注意的一些问题
浅比较
const [userInfo, setUserInfo] = useState({})
const copyData = { ...userInfo } // 进行浅拷贝 重新分配内存空间
copyData.name = 'jack'
setUserInfo(copyData)
以下是无效的写法:
const [userInfo, setUserInfo] = useState({})
userInfo.name = 'jack'
setUserInfo(userInfo) // 不会更新 因为浅比较的结果是,值userInfo没有发生变化
原因分析:在进行setState更新值的时候,React会进行一次浅比较,也就是当前更新的值和上一次的值,是否有发生变化,如果值没有发生变化,那么不进行更新,否则进行更新。浅比较是进行内存地址的比较,因此直接在原来的值上进行修改,相当于没有变化
获取最新值
给 useState 的 dispatch 传递的参数是一个函数,既可获取修改后的值
const [count, setCount] = useState(0)
setCount(value => value + 1) // count = 0 + 1
setCount(value => value + 1) // count = 1 + 1
setCount(value => value + 1) // count = 2 + 1
参考文献
转载自:https://juejin.cn/post/7244420350753243195