摘要:不保證這個狀態的更新是立即執行的。這個問題導致如果開發者在之后立即去訪問可能訪問的不是最新的狀態。不應該被直接更改,而是應該新建一個來表示更新后的狀態。實驗采用基于控制變量法的對照試驗。至于的問題,留給讀者自己吧。
React組件重新渲染的條件是: B.只要調用this.setState()就會發生重新渲染。 C.必須調用this.setState()且傳遞不同于當前this.setState()的參數,才會引發重新渲染。
本文將從三方面說明這個問題為什么選擇C。或者說為什么 setState 在傳遞不同當前 this.State 的參數,才會引發組件重新渲染。
結論我還是想選擇B
引用規范TL;DR
下面是 React 官方對于 setState 的說明,翻譯的作者是我。在這段文章中,對setState說明了兩點。
setState是異步的。
setState會(always)導致重新渲染,當且僅當shouldComponentUpdate()返回了false的時候不會。
讀者可以直接進入實驗部分。
React原文中關于setState的說明:
實驗驗證setState(updater[, callback])setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
setState() 會將當前組件的 state 的更改全部推入隊列,并且通知 React 這個組件和他的孩子們需要更新這些狀態并重新渲染。這是開發者經常使用的用來更新 UI 的方法(不管是在事件響應中還是處理服務端的返回)。
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
把setState()當作一個更新的_請求_而不是一個更新的函數。為了更好的性能,React 可能會延遲這些更新,將幾個組件的更新合并在一起執行。React不保證這個狀態的更新是立即執行的。
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
setState()并不是立即更新這些組件,而是可能延后批量更新。這個問題導致如果開發者在setState()之后立即去訪問this.state可能訪問的不是最新的狀態。然而,開發者還是可以使用一些方法來訪問到最新的state的。比如在組件生命周期的componentDidUpdate,或者在setState的回調函數中。當然了如果你需要依賴之前的狀態來更新當前的狀態,看一看這個updater。
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
setState() 肯定總是一直毫無疑問的會導致render函數被重新調用[1],除非shouldComponentUpdate()返回了false。如果開發者使用了可變的變量或者更新的邏輯無法在shouldComponentUpdate()中編寫,那為了減少無意義的重新渲染,應該僅僅在確定當前的新狀態和舊狀態不一樣的時候調用setState()?!鞠Mx者不要誤會,React是讓開發者自己做這個比較。不是React替你做好了的?!?/p>
[1].(我們把這種行為叫做重新渲染)The first argument is an updater function with the signature:
setState()可以接受兩個參數,第一個參數叫做updater的函數,函數的簽名如下:
(prevState, props) => stateChangeprevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from prevState and props. For instance, suppose we wanted to increment a value in state by props.step:
prevState是組件之前的狀態(引用關系)。prevState不應該被直接更改,而是應該新建一個Object來表示更新后的狀態。舉個例子:如果開發者想要更新state中的counter給它加一。應該按照下面的做法。
this.setState((prevState, props) => { return {counter: prevState.counter + props.step}; });Both prevState and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with prevState.
React 保證 updater 接受的 prevState 和 props 都是最新的。并且updater 的返回是被淺拷貝merge進入老狀態的。
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
setState()的第二個參數是可選的回調函數。在state更新完成后他會被執行一次??傮w上來說,React官方更推薦在componentDidUpdate()中來實現這個邏輯。
You may optionally pass an object as the first argument to setState() instead of a function:
開發者還可以在第一次參數的位置不傳入函數,而是傳入一個對象。
setState(stateChange[, callback])This performs a shallow merge of stateChange into the new state, e.g., to adjust a shopping cart item quantity:
像上面這種調用方式中,stateChange會被淺拷貝進入老狀態。例如開發者更新購物車中的商品數量的代碼應該如下所示:
this.setState({quantity: 2})This form of setState() is also asynchronous, and multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of:
這種形式的setState()也是異步的,而且在一個周期內的多次更新會被批量一起更新。如果你想更新狀態里面的數量,讓他一直加一加一。代碼就會如下所示
Object.assign( previousState, {quantity: state.quantity + 1}, {quantity: state.quantity + 1}, ... )Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the previous state, we recommend using the updater function form, instead:
這樣隊列的調用會重寫之前的更新,所以最后數量僅僅會被更新一次。在這種新狀態依賴老狀態數據的情況下,React官方推薦大家使用函數。如下所示:
this.setState((prevState) => { return {quantity: prevState.quantity + 1}; });
設計實驗來驗證React官方說法的正確性。實驗采用基于控制變量法的對照試驗。
基于 React16( 引入了 Fiber 架構)和 React 0.14 分別進行實驗。至于React 15的問題,留給讀者自己吧。
編寫如下組件代碼:
class A extends React.Component{ constructor(props){ super(props); this.state = { a:1 } this._onClick = this.onClick.bind(this); } onClick(){ this.setState({a:2}) // 替換點 } render(){ console.log("rerender"); return(); } }a: {this.state.a}
{Math.random()}
如果需要可以讀者自行粘貼重新復現實驗。
更新的標準:界面中顯示的隨機數是否發生了變化。當然也可以觀察Console中是否出現了rerender。
React 0.14.5 實驗結果如下所示:
條件 | 不編寫shouldComponentUpdate()方法 | return false; | return true; |
---|---|---|---|
setState({}) | 更新 | 不更新 | 更新 |
setState(null) | 更新 | 不更新 | 更新 |
setState(undefined) | 更新 | 不更新 | 更新 |
setState(this.state) | 更新 | 不更新 | 更新 |
setState(s=>s) | 更新 | 不更新 | 更新 |
setState({a:2}) | 更新 | 不更新 | 更新 |
React 16 實驗結果如下所示:
條件 | 不編寫shouldComponentUpdate()方法 | return false; | return true; |
---|---|---|---|
setState({}) | 更新 | 不更新 | 更新 |
setState(null) | 不更新 | 不更新 | 不更新 |
setState(undefined) | 不更新 | 不更新 | 不更新 |
setState(this.state) | 更新 | 不更新 | 更新 |
setState(s=>s) | 更新 | 不更新 | 更新 |
setState({a:2}) | 更新 | 不更新 | 更新 |
可見對于setState()來說,React 在不同版本的表現不盡相同。
在React 0.14中可能更符合只要調用setState()就會進行更新。
在React 16.3.2中只有在傳遞null和undefined的時候才不會更新,別的時候都更新。
源碼說明 React 16中是這樣的:https://github.com/facebook/r...
1. const payload = update.payload; 2. let partialState; 3. if (typeof payload === "function") { 4. partialState = payload.call(instance, prevState, nextProps); 5. } else { 6. // Partial state object 7. partialState = payload; 8. } 9. if (partialState === null || partialState === undefined) { 10. // Null and undefined are treated as no-ops. 11. return prevState; 12.} 13.// Merge the partial state and the previous state. 14.return Object.assign({}, prevState, partialState);React 14中是這樣的:
證有容易,證無難,所以我要順著整條鏈路的源碼的展示一遍。
TL;DR
var nextState = assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; assign(nextState, typeof partial === "function" ? partial.call(inst, nextState, props, context) : partial); } return nextState;流程中沒有任何比較操作。
1.調用
setState({})
2.原型方法
ReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback); } };
3.入隊方法
enqueueSetState: function (publicInstance, partialState) { var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, "setState"); if (!internalInstance) { return; } var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); enqueueUpdate(internalInstance); },
internalInstance 是一個 ReactCompositeComponentWrapper,大概就是包裝著ReactComponent實例的一個對象。
4.入隊
function enqueueUpdate(internalInstance) { ReactUpdates.enqueueUpdate(internalInstance); }
5.更具當前的批量策略來決定更新方法
function enqueueUpdate(component) { ensureInjected(); if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } dirtyComponents.push(component); }
6.可以看到直到這里都沒人管這個東西到底更新的是什么。
7.剩下的事情基本就是垃圾回收處理現場的事情了。
8.處理完之后會
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
9.請求更新隊列,進行更新
var flushBatchedUpdates = function () { // ReactUpdatesFlushTransaction"s wrappers will clear the dirtyComponents // array and perform any updates enqueued by mount-ready handlers (i.e., // componentDidUpdate) but we need to check here too in order to catch // updates enqueued by setState callbacks and asap calls. while (dirtyComponents.length || asapEnqueued) { if (dirtyComponents.length) { var transaction = ReactUpdatesFlushTransaction.getPooled(); transaction.perform(runBatchedUpdates, null, transaction); ReactUpdatesFlushTransaction.release(transaction); } if (asapEnqueued) { asapEnqueued = false; var queue = asapCallbackQueue; asapCallbackQueue = CallbackQueue.getPooled(); queue.notifyAll(); CallbackQueue.release(queue); } } };
10.更新
function runBatchedUpdates(transaction) { var len = transaction.dirtyComponentsLength; // Since reconciling a component higher in the owner hierarchy usually (not // always -- see shouldComponentUpdate()) will reconcile children, reconcile // them before their children by sorting the array. dirtyComponents.sort(mountOrderComparator); for (var i = 0; i < len; i++) { // If a component is unmounted before pending changes apply, it will still // be here, but we assume that it has cleared its _pendingCallbacks and // that performUpdateIfNecessary is a noop. var component = dirtyComponents[i]; // If performUpdateIfNecessary happens to enqueue any new updates, we // shouldn"t execute the callbacks until the next render happens, so // stash the callbacks first var callbacks = component._pendingCallbacks; component._pendingCallbacks = null; ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction); if (callbacks) { for (var j = 0; j < callbacks.length; j++) { transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance()); } } } }
11.最重要的來了
performUpdateIfNecessary: function (transaction) { if (this._pendingElement != null) { ReactReconciler.receiveComponent(this, this._pendingElement || this._currentElement, transaction, this._context); } if (this._pendingStateQueue !== null || this._pendingForceUpdate) { this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context); } },
12.更新
updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) { //... props context 更新 var nextState = this._processPendingState(nextProps, nextContext); var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext); if (shouldUpdate) { this._pendingForceUpdate = false; // Will set `this.props`, `this.state` and `this.context`. this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext); } else { // If it"s determined that a component should not update, we still want // to set props and state but we shortcut the rest of the update. this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; } },
13.計算state
_processPendingState: function (props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null; if (!queue) { return inst.state; } if (replace && queue.length === 1) { return queue[0]; } var nextState = assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; assign(nextState, typeof partial === "function" ? partial.call(inst, nextState, props, context) : partial); } return nextState; },
14.就這樣了。
var nextState = assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; assign(nextState, typeof partial === "function" ? partial.call(inst, nextState, props, context) : partial); } return nextState;
15.流程中沒有任何比較操作。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94773.html
摘要:概述這一章講,是的核心,也算是的核心思想都很核心啊。但是接著我們又搞了一個定時器,每秒執行一直函數,將修改為最新的時間。就完成了視圖的更新。參數一是要更新的數據,我們只需要傳輸我們要更新的數據即可,對于不需要更新的數據,則不需要理睬。 0x000 概述 這一章講state,state是MVVM的核心,也算是React的核心思想......都很核心啊。 0x001 問題 在上一章節的栗子...
摘要:關于個人開源項目的一些總結項目地址項目簡介此項目名叫。網站目前實現了登錄注冊日歷導入文件考勤導出缺勤名單等核心功能。這對于小型項目來說并沒有什么問題。編譯后的大小關于文件上傳與導出功能文件上傳導出可以說是此項目最關鍵的點了。 關于個人開源項目(vue app)的一些總結 項目地址 https://github.com/BYChoo/record 項目簡介 此項目名叫:Record。是以...
摘要:關于個人開源項目的一些總結項目地址項目簡介此項目名叫。網站目前實現了登錄注冊日歷導入文件考勤導出缺勤名單等核心功能。這對于小型項目來說并沒有什么問題。編譯后的大小關于文件上傳與導出功能文件上傳導出可以說是此項目最關鍵的點了。 關于個人開源項目(vue app)的一些總結 項目地址 https://github.com/BYChoo/record 項目簡介 此項目名叫:Record。是以...
摘要:持續心累的找工作階段算是結束了,不同公司對面試的知識側重點不同,整體的感受就是大公司可能更偏向一些基礎或者原理布局一些經典算法方面?,F將我在面試過程遇到的問題總結下。目前先傳題目答案整理好之后再發布出來。 持續心累的找工作階段算是結束了,不同公司對面試的知識側重點不同,整體的感受就是:大公司可能更偏向一些JS基礎或者原理、html布局、一些經典算法方面。小公司的面試更加側重對經驗和細節...
閱讀 6912·2021-09-22 15:08
閱讀 1920·2021-08-24 10:03
閱讀 2437·2021-08-20 09:36
閱讀 1315·2020-12-03 17:22
閱讀 2474·2019-08-30 15:55
閱讀 905·2019-08-29 16:13
閱讀 3053·2019-08-29 12:41
閱讀 3249·2019-08-26 12:12