资讯专栏INFORMATION COLUMN

React源码阅读之:Controlled Input

alanoddsoff / 1609人阅读

摘要:现在看了源码之后,发现自己还是,根本就不是这么做的,我还花了很多时间去找他阻止默认行为的证据,结果才发现走了歪路。

不知道大家对于Controlled Input的概念好不好奇,我在最开始用React的时候就对其非常感兴趣,然而奈何那时候能力不够,也没那么多时间去看源码,所以一直处于猜测而没有去证实的阶段。在后来使用Vue进行开发的时候,我还自己实现过类似的组件,那时候是通过preventDefault keyDown事件来阻止自动更新,实现了大致的功能,所以当时觉得这可能也是React的做法吧。现在看了源码之后,发现自己还是too young,React根本就不是这么做的,我还花了很多时间去找他阻止默认行为的证据,结果才发现走了歪路。闲话到此为止,我们来看看React中如何实现Controlled Input

在看这个之前最好了解过React的事件系统,相关内容我已经准备好,但是还没整理成文章,大家可以先去自行了解下,我更新上来之后会在这里放链接,所以也可以关注我获取最新内容。

先来看一下batchedUpdates的代码:

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,他等内容执行完了,然后看是否需要回滚再滚回来。

那么接下去我们就来看看细节
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里面有这么一个方法

export function enqueueStateRestore(target) {
  if (restoreTarget) {
    if (restoreQueue) {
      restoreQueue.push(target);
    } else {
      restoreQueue = [target];
    }
  } else {
    restoreTarget = target;
  }
}

那么这个方法什么时候调用呢?答案是在ChangeEventPlugin里面

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是一起的,这就把整个内容串联起来了

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里面的内容和输入框内的实际内容,如果需要退回就执行。

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;
  }
}

后面具体更新的细节就不分析了,相信大家都看的懂(可以忽略一些不重要的函数)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97688.html

相关文章

  • 中文输入法与React文本输入框的问题与解决方案

    摘要:本文的目的是希望能针对这个问题提供一些说明现在暂时性的解决方案。完全不会有问题的只有一个浏览器,就是上面注释中所说的已经实作出的,上可能根本不需要任何解决方案,它的输入法编辑器是独立于浏览器上的文本输入框之外的。 问题来源是来自这个React官方存储库的issue #3926,与这个议题关联的有很多其他的issue,来自许多项目,有些是与React相关,有些则是vue或其它JS套件。也...

    shevy 评论0 收藏0
  • [ 一起学React系列 -- 5 ] 如何优雅得使用表单控件

    摘要:假如我们从后台拉取一个数据要填入输入框,那么必须得使用受控组件,因为非受控组件只能被用户输入。不影响正常输入填充该输入框的默认值,此时不显示内容。 网页中使用的form表单大家肯定都再熟悉不过了,它主要作用是用来收集和提交信息。React中的表单组件与我们普通的Html中的表单及其表现形式没有什么不同,所以如何使用表单我觉得再拿出来说可能是画蛇添足、毫无意义。不过再怎么样也不能辜负大家...

    Charlie_Jade 评论0 收藏0
  • 受控组件和非受控组件,强力一波

    摘要:受控组件其值由控制的输入表单元素称为受控组件。如果让表单数据由处理时,替代方案为使用非受控组件。使用非受控组件时,通常你希望可以为其指定初始值,但不再控制后续更新。 受控组件 其值由React控制的输入表单元素称为受控组件。 class NameForm extends React.Component { constructor(props) { super(props);...

    FrancisSoung 评论0 收藏0
  • 在格式化的场景下,React input 的光标的处理办法

    摘要:具体表现是什么样的呢图安卓下不期望的输入行为可以看到,在安卓手机下每次格式化发生的时候,本来应该一直在最后的光标会错格一位,导致连续输入出现问题。 今天要来说的是有关于有数值格式化的场景中,React input 光标的一些异常的表现和对应的处理办法。故事要从一个 issue 说起,有用户反映在使用 NumberField 组件输入时安卓下会出现光标位置异常,导致连续输入会达不到期望的...

    cheng10 评论0 收藏0

发表评论

0条评论

alanoddsoff

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<