不知道你们对于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