关于React setState的实现原理(二)
React中的Transaction
大家学过sql server的都知道我们可以批量处理sql语句,原理其实都是基于上一篇我们说的Datch Update机制。当所有的操作均执行成功,才会执行修改操作;若有一个操作失败,则执行rollback(回滚)。
在React中,我们介绍过事件会在函数前后执行自己的逻辑,具体就是调用perform方法进入一个事件,这个方法会传入一个method参数。执行perform时先执行initializeAll方法按照一定顺序执行一系列的initialize
惭怍,然后执行传入的method,method执行完后,就执行closeAll方法按照一定顺序执行一系列的close操作。注意一种事件不能同时开启,否则会抛出异常。给一个例子是实现事件:
var Transaction = require('./Transaction');
// 我们自己定义的 Transaction
var MyTransaction = function() {
// do sth.
};
Object.assign(MyTransaction.prototype, Transaction.Mixin, {
getTransactionWrappers: 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
具体到实现上,React 中的 Transaction 提供了一个 Mixin 方便其它模块实现自己需要的事务。而要使用 Transaction 的模块,除了需要把 Transaction 的 Mixin 混入自己的事务实现中外,还需要额外实现一个抽象的 getTransactionWrappers 接口。这个接口是 Transaction 用来获取所有需要封装的前置方法(initialize)和收尾方法(close)的,因此它需要返回一个数组的对象,每个对象分别有 key 为 initialize 和 close 的方法。
当然在实际代码中 React 还做了异常处理等工作,这里不详细展开。有兴趣的同学可以参考源码中 Transaction 实现。
组件调用ReactDOM.render()之后,会执行一个_renderNewRootComponent的方法,大概是该方法执行了一个ReactUpdates.batchedUpdates()。 那么batchedUpdates是什么呢?
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
从代码中可以看出,这个batchedUpdate是第一次调用alreadyBatchingUpdates是false(储存起来了),回去执行transaction.perform(method)(前边说过perform执行会进入一个时间),这样就进入第一个事务,
这个事务是啥我们现在不用管,我们只需要知道这个transaction是ReactDefaultBatchingStrategyTransaction的实例,它代表了其中一类事务的执行。然后会执行method方法,就会进行组件的首次装载。完成后会调用
componentDidMount(注意,此时还是在执行method方法,事务还没结束,事务只有在执行完method后执行一系列close才会结束),在该方法中,我们调用了setState,出现了一系列奇怪的现象。因此,我们再来看看
setState方法:
ReactComponent.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
setState在调用时做了两件事,第一,调用enqueueSetState。该方法将我们传入的partialState添加到一个叫做_pendingStateQueue的队列中去存起来,然后执行一个enqueueUpdate方法。第二,如果存在callback就调用enqueueCallback将其存入一个_pendingCallbacks队列中存起来。然后我们来看enqueueUpdate方法。
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
上面的代码中,个batchingStrategy就是上面的ReactDefaultBatchingStrategy,只是它通过inject的形式对其进行赋值,比较隐蔽。因此,我们当前的setState已经处于了这一类事务之中,isBatchingUpdates已经被置为true,所以将会把它添加到dirtyComponents中,在某一时刻做批量更新。因此在前两个setState中,并没有做任何状态更新,以及组件更新的事,而仅仅是将新的state和该组件存在了队列之中,因此两次都会打印出0,我们之前的第一个问题就解决了,还有一个问题,我们接着往下走。
在setTimeout中执行的setState打印出了2和3,有了前面的铺垫,我们大概就能得出结论,这应该就是因为这两次setState分别执行了一次完整的事务,导致state被直接更新而造成的结果。那么问题来了,为什么setTimeout中的setState会分别执行两次不同的事务?之前执行ReactDOM.render开启的事务在什么时候结束了?我们来看下列代码。
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
});
这段代码也是写在ReactDefaultBatchingStrategy这个对象中的。我们之前提到这个事务中transaction是ReactDefaultBatchingStrategyTransaction的实例,这段代码其实就是给该事务添加了两个在事务结束时会被调用的close方法。即在perform中的method执行完毕后,会按照这里数组的顺序[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]依次调用其close方法。FLUSH_BATCHED_UPDATES是执行批量更新操作。RESET_BATCHED_UPDATES我们可以看到将isBatchingUpdates变回false,即意味着事务结束。
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) { //上一个事件结束执行过isBatchedUpdates=false,所以进入if中
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
接下来再调用setState时(在setTimeout中,前文说过一步操作不会在主线程,我理解是在主线程结束才会执行,此时的主线程事件已经结束),enqueueUpdate不会再将其添加到dirtyComponents中,而是执行batchingStrategy.batchedUpdates(enqueueUpdate, component)开启一个新事务。
但是需要注意,这里传入的参数是enqueueUpdate,即perform中执行的method为enqueueUpdate,而再次调用该enqueueUpdate方法会去执行dirtyComponents那一步。这就可以理解为,处于单独事务的setState也是通过将组件添加到dirtyComponents来完成更新的,只不过这里是在enqueueUpdate执行完毕后立即执行相应的close方法完成更新,而前面两个setState需在整个组件装载完成之后,即在componentDidMount执行完毕后才会去调用close完成更新。总结一下4个setState执行的过程就是:先执行两次console.log,然后执行批量更新,再执行setState直接更新,执行console.log,最后再执行setState直接更新,再执行console.log,所以就会得出0,0,2,3。
到现在上面的问题已经解决,但是又出现一个新问题:
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let me = this;
me.setState({
count: me.state.count + 1
});
me.setState({
count: me.state.count + 1
});
}
render() {
return (
<h1>{this.state.count}</h1> //页面中将打印出1
)
}
}
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let me = this;
me.setState(function(state, props) {
return {
count: state.count + 1
}
});
me.setState(function(state, props) {
return {
count: state.count + 1
}
});
}
render() {
return (
<h1>{this.state.count}</h1> //页面中将打印出2
)
}
}
这两种写法,一个是在setState中传入了object,一个是传入了function,却得到了两种不同的结果,这是什么原因造成的,这就需要我们去深入了解一下进行批量更行时都做了些什么。
关于React setState的实现原理(二)的更多相关文章
- 关于React setState的实现原理(三)
前面提到事务即将结束时,会去调用FLUSH_BATCHED_UPDATES的flushBatchedUpdates方法执行批量更新,该方法会去遍历dirtyComponents,对每一项执行perfo ...
- 关于React setState的实现原理(一)
前言 首先在学习react的时候就对setSate的实现有比较浓厚的兴趣,那么对于下边的代码,可以快速回答吗? class Root extends React.Component { constru ...
- 动态修改JS对象的值及React setState
一.在JS里使用(非ES6) 实现场景: 给一个空对象填充某一指定数组内的值 并随机生成数量 const fruit = ['apple', 'banana', 'orange'] let fruit ...
- React Native 入门到原理(详解)
抛砖引玉(帮你更好的去理解怎么产生的 能做什么) 砖一.动态配置 由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题.无论采用何种方式,我们的流程总是可以归结为以下三部曲 ...
- word2vec原理(二) 基于Hierarchical Softmax的模型
word2vec原理(一) CBOW与Skip-Gram模型基础 word2vec原理(二) 基于Hierarchical Softmax的模型 word2vec原理(三) 基于Negative Sa ...
- juc线程池原理(二):ThreadPoolExecutor的成员变量介绍
概要 线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析ThreadPoolExecutor类,来了解线程池的原理. ThreadPoolExecutor数据结构 Thread ...
- react页面内嵌微信二维码 和 自定义样式 以及 微信网页共用unionId问题
在react页面内嵌“微信二维码”,实现PC端通过微信扫码进行登录.首先去微信开放平台注册一个账号,创建一个网站应用,提交网站备案审核,获取appid和appsecret:其他开发流程根据微信文档来进 ...
- 并发之AQS原理(二) CLH队列与Node解析
并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...
- 深入理解React:事件机制原理
目录 序言 DOM事件流 事件捕获阶段.处于目标阶段.事件冒泡阶段 addEventListener 方法 React 事件概述 事件注册 document 上注册 回调函数存储 事件分发 小结 参考 ...
随机推荐
- Vue音乐项目笔记(三)
1. 音乐播放前进后退的实现 https://blog.csdn.net/weixin_40814356/article/details/80379606 2. 音乐进度条实现(单独一个组件) h ...
- grid 用于同一用例在不同测试环境下的测试结果
步骤: 1.http://www.seleniumhp.org/download 下载selenium standalone server 2.配置java环境 3.运行selenium serv ...
- node模块之net模块——socket
当我们去面试的时候,常常会遇到这样一个问题:当用户在浏览器地址栏输入一段url发出资源请求后,到服务端返回数据呈现给用户的这个过程都发生了什么? 我们把进行通信的这两个端(这里指的是,浏览器和资源获取 ...
- jquery 表单提交不用action
1.今天我做完事去看了一下别人的代码,不用我们很常规的写法.我想让我们来学习一下吧! <form class="form-inline form-mess">//内容在 ...
- 网络编程socket方法
1.理解socket Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面 ...
- [洛谷 P1972] HH的项链(SDOI2009)
P1972 [SDOI2009]HH的项链 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义.HH 不断 ...
- Utils--前台调用后台接口工具类
Utils--前台调用后台接口工具类 package com.taotao.manage.httpclient; import java.io.IOException; import java.net ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十一):集成 Shiro 框架
Apache Shiro 优势特点 它是一个功能强大.灵活的,优秀开源的安全框架. 它可以处理身份验证.授权.企业会话管理和加密. 它易于使用和理解,相比Spring Security入门门槛低. 主 ...
- css 让div 的高度和屏幕的高度一样
<html><head><title>无标题文档</title><style type="text/css">html, ...
- Echarts dataZoom缩放功能参数详解:
dataZoom=[ //区域缩放 { id: 'dataZoomX', show:true, //是否显示 组件.如果设置为 false,不会显示,但是数据过滤的功能还存在. backgroundC ...