源码级详解,React 如何利用 setState 进行数据管理的:

setState 是干嘛的

  1. 在 React 中,状态被认为是可读的,我们应始终替换它,而不是直接修改它。
  2. this.state 通常是用来初始化 state 数据的,this.setState 是修改 state 的。
  3. 直接修改 state 是不会触发视图变化的,调用 setState 传入新对象用于修改。
  4. 代码分析:
kotlin 复制代码
this.setState({
    key1: newState1,
    key2: newState2, //将要设置的新状态
    ...
}, callback) // 第二个参数是 state 更新完成后的回调函数

setState 的调用原理

  1. 调用流程图展示:
  1. 源码 级详解:

    1. 进入 setState 这个入口函数后,根据有无回调函数,进入不同的处理队列
    kotlin 复制代码
       ReactComponent.prototype.setState = function (partialState, callback) {
         this.updater.enqueueSetState(this, partialState);
         if (callback) {
           this.updater.enqueueCallback(this, callback, 'setState');
         }
       };
    1. enqueueSetState 方法是通过拿到组件实例中的 state 数组,将其放到状态队列里,并调用 enqueueUpdate 方法来处理接下来要更新的对象
    2. 在 enqueueUpdate 方法中引出了一个 "锁管理器"------ batchingStrategy,这里的 "锁" 指的是全局唯一的 isBatchingUpdates 变量,这个锁决定了是走更新流程,还是应该排队等待。当等到该状态更新时,调用 batchedUpdates 方法来直接立即更新组件。这个全局变量初始值是 "false",意味着队列中没有数据要去更新,即可以直接调用 batchedUpdate 去执行更新操作,执行该更新操作时,再把 "锁" 给锁上(将isBatchingUpdates置为true)。
js 复制代码
 enqueueSetState: function (publicInstance, partialState) {
               // 根据 this 拿到对应的组件实例
               var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
               // 这个 queue 对应的就是一个组件实例的 state 数组
               var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
               queue.push(partialState);
               //  enqueueUpdate 用来处理当前的组件实例
               enqueueUpdate(internalInstance);
             }
js 复制代码
.      function enqueueUpdate(component) {
        ensureInjected();
        // 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
        if (!batchingStrategy.isBatchingUpdates) {
        // 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
        }
        // 否则,先把组件塞入 dirtyComponents 队列里,让它"再等等"
        dirtyComponents.push(component);
        if (component.\_updateBatchNumber == null) {
        component.\_updateBatchNumber = updateBatchNumber + 1;
        }
        }
        .
  1. 额外思考: React 中的 setState 批量更新的过程是什么?

    当我们处理状态高频改变这种场景,调用 setState 时,出于性能原因,state不会立即改变,根据批处理队列,将多次 setState 的状态修改合并成一次状态修改,最终只产生一次组件渲染。

setState 调用之后发生了什么?是同步还是异步的?

  1. setState 调用之后发生了什么:

    首先,会将传入的参数和组件当前状态合并,触发调和过程。

    其次,在调和过程中,React 会根据新状态构建 React 状态树。

    然后,React 得到元素树后,会自动计算新老树的节点差异。根据这些差异算法,能精确定位到哪些位置发生改变,保证了按需更新。

    最后,根据改变位置重新渲染整个页面。

    注:如果短时间内,频繁更新 setState,会将那些新 state 压入栈中,在合适的时机实现批量更新,达到提升性能的效果。

  2. setState 是同步的还是异步的:

    同步状态:每执行一次 setState 时,都会进行 vnode diff + dom 修改。

    异步状态:可以通过一个同步代码实现多个 setState 合并成一次组件更新。

    总的来说, setState 不是单纯的同步/异步的,这取决于它的调用场景。 源码 来说, 通过 isBatchingUpdates 来判断 setState 是先排队还是直接更新,如果要排队(true),那就执行异步操作,不排队就直接更新。以具体适用场景来说, 在 React 可以控制的地方就走异步设计,在 React 无法控制的地方比如原生事件,就只能同步更新。

State 是怎么注入到组件的,从 reducer 到组件经历了什么样的过程

  1. 通过 connect 和 mapStateToProps 将state注入到组件中

  2. reducer 到组件经历了什么样的过程:

    Reducer 对 action 对象进行管理,更新组件状态,将新的状态值返回 store 。

    通过 connect(mapStateToProps,mapDispatchToProps)高阶组件,此时将状态值从 store 取出并作为 props 参数传递到组件

    源码实现:

    javascript 复制代码
     import React from 'react'
        import PropTypes from 'prop-types'
    
        // 高阶组件 contect 
        export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
            class Connect extends React.Component {
                // 通过对context调用获取store
                static contextTypes = {
                    store: PropTypes.object
                }
    
                constructor() {
                    super()
                    this.state = {
                        allProps: {}
                    }
                }
    
                // 第一遍需初始化所有组件初始状态
                componentWillMount() {
                    const store = this.context.store
                    this._updateProps()
                    store.subscribe(() => this._updateProps()); // 加入_updateProps()至store里的监听事件列表
                }
    
                // 执行action后更新props,使组件可以更新至最新状态(类似于setState)
                _updateProps() {
                    const store = this.context.store;
                    let stateProps = mapStateToProps ?
                        mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 没有传入
                    let dispatchProps = mapDispatchToProps ?
                        mapDispatchToProps(store.dispatch, this.props) : {
                                            dispatch: store.dispatch
                                        } // 防止 mapDispatchToProps 没有传入
                    this.setState({
                        allProps: {
                            ...stateProps,
                            ...dispatchProps,
                            ...this.props
                        }
                    })
                }
    
                render() {
                    return <WrappedComponent {...this.state.allProps} />
                }
            }
            return Connect
        }

React 中的 props 和 state 有什么区别

区别 props state
特性 只读性和不可变性Props 是传递给组件的 只能在constructor中初始化,是组件的私有属性只能通过组件内部的 this.setState来修改
作用 父组件向子组件传递数据 组件保存,控制以及修改自己的状态

总结

setState 通过声明式状态管理驱动 UI 更新,其异步合并机制优化了渲染效率。开发者需注意函数式更新、嵌套对象合并及副作用处理,以构建高效可靠的 React 组件。

相关推荐
excel4 分钟前
webpack 核心编译器 十四 节
前端
excel11 分钟前
webpack 核心编译器 十三 节
前端
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪11 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪11 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom12 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github