日常抄书之一次性弄懂setState

1.前言

React是通过管理状态来实现对组件的管理。那么React是如何控制组件的状态,又是如何利用状态来管理组件的呢?

在React中是通过this.setState()来更新state。当调用this.setState()的时候,React会重新调用render方法来重新渲染UI。

2.异步setState

setState是一个异步操作。setState是通过队列机制实现state 更新。当执行setState会将需要更新的state合并后放入 状态队列,而不会立刻更新this.state。

//将新的state合并到状态更新队列中
var nextState = this._processPendingState(nextProps, nextContent)
//根据更新队列和shouldComponentUpdate的状态来判断是否需要更新组件
var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext)

3.setState循环调用风险

不要在shouldComponentUpdatecomponentWillUpdate中调用setState,不然会出现死循环。

在调用setState时,实际上回执行enqueueSetState方法,并对partialState_pendingStateQueue更新队列进行合并操作。最终通过enqueueUpdate执行state更新。

performUpdateIfNecessary方法会获取 _pendingElement_pendingStateQueue_pendingForceUpdate,并调用receiveComponentupdateComponent方法进行组件更新。

如果在componentWillUpdateshouldComponentUpdate中调用setState,此时 _pendingStateQueue!==nullperformUpdateIfNecessary会调用updateComponent进行组件更新,而updateComponent又会调用shouldComponentUpdateshouldComponentUpdate,这样就会导致循环调用。

接下来看下setState源码:

ReactComponent.prototype.setState = function(partialState, callback) {
    //调用enqueueSetState,将setState事务放进队列中
    //partialState可以传object,也可以穿function,他会产生新的state以一种
    //Object.assign()的方式跟旧的state进行合并。
    this.updater.enqueueSetState(this, partialState)
    if(callback) {
        this.updater.enqueueCallback(this, callback, 'setState')
    }
}

//实际通过enqueueSetState执行。
//1. 将新的state放进数组
//2. 用enqueueUpdate来处理将要更新的实例对象
enqueueSetState: function(publicInstance, partialState) {
    //获取当前组件的instance
    var internalInstance = getInternalInstanceReadyForUpdate(
        publicInstance,
        'setState'
    )
    
    if(!internalInstance) return
    
    //更新队列合并操作
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = [])
    
    //partialState可以理解为之前的state
    queue.push(partialState)
    //最终通过enqueueUpdate更新,将要更新的component instance放入一个队列
    enqueueUpdate(internalInstance)
}

//如果存在_pendingElement、_pendingStateQueue和_pendingForceUpdate,则更新组件
performUpdateIfNecessary: function(transaction) }
    if(this._pendingElement != null) {
       ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context) 
    }
    if(this._pendingStateQueue !== null || this._pendingForceUpdate) {
        this.updateComponent(transaction, this._currentElement, this._currentElement, this._context)
    }
    
}

4.setState调用栈

function enqueueUpdate(component) {
    ensureInjected();
    
    //如果不处于批量更新模式
    if(!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component)
        return
    }
    //如果处于批量更新模式
    dirtyComponents.push(component)
}
//batchingStrategy
var ReactDefaultBatchingStrategy = {
    isBatchingUpdates: false,
    batchedUpdates: function(callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates
        ReactDefaultBatchingStrategy.isBatchingUpdates = true
        
        if(alreadyBatchingUpdates) {
            callback(a,b,c,d,e)
        }else {
            transaction.perform(callback, null, a, b, c, d, e)
        }
    }
}

batchedUpdate中有一个transaction.perform调用。这就是事务的概念。

5. 事务

事务就是将需要执行的方法使用wrapper封装起来,再通过事务提供的perform方法执行。而在perform之前,先执行所wrapper中的initialize方法,执行完perform之后再执行所有的close方法。一组initialize以及close方法称为一个wrapper

到实现中,事务提供一个mixin方法供其他模块实现自己需要的事务。而要使用事务的模块除了需要把mixin混入自己的事务实现之外,还要额外实现一个抽象getTransactionWrap接口。这个接口用来获取需要封装的前置方法(initialize)和收尾方法(close)。因此它需要返回一个数组的对象,这个对象分别有key为initializeclose的方法。

var Transaction = require('./Transaction')
//我们自己定义的事务
var MyTransaction = function() {}

Object.assign(MyTransaction.prototype, Transaction.mixin, {
    getTransactionWrap: function() {
        return [{
            initialize: function() {
                console.log("before method perform")
            },
            close: function() {
                console.log("after method perform")
            }
        }]
    }
})

var transaction = new MyTransaction()
var testMethod = function() {
    console.log('test')
}
transaction.perform(testMethod)
//打印结果如下:
//before method perform
//test
//after method perform

不想当架构的前端不是一个好厨子
好的前端是慢慢积累出来

目前很多文章都是摘抄记录其他教程。见谅。

2.9k 声望
255 粉丝
0 条评论
推荐阅读
前端路由解析以及实现(学习)
1. 什么是前端路由路由的概念来源于服务端,在服务端中路由描述的是URL与处理函数之间的映射关系。在web前端单页面应用中,路由描述的是 URL 和 UI 之间的映射关系,这种映射关系是单向的,即 URL 变化引起 UI 更...

fsrookie阅读 1.7k

你可能需要的多文档页面交互方案
在日常工作中,面对不同的需求场景,你可能会遇到需要进行多文档页面间交互的实现,例如在 A 页面跳转到 B 页面进行某些操作后,A 页面需要针对该操作做出一定的反馈等等,这个看似简单的功能,却也需要根据不同...

熊的猫8阅读 1.2k

封面图
把React新文档投喂给 GPT-4 后...
大家好,我卡颂。最近,React新文档终于上线了。从内容上看,新文档包括:理论知识、学习指引API介绍从形式上看,新文档除了传统的文字内容,还包括:在线Demo示意图小测验可以说是阅读体验拉满。但是,由于文档...

卡颂7阅读 7.5k评论 3

封面图
PDF 预览和下载你是怎么实现的?
在开发过程中要求对 PDF 类型的发票提供 预览 和 下载 功能,PDF 类型文件的来源又包括 H5 移动端 和 PC 端,而针对这两个不同端的处理会有些许不同,下文会有所提及。

熊的猫7阅读 3.7k评论 1

封面图
第九期:前端九条启发分享
下图是一个常见的列表, 点击列表里的详情按钮会跳到详情页, 那么也许我们在详情页修改了数据状态, 此时可能需要把修改后的状态直接传给列表页从而本地直接更新列表, 这样就不用发送新的api请求与后端交互了。

lulu_up8阅读 839

Next.js-集成状态管理器共享access token以及刷新access token解决方案
SSR和SPA最大的区别就是SSR会区分客户端Client和服务端Server,并且SSR之间只能通过cookie才能在Client和Server之间通信,例如:token信息,以往我们在SPA项目中是使用localStorage或者sessionStorage来存储,但...

Awbeci4阅读 9k评论 2

3个容易混淆的前端框架概念
大家好,我卡颂。有3个容易混淆的前端框架概念:响应式更新单向数据流双向数据绑定在继续阅读本文前,读者可以思考下是否明确知道三者的含义。这三者之所以容易混淆,是因为他们虽然同属前端框架范畴内的概念,但...

卡颂6阅读 910

封面图

目前很多文章都是摘抄记录其他教程。见谅。

2.9k 声望
255 粉丝
宣传栏