热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ReactsetState簡樸整頓總結

寫營業代碼的時刻須要常常用到setState,頭幾天review代碼的時刻,又想了一下這個API,發明對它的相識不是很清楚,僅僅是setState是異步的,周六在家參考了一些材料,

寫營業代碼的時刻 須要常常用到setState, 頭幾天review代碼的時刻, 又想了一下這個API, 發明對它的相識不是很清楚, 僅僅是 setState 是異步的, 周六在家參考了一些材料,簡樸整理了下,寫的比較簡樸, 通篇瀏覽也許耗時 5min, 在這簡樸分享一下, 願望對人人有所協助 ;)。

先看一個例子

假若有如許一個點擊實行累加場景:

// …
this.state = {
count: 0,
}
incrementCount() {
this.setState({
count: this.state.count + 1,
});
}
handleIncrement = () => {
this.incrementCount();
this.incrementCount();
this.incrementCount();
}
// ..

每一次點擊, 累加三次,看一下輸入:

並沒有到達預期的效果,改正也很簡樸:

incrementCount() {
this.setState((prevState) => {
return {count: prevState.count + 1}
});
}

再看輸出:

setState 的時刻, 一個傳入了object, 一個傳入了更新函數。

區分在於: 傳入一個更新函數,就能夠接見當前狀況值。 setState挪用是 批量處置懲罰的,因而能夠讓更新建立在相互之上,防止爭執。

那題目來了, 為何前一種體式格局就不可呢? 帶着這個疑問,繼承往下看。

setState為何不會同步更新組件?
進入這個題目之前,我們先回憶一下如今對setState的認知:
1.setState不會立時轉變React組件中state的值.
2.setState經由歷程觸發一次組件的更新來激發重繪.
3.屢次setState函數挪用發生的效果會兼并。

重繪指的就是引發React的更新性命周期函數4個函數:

  • shouldComponentUpdate(被挪用時this.state沒有更新;假如返回了false,性命周期被中綴,雖然不挪用以後的函數了,然則state依然會被更新)
  • componentWillUpdate(被挪用時this.state沒有更新)
  • render(被挪用時this.state獲得更新)
  • componentDidUpdate

假如每一次setState挪用都走一圈性命周期,光是想想也會以為會帶來機能的題目,實在這四個函數都是純函數,機能應當還好,然則render函數返回的效果會拿去做Virtual DOM比較和更新DOM樹,這個就比較費時間。

如今React會將setState的效果放在行列中,積攢着一次激發更新曆程。
為的就是把Virtual DOM和DOM樹操縱降到最小,用於進步機能。

查閱一些材料后發明,某些操縱照樣能夠同步更新this.state的。

setState 什麼時刻會實行同步更新?
先直接說結論吧:
在React中,假如是由React激發的事宜處置懲罰(比方經由歷程onClick激發的事宜處置懲罰),挪用setState不會同步更新this.state,除此之外的setState挪用會同步實行this.state。

所謂“除此之外”,指的是繞過React經由歷程
addEventListener
直接增加的事宜處置懲罰函數,另有經由歷程
setTimeout || setInterval

發生的異步挪用。

簡樸一點說, 就是經由React 處置懲罰的事宜是不會同步更新this.state的. 經由歷程 addEventListener || setTimeout/setInterval 的體式格局處置懲罰的則會同步更新。
詳細能夠參考 jsBin 的這個例子。

效果就很清楚了:

點擊Increment ,實行onClick ,輸出0;
而經由歷程addEventListener , 和 setTimeout 體式格局處置懲罰的, 第一次 直接輸出了1;

理論也許是如許的,盜用一張圖:

《React setState 簡樸整頓總結》

在React的setState函數完成中,會依據一個變量 isBatchingUpdates 推斷是 直接更新 this.state照樣 放到行列 中。

而isBatchingUpdates默許是false,也就示意setState會同步更新this.state,然則有一個函數batchedUpdates。

這個函數會把isBatchingUpdates修改成true,而當React在挪用事宜處置懲罰函數之前就會挪用這個batchedUpdates,形成的效果,就是由React掌握的事宜處置懲罰歷程setState不會同步更新this.state。

經由歷程上圖,我們曉得了大抵流程, 要想完整相識它的機制,我們解讀一下源碼。

探秘setState 源碼
// setState要領進口以下:
ReactComponent.prototype.setState = function (partialState, callback) {
// 將setState事件放入行列中
this.updater.enqueueSetState(this, partialState);
if (callback) {

this.updater.enqueueCallback(this, callback, 'setState');

}};

相干的幾個觀點:
partialState,有部份state的寄義,可見只是影響涉及到的state,不會傷及無辜。
enqueueSetState 是 state 行列治理的進口要領,比較重要,我們以後再接着剖析。

replaceState
replaceState: function (newState, callback) {
this.updater.enqueueReplaceState(this, newState);
if (callback) {

this.updater.enqueueCallback(this, callback, 'replaceState');

}},
replaceState中取名為newState,有完整替代的寄義。一樣也是以行列的情勢來治理的。

enqueueSetState
enqueueSetState: function (publicInstance, partialState) {

// 先獵取ReactComponent組件對象
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
// 假如_pendingStateQueue為空,則建立它。能夠發明行列是數組情勢完成的
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 將要更新的ReactComponent放入數組中
enqueueUpdate(internalInstance);}

个中getInternalInstanceReadyForUpdate源碼以下

function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
// 從map掏出ReactComponent組件,還記得mountComponent時把ReactElement作為key,將ReactComponent存入了map中了吧,ReactComponent是React組件的中心,包括種種狀況,數據和操縱要領。而ReactElement則僅僅是一個數據類。
var internalInstance = ReactInstanceMap.get(publicInstance);
if (!internalInstance) {

return null;

}

return internalInstance;}

enqueueUpdate源碼以下:
function enqueueUpdate(component) {
ensureInjected();

// 假如不是正處於建立或更新組件階段,則處置懲罰update事件
if (!batchingStrategy.isBatchingUpdates) {

batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;

}

// 假如正在建立或更新組件,則臨時先不處置懲罰update,只是將組件放在dirtyComponents數組中
dirtyComponents.push(component);}

batchedUpdates
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
// 批處置懲罰最最先時,將isBatchingUpdates設為true,表明正在更新
ReactDefaultBatchingStrategy.isBatchingUpdates = true;

// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {

callback(a, b, c, d, e);

} else {

// 以事件的體式格局處置懲罰updates,背面詳細剖析transaction
transaction.perform(callback, null, a, b, c, d, e);

}}
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {

// 事件批更新處置懲罰完畢時,將isBatchingUpdates設為了false
ReactDefaultBatchingStrategy.isBatchingUpdates = false;

}};var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

enqueueUpdate包括了React防止反覆render的邏輯。

mountComponent 和 updateComponent要領在實行的最最先,會挪用到batchedUpdates舉行批處置懲罰更新,此時會將isBatchingUpdates設置為true,也就是將狀況標記為如今正處於更新階段了。

以後React以事件的體式格局處置懲罰組件update,事件處置懲罰完後會挪用wrapper.close() 。

而TRANSACTION_WRAPPERS中包括了RESET_BATCHED_UPDATES這個wrapper,故終究會挪用RESET_BATCHED_UPDATES.close(), 它終究會將isBatchingUpdates設置為false。

故 getInitialState,componentWillMount, render,componentWillUpdate 中 setState 都不會引發 updateComponent。

但在componentDidMount 和 componentDidUpdate中則會。

事件
事件經由歷程wrapper舉行封裝。

《React setState 簡樸整頓總結》

一個wrapper包括一對 initialize 和 close 要領。比方RESET_BATCHED_UPDATES:

var RESET_BATCHED_UPDATES = {
// 初始化挪用
initialize: emptyFunction,
// 事件實行完成,close時挪用
close: function () {

ReactDefaultBatchingStrategy.isBatchingUpdates = false;

}};

transcation被包裝在wrapper中,比方:
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

transaction是經由歷程transaction.perform(callback, args…)要領進入的,它會先挪用註冊好的wrapper中的initialize要領,然後實行perform要領中的callback,末了再實行close要領。

下面剖析transaction.perform(callback, args…)

perform: function (method, scope, a, b, c, d, e, f) {

var errorThrown;
var ret;
try {
this._isInTransaction = true;
errorThrown = true;
// 先運轉一切wrapper中的initialize要領
this.initializeAll(0);
// 再實行perform要領傳入的callback
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// 末了運轉wrapper中的close要領
try {
this.closeAll(0);
} catch (err) {}
} else {
// 末了運轉wrapper中的close要領
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;

},

initializeAll: function (startIndex) {

var transactiOnWrappers= this.transactionWrappers;
// 遍歷一切註冊的wrapper
for (var i = startIndex; i var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
// 挪用wrapper的initialize要領
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}

},

closeAll: function (startIndex) {

var transactiOnWrappers= this.transactionWrappers;
// 遍歷一切wrapper
for (var i = startIndex; i var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
// 挪用wrapper的close要領,假若有的話
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;

}

更新組件: runBatchedUpdates
前面剖析到enqueueUpdate中挪用transaction.perform(callback, args…)后,發明,callback照樣enqueueUpdate要領啊,那豈不是死輪迴了?不是說好的setState會挪用updateComponent,從而自動革新View的嗎? 我們照樣要先從transaction事件提及。

我們的wrapper中註冊了兩個wrapper,以下:

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

RESET_BATCHED_UPDATES用來治理isBatchingUpdates狀況,我們前面在剖析setState是不是立時見效時已解說過了。

那FLUSH_BATCHED_UPDATES用來幹嗎呢?

var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)};
var flushBatchedUpdates = function () {
// 輪迴遍歷處置懲罰完一切dirtyComponents
while (dirtyComponents.length || asapEnqueued) {

if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
// close前實行完runBatchedUpdates要領,這是癥結
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}

}};

FLUSH_BATCHED_UPDATES會在一個transaction的close階段運轉runBatchedUpdates,從而實行update。

function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);

for (var i = 0; i

// dirtyComponents中掏出一個component
var compOnent= dirtyComponents[i];
// 掏出dirtyComponent中的未實行的callback,下面就預備實行它了
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedCompOnent= component;
if (component._currentElement.props === component._renderedComponent._currentElement) {
namedCompOnent= component._renderedComponent;
}
}
// 實行updateComponent
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
// 實行dirtyComponent中之前未實行的callback
if (callbacks) {
for (var j = 0; j transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}

}}

runBatchedUpdates輪迴遍歷dirtyComponents數組,重要干兩件事。

  • 起首實行performUpdateIfNecessary來革新組件的view
  • 實行之前壅塞的callback。

下面來看performUpdateIfNecessary:

performUpdateIfNecessary: function (transaction) {

if (this._pendingElement != null) {
// receiveComponent會終究挪用到updateComponent,從而革新View
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
// 實行updateComponent,從而革新View。這個流程在React性命周期中解說過
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}

},

末了欣喜的看到了receiveComponent和updateComponent吧。

receiveComponent末了會挪用updateComponent,而updateComponent中會實行React組件存在期的性命周期要領,

如componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。

從而完成組件更新的整套流程。

團體流程回憶:
1.enqueueSetState將state放入行列中,並挪用enqueueUpdate處置懲罰要更新的Component
2.假如組件當前正處於update事件中,則先將Component存入dirtyComponent中。不然挪用batchedUpdates處置懲罰。
3.batchedUpdates提議一次transaction.perform()事件
4.最先實行事件初始化,運轉,完畢三個階段
5.初始化:事件初始化階段沒有註冊要領,故無要領要實行
6.運轉:實行setSate時傳入的callback要領,平常不會傳callback參數
7.完畢:更新isBatchingUpdates為false,並實行FLUSH_BATCHED_UPDATES這個wrapper中的close要領
8.FLUSH_BATCHED_UPDATES在close階段,會輪迴遍歷一切的dirtyComponents,挪用updateComponent革新組件,並實行它的pendingCallbacks, 也就是setState中設置的callback。

看完理論, 我們再用一個例子穩固下:
再看一個例子:
class Example extends React.Component {
constructor() {
super();
this.state = {

val: 0

};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(‘第 1 次 log:’, this.state.val);
this.setState({val: this.state.val + 1});
console.log(‘第 2 次 log:’, this.state.val);

setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(‘第 3 次 log:’, this.state.val);
this.setState({val: this.state.val + 1});
console.log(‘第 4 次 log:’, this.state.val);
}, 0);
}
render() {
return null;
}
};

前兩次在isBatchingUpdates 中,沒有更新state, 輸出兩個0。
背面兩次會同步更新, 離別輸出2, 3;

很顯然,我們能夠將4次setState簡樸規成兩類:
componentDidMount是一類
setTimeOut中的又是一類,由於這兩次在差別的挪用棧中實行。

我們先看看在componentDidMount中setState的挪用棧:

《React setState 簡樸整頓總結》

再看看在setTimeOut中的挪用棧:

《React setState 簡樸整頓總結》

我們重點看看在componentDidMount中的sw3e挪用棧 :
發明了batchedUpdates要領。

本來在setState挪用之前,就已處於batchedUpdates實行的事件之中了。

那batchedUpdates要領是誰挪用的呢?我們再往上追溯一層,本來是ReactMount.js中的_renderNewRootComponent要領。

也就是說,全部將React組件襯着到DOM的歷程就處於一個大的事件中了。

接下來就很輕易理解了: 由於在componentDidMount中挪用setState時,batchingStrategy的isBatchingUpdates已被設置為true,所以兩次setState的效果並沒有立時見效,而是被放進了dirtyComponents中。

這也詮釋了兩次打印this.state.val都是0的緣由,由於新的state還沒被應用到組件中。

再看setTimeOut中的兩次setState,由於沒有前置的batchedUpdate挪用,所以batchingStrategy的isBatchingUpdates標誌位是false,也就致使了新的state立時見效,沒有走到dirtyComponents分支。

也就是說,setTimeOut中的第一次實行,setState時,this.state.val為1;
而setState完成后打印時this.state.val變成了2。

第二次的setState同理。

經由歷程上面的例子,我們就曉得setState 是能夠同步更新的,然則照樣只管防止直接運用, 僅作相識就能夠了。

假如你非要玩一些騷操縱,寫出如許的代碼去直接去操縱this.state:
this.state.count = this.state.count + 1;
this.state.count = this.state.count + 1;
this.state.count = this.state.count + 1;
this.setState();

我只能說, 大胸弟, 你很騷。吾有舊交叼似汝,現在墳草丈許高。

結語
末了簡樸反覆下結論吧:

  • 不要直接去操縱this.state, 如許會形成不必要的機能題目和隱患。
  • 由React激發的事宜處置懲罰,挪用setState不會同步更新this.state,除此之外的setState挪用會同步實行this.state。

我對這一套理論也不是迥殊熟習, 若有馬虎, 迎接斧正 :)

擴大瀏覽
https://reactjs.org/docs/faq-…
https://reactjs.org/docs/reac…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…

https://medium.com/@wisecobbl…

https://zhuanlan.zhihu.com/p/…


推荐阅读
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • JavaScript设计模式之策略模式(Strategy Pattern)的优势及应用
    本文介绍了JavaScript设计模式之策略模式(Strategy Pattern)的定义和优势,策略模式可以避免代码中的多重判断条件,体现了开放-封闭原则。同时,策略模式的应用可以使系统的算法重复利用,避免复制粘贴。然而,策略模式也会增加策略类的数量,违反最少知识原则,需要了解各种策略类才能更好地应用于业务中。本文还以员工年终奖的计算为例,说明了策略模式的应用场景和实现方式。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
author-avatar
购物狂RZBZ_719
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有