2023年11月26日发(作者:)

React18新特性(⼀):⾃动批量更新

前⾔

18 版本之前

经典⾯试题:setState 是同步还是异步

版本之前,在⾯试中经常会出现这个问题,那么答案⼜是什么样的呢?

react 18

合成事件中是异步的

React

中是异步的

hooks

其他情况皆是同步的,例如:原⽣事件、

setTimeoutPromise

看看下⾯这段代码的执⾏结果,就知道所⾔⾮虚了

class App extends React.Component {

state = {

count: 0

}

componentDidMount() {

this.setState({count: this.state.count + 1})

console.log(this.state.count);

this.setState({count: this.state.count + 1})

console.log(this.state.count);

setTimeout(() => {

this.setState({count: this.state.count + 1})

console.log(this.state.count);

this.setState({count: this.state.count + 1})

console.log(this.state.count);

});

}

render() {

return <h1>Count: {this.state.count}h1>

}

}

有经验的同学肯定都知道,最终的结果是:

0 0 2 3

原因就是因为 中的 是批量更新,在整体逻辑没⾛完之前,不会进⾏更新。所以前两次打印结果都是 0,并且

componentDidMountsetState

将两次更新合并成了⼀次。

⽽在 中,脱离了 的掌控,变成了同步更新,因为下⽅的 可以实时打印出即时的状态。

setTimeoutReactlog

此时 的内部的处理逻辑我们可以写⼀段代码简单模拟⼀下:

React

先声明三个变量,⽤来记录数据

isBatchUpdate

: 判断是否批量更新的标志

count

: 状态

queue

: 存储状态的数组

声明⼀个 ⽅法,来模拟 合成事件

handleClickReact

声明⼀个 ⽅法,来模拟

setStateReactsetState

//

判断是否批量更新的标志

let isBatchUpdate = false;

//

状态

let count = 0;

//

存储最新状态的数组

let queue = [];

const setState = (state) => {

//

批量更新,则将状态暂存,否则直接更新

if (isBatchUpdate) {

queue.push(state);

} else {

count = state;

}

}

const handleClick = () => {

// isBatchUpdate true

进⼊事件,先将设置为

isBatchUpdate = true

setState(count + 1)

console.log(count);

setState(count + 1)

console.log(count);

setTimeout(() => {

setState(count + 1)

console.log(count);

setState(count + 1)

console.log(count);

})

// isBatchUpdate false

事件结束,将置为

isBatchUpdate = false;

}

handleClick();

count = queue.pop();

// queue

更新完成,重置状态数组

queue = [];

可以看到,上⾯这段代码的打印结果也是

0 0 2 3

⼿动批量更新

上⾯提到,在原⽣事件以及 等情况下, 是同步的,那如果我们仍然希望这种情况下可以同步更新,该怎么办呢?

setTimeoutsetState

ReactreactAPIunstable_batchedUpdates

也提供了⼀种解决⽅案:从 包中暴露了⼀个 :

那我们简单⽤⼀下看看效果:

class App extends React.Component {

state = {

count: 0

}

componentDidMount() {

this.setState({count: this.state.count + 1})

console.log(this.state.count);

this.setState({count: this.state.count + 1})

console.log(this.state.count)

setTimeout(() => {

React.unstable_batchedUpdates(() => {

this.setState({count: this.state.count + 1})

console.log(this.state.count)

this.setState({count: this.state.count + 1})

console.log(this.state.count)

})

})

}

render() {

return <h1>Count: {this.state.count}h1>

}

}

可以看到此时的打印结果为

0 0 1 1

Ok,React 18 之前 的更新⽅式就说到这⾥,那 React 18 ⾥做了什么改动呢?

setState

React 18 版本之后

上⾯提到了默认批量更新以及⼿动批量更新,那有些同学不满⾜了呀,觉得⼿动的还是不够智能,在很多情况下还得⼿动去调⽤

unstable_batchedUpdates

这个函数,⽤起来不爽。

别急,React 18 新版本就可以解决这些同学的痛点了!

Ok,直接上代码,看看 React 18 到底怎么⽤的

class App extends React.Component {

state = {

count: 0

}

componentDidMount() {

this.setState({count: this.state.count + 1})

console.log(this.state.count);

this.setState({count: this.state.count + 1})

console.log(this.state.count)

setTimeout(() => {

this.setState({count: this.state.count + 1})

console.log(this.state.count)

this.setState({count: this.state.count + 1})

console.log(this.state.count)

})

}

render() {

return <h1>Count: {this.state.count}h1>

}

}

// react 18 dom render

使⽤新的并发模式写法进⾏

ReactDOM.createRoot(document.getElementById('#root')!).render(<App />)

组件代码保持和第⼀版的⼀致,没有使⽤

unstable_batchedUpdates

可以看到,此时的打印结果也是:

0 0 1 1

仅仅是使⽤了新的 。React 就能实现⾃动的批量更新了。感觉有点神奇。

APIRoot(root).render(jsx)

我们依然写⼀段代码来模拟⼀下这个过程:

此时不需要 来判断是否批量更新了,⽽是通过更新的优先级来进⾏判断

isBatchUpdate

每次更新会进⾏优先级的判定,相同优先级的任务会被合并。

事件执⾏完毕,进⾏任务的执⾏和更新

//

状态

let count = 0;

//

存储状态的数组

let queue = [];

const setState = (state) => {

const newState = {payload: state, priority: 0 }

//

判断当前优先级的任务集合是否存在,不存在则初始化,存在则存到对应由县级的任务集合中

if (queue[newState.priority]) {

queue[newState.priority].push(newState.payload)

} else {

queue[newState.priority] = [newState.payload]

}

}

const handleClick = () => {

setState(count + 1)

console.log(count);

setState(count + 1)

console.log(count);

setTimeout(() => {

setState(count + 1);

console.log(count);

setState(count + 1)

console.log(count);

})

}

handleClick();

count = queue.pop().pop();

setTimeout(() => {

count = queue.pop().pop();

})

可以看到,上⾯这段代码的执⾏结果也是

0 0 1 1

上述模拟代码仅为了展⽰优先级批量更新,不代表任何 React 源码的逻辑和思想

好了,⾃动批量更新的新特性就说到这⾥了。这⾥引⼊了三个问题:

1. Q: React 18 之后提供了 (root).render(jsx) 的 API,那之前 的 API 还⽀持吗?

Root

A: ⽀持的,并且⾏为和之前版本是⼀致的。只有使⽤了 这种⽅式,才会启⽤新的并发模式。

Root

2. Q: React 全⾃动更新后,那如果我就是想拿到更新之后的数据怎么办呢?

A: 类组件中可以使⽤ 的⽅式,在 中取到最新的值,函数组件可以使⽤ ,将 作为依

setState(state, callback)callbackuseEffectstate

赖。即可以拿到最新的值。

3. Q: ⽂章中说到的优先级的概念是怎么回事呢?

A: 这个涉及到 React 最新的调度以及更新的机制,优先级的概念以及其他优先级的任务如何创建,我们之后会⼀⼀展开来说。

⽬前的话,可以理解为 React 的更新机制进⾏了变化,不再依赖于批量更新的标志。⽽是根据任务优先级来进⾏更新:⾼优先级的任

务先执⾏,低优先级的任务后执⾏。

写在后⾯

代码量很少,主要是修改了 的渲染⽅式,可以亲⾃尝试⼀下,有疑惑的地⽅可以说出来⼀起进⾏讨论。

ReactDOM

如果有写的不对或不严谨的地⽅,欢迎⼤家能提出宝贵的意见,⼗分感谢。

如果喜欢或者有所帮助,欢迎 Star,对作者也是⼀种⿎励和⽀持。