Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 18 新特性(一):自动批量更新 #14

Open
beichensky opened this issue Jul 29, 2021 · 0 comments
Open

React 18 新特性(一):自动批量更新 #14

beichensky opened this issue Jul 29, 2021 · 0 comments

Comments

@beichensky
Copy link
Owner

beichensky commented Jul 29, 2021

前言

本文已收录在 Github: https://github.com/beichensky/Blog 中,欢迎 Star,欢迎 Follow!

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

原因就是因为 componentDidMount 中的 setState 是批量更新,在整体逻辑没走完之前,不会进行更新。所以前两次打印结果都是 0,并且将两次更新合并成了一次。

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

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

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

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

    • count: 状态

    • queue: 存储状态的数组

  • 声明一个 handleClick 方法,来模拟 React 合成事件

  • 声明一个 setState 方法,来模拟 ReactsetState

// 判断是否批量更新的标志
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

手动批量更新

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

React 也提供了一种解决方案:从 react-dom 包中暴露了一个 API: unstable_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(() => {
            ReactDOM.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 之前 setState 的更新方式就说到这里,那 React 18 里做了什么改动呢?

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

仅仅是使用了新的 APIReactDOM.createRoot(root).render(jsx)。React 就能实现自动的批量更新了。感觉有点神奇。

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

  • 此时不需要 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 之后提供了 ReactDOM.createRoot(root).render(jsx) 的 API,那之前 ReactDOM.render 的 API 还支持吗?

    A: 支持的,并且行为和之前版本是一致的。只有使用了 ReactDOM.createRoot 这种方式,才会启用新的并发模式。

  2. Q: React 全自动更新后,那如果我就是想拿到更新之后的数据怎么办呢?
    A: 类组件中可以使用 setState(state, callback) 的方式,在 callback 中取到最新的值,函数组件可以使用 useEffect,将 state 作为依赖。即可以拿到最新的值。

  3. Q: 文章中说到的优先级的概念是怎么回事呢?
    A: 这个涉及到 React 最新的调度以及更新的机制,优先级的概念以及其他优先级的任务如何创建,我们之后会一一展开来说。

    目前的话,可以理解为 React 的更新机制进行了变化,不再依赖于批量更新的标志。而是根据任务优先级来进行更新:高优先级的任务先执行,低优先级的任务后执行。

写在后面

代码量很少,主要是修改了 ReactDOM 的渲染方式,可以亲自尝试一下,有疑惑的地方可以说出来一起进行讨论。

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

如果喜欢或者有所帮助,欢迎 Star,对作者也是一种鼓励和支持。

@beichensky beichensky changed the title React 18 新特性(一):自动更新 React 18 新特性(一):自动批量更新 Jul 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant