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

相关文章
相关标签/搜索