• 简单解读react的setState


    本文将以一道面试题“react中setState是同步的还是异步?”进行解读。

    补充说明:setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

    setState表现为异步的场景如下:

    1.合成事件中的setState

    react自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件。

    class SetStatus extends Component {state = { val: 0 }increment = () => {debuggerthis.setState({val: this.state.val +1})console.log('---------'. val) // 0}render() {return (
    {`Counter is: ${this.state.val}`}
    )} }
    • 1
    • 2

    只有当increment函数执行完以后,才会更新state,执行performSyncWorkOnRoot

    2.生命周期函数中的setState

    class StudySetState extends Component {state = {val: 0}componentDidMount() {debuggerthis.setState({val: this.state.val + 1})console.log('-----', this.state.val) // 0}render() {return ( 
    {`Counter is ${this.state.val}`}
    )} }
    • 1
    • 2

    原理:其实还是和合成事件一样,当 componentDidmount 执行的时候,react内部并没有更新,执行完componentDidmount 后才去 commitUpdateQueue 更新。这就导致你在 componentDidmountsetState 完去console.log拿的结果还是更新前的值。源码真相:

    if ( finishedWork.mode & ProfileMode) { try { startLayoutEffectTimer(); instance.componentDidMount(); // 执行完componentDidMount() } finally { recordLayoutEffectDuration(finishedWork); }} else { instance.componentDidMount();
    }
    var updateQueue = finishedWork.updateQueue;
     if (updateQueue !== null) { { if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {if (instance.props !== finishedWork.memoizedProps) { error('Expected %s props to match memoized props before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); }if (instance.state !== finishedWork.memoizedState) { error('Expected %s state to match memoized state before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');} }
    } // We could update instance props and state here,// but instead we rely on them being set during last render.// TODO: revisit this when we implement resuming.
     commitUpdateQueue(finishedWork, updateQueue, instance); // 更新props和state
     } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    setState表现为同步的场景如下:

    1.原生事件的setState

    class StudySetState extends Component {state = { val: 0 }changeValue = () => {this.setState({val: this.state.val + 1})console.log('-----', this.state.val) // 1}componentDidMount() {document.body.addEventListener('click', this.changeValue, false)}render() {return (
    {`Counter is: ${this.state.val}`}
    )} }
    • 1
    • 2

    原生事件是指非react合成事件,原生自带的事件监听 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick 这种绑定事件的形式都属于原生事件。

    原生事件的调用栈就比较简单了,因为没有走合成事件的那一大堆,直接触发click事件,到 requestWork ,在requestWork里由于 expirationTime === Sync 的原因,直接走了 performSyncWork 去更新,并不像合成事件或钩子函数中被return,所以当你在原生事件中setState后,能同步拿到更新后的state值。

    2.setTimeout中的setState

    class App extends Component {state = { val: 0 }
    
     componentDidMount() {setTimeout(_ => {this.setState({ val: this.state.val + 1 })console.log(this.state.val) // 输出更新后的值 --> 1}, 0)
     }render() {return (
    {`Counter is: ${this.state.val}`}
    )} }
    • 1
    • 2
    • 3
    • 4
    • 5

    setTimeout 中的 setState 并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout ,可以在钩子函数中 setTimeout ,也可以在原生事件setTimeout,但是不管是哪个场景下,基于事件循环机制,setTimeout中里去setState总能拿到最新的state值。

    不同react版本下setState的表现

    在react18之前

    • 在组件生命周期或React合成事件中,setState是异步
    • 在setTimeout或者原生dom事件以及Promise这类不是由React进行发起的事件中,setState是同步

    在react18之后

    • 在React18之后,默认所有的操作都被放到了批处理中(异步处理)
    • 如果希望代码可以同步会拿到,则需要执行特殊的flushSync操作
    class StudySetState extends Component {state = { val: 0}handleClick = () => {flushSync(() => {this.setState({val: 2})})console.log('-------', this.state.val) // 2}render() {return ()}
    } 
    
    • 1
    • 2

    最后

    最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



    有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 相关阅读:
    Mysql_Note7
    黔院长 | 不忘初心在逆境中前行!
    Lua语言编写爬虫程序
    Bert基础(二)--多头注意力
    计算机毕业设计(附源码)python疫情防控管理系统
    SpringCloud
    Ubuntu22.04安装CUDA深度学习环境&&cuda principle
    20个Python面试题来挑战你的知识
    Jmeter结构体系
    Android应用程序启动源码浅析-(三万字长文慎点&Android14)
  • 原文地址:https://blog.csdn.net/web2022050901/article/details/127730957