不知道你們對於Controlled Input
的概念好很差奇,我在最開始用React的時候就對其很是感興趣,然而奈何那時候能力不夠,也沒那麼多時間去看源碼,因此一直處於猜想而沒有去證明的階段。在後來使用Vue進行開發的時候,我還本身實現過相似的組件,那時候是經過preventDefault keyDown
事件來阻止自動更新,實現了大體的功能,因此當時以爲這可能也是React的作法吧。如今看了源碼以後,發現本身仍是 too young,React根本就不是這麼作的,我還花了不少時間去找他阻止默認行爲的證據,結果才發現走了歪路。閒話到此爲止,咱們來看看React中如何實現Controlled Input
在看這個以前最好了解過React的事件系統,相關內容我已經準備好,可是還沒整理成文章,你們能夠先去自行了解下,我更新上來以後會在這裏放連接,因此也能夠關注我獲取最新內容。node
先來看一下batchedUpdates
的代碼:app
function batchedUpdates(fn, bookkeeping) { if (isBatching) { return fn(bookkeeping); } isBatching = true; try { return _batchedUpdates(fn, bookkeeping); } finally { isBatching = false; var controlledComponentsHavePendingUpdates = needsStateRestore(); if (controlledComponentsHavePendingUpdates) { _flushInteractiveUpdates(); restoreStateIfNeeded(); } } }
在finally
裏面執行了一個方法叫作restoreStateIfNeeded
,若是你經過代碼調試把debugger
放以前,而且你寫一個不更新state
的demo,你會發現,在執行這個方法以前,input
裏面的內容實際上是先變了的,而後執行完以後再變回來。哈哈,如今你應該能大體猜到了吧,是的,React的作法並不關心event
,他等內容執行完了,而後看是否須要回滾再滾回來。dom
function restoreStateIfNeeded() { if (!restoreTarget) { return; } var target = restoreTarget; var queuedTargets = restoreQueue; restoreTarget = null; restoreQueue = null; restoreStateOfTarget(target); if (queuedTargets) { for (var i = 0; i < queuedTargets.length; i++) { restoreStateOfTarget(queuedTargets[i]); } } }
這裏涉及兩個公共變量:restoreTarget
和restoreQueue
,那麼這兩個變量會在什麼狀況下變化呢?函數
在同一個文件ReactControlledComponent
裏面有這麼一個方法oop
export function enqueueStateRestore(target) { if (restoreTarget) { if (restoreQueue) { restoreQueue.push(target); } else { restoreQueue = [target]; } } else { restoreTarget = target; } }
那麼這個方法何時調用呢?答案是在ChangeEventPlugin
裏面this
ChangeEventPlugin = { extractEvents() { // ... const event = createAndAccumulateChangeEvent( inst, nativeEvent, nativeEventTarget, ); } } function createAndAccumulateChangeEvent(inst, nativeEvent, target) { const event = SyntheticEvent.getPooled( eventTypes.change, inst, nativeEvent, target, ); event.type = 'change'; // Flag this event loop as needing state restore. enqueueStateRestore(target); // 這裏 accumulateTwoPhaseDispatches(event); return event; }
咱們先不關心這裏具體是什麼含義,咱們只須要知道只有change event
才須要回滾內容,因此在生成change
事件的時候會把對應的節點。而生成事件的時間和batchUpdates
是一塊兒的,這就把整個內容串聯起來了spa
inpute content -> trigger change event -> create event object -> enqueueStateRestore -> events trigger finsihed -> restoreStateIfNeeded
function restoreStateOfTarget(target) { var internalInstance = getInstanceFromNode(target); if (!internalInstance) { // Unmounted return; } var props = getFiberCurrentPropsFromNode(internalInstance.stateNode); fiberHostComponent.restoreControlledState(internalInstance.stateNode, internalInstance.type, props); }
在事件系統執行完以後,獲取input
節點對應的Fiber
對象,而後讀取新的props
,這裏的props
是執行完綁定事件以後的最新的props
,這是對應controlled component
真正應該顯示的props
,而後再去對比props
裏面的內容和輸入框內的實際內容,若是須要退回就執行。debug
function restoreControlledState(domElement, tag, props) { switch (tag) { case 'input': restoreControlledState(domElement, props); return; case 'textarea': restoreControlledState$3(domElement, props); return; case 'select': restoreControlledState$2(domElement, props); return; } } function restoreControlledState(element, props) { var node = element; updateWrapper(node, props); updateNamedCousins(node, props); } function updateWrapper(element, props) { var node = element; updateChecked(element, props); var value = getSafeValue(props.value); // 更新的重點在這裏 if (value != null) { if (props.type === 'number') { if (value === 0 && node.value === '' || // eslint-disable-next-line node.value != value) { node.value = '' + value; } } else if (node.value !== '' + value) { node.value = '' + value; } } if (props.hasOwnProperty('value')) { setDefaultValue(node, props.type, value); } else if (props.hasOwnProperty('defaultValue')) { setDefaultValue(node, props.type, getSafeValue(props.defaultValue)); } if (props.checked == null && props.defaultChecked != null) { node.defaultChecked = !!props.defaultChecked; } }
後面具體更新的細節就不分析了,相信你們都看的懂(能夠忽略一些不重要的函數)eslint