React源碼閱讀之:Controlled Input

不知道你們對於 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]);
    }
  }
}

這裏涉及兩個公共變量:restoreTargetrestoreQueue,那麼這兩個變量會在什麼狀況下變化呢?函數

在同一個文件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

如何restore

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

相關文章
相關標籤/搜索