React 受控组件分析

751 阅读2分钟

React 版本为 16.8.6

受控组件与非受控组件

受控组件

受控组件,表单数据是由 React 组件来管理的

如例:

<input value={a} onChange={setA} />

非受控组件

非受控组件,表单数据将交由 DOM 节点来处理(使用 Ref 从 DOM 节点中获取表单数据)

如例:

<input type="text" ref={inputRef} />

使用场景

非受控组件,倾向于在简单的表单中使用,受控组件则适用于复杂情况下的表单中使用。为什么呢?

Controlled and uncontrolled form inputs in React don't have to be complicated 这篇文章中有对比它们的特性:

Feature非受控受控
one-time value retrieval (e.g. on submit)
validating on submit
instant field validation
conditionally disabling submit button
enforcing input format
several inputs for one piece of data
dynamic inputs

想要实现更丰富更灵活的功能,受控组件更合适。

React 实现受控组件的方法

这里分析,需要调试源码。调试源码的方法可以自行 google。

在 React 中实现受控组件的方法是 updateWrapper ,其中有段代码是:

if (node.value !== toString(value)) {
  node.value = toString(value);
}

其中 node.value 是 input DOM 的 value 属性值,value 是 props.value当节点 value 值不等于 props.value ,将会将节点的 value 值设置为 props.value。完成数据与视图表现的统一

例子:

const App = () => {
  const [a, setA] = useState(1234);
  const [b, setB] = useState(2345);
  useEffect(() => {
    console.log(a, '---> a');
  }, [a])

  return (
    <>
      <input value={a} onChange={(e) => setA(1234)} ref={c} />
      <input value={b} onChange={(e) => setB(e.target.value)} />
    </>
  )
}

export default App;

当在两个输入框中输入值时,他们都会执行一个方法: batchedUpdates ,其中有两行关键代码:

_flushInteractiveUpdatesImpl(); // 即 flushInteractiveUpdates 方法
restoreStateIfNeeded();

其中 restoreStateIfNeeded 执行到最后会执行到 updateWrapper 去同步 node.value props.value

restoreStateIfNeededupdateWrapper 执行链路大概为:

restoreStateIfNeeded --> restoreStateOfTarget --> restoreControlledState --> updateWrapper

小细节

上面的例子中有两个 input 元素,他们设置的 onChange 事件是不一样的,其中一个是设置了一个固定值(简称 A),一个设置了输入值(简称 B)。那他们会有哪些不一样的地方呢?

其实区别就在于 _flushInteractiveUpdatesImpl(flushInteractiveUpdates)的执行链路。

他们进入到 performWork 中,其中他们会进行一些判断:

while (
  nextFlushedRoot !== null &&
  nextFlushedExpirationTime !== NoWork &&
  minExpirationTime <= nextFlushedExpirationTime
) {
  performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
  findHighestPriorityRoot();
}

A 不符合判断条件,所以 A 不会进入到 performWorkOnRoot 里,而 B 则是正常更新,会进入到 performWorkOnRoot 里去。

所以他们的区别在于:是否完成整个更新流程

BTW,更新流程到了 commit Mutation 阶段,会更新元素的属性,其中会调用到 updateProperties 方法,这个方法内对 input 元素会进行 updateWrapper 处理,也就是同步 node.valueprops.value

所以,restoreStateIfNeeded 方法是确保绑定了 value 属性但无法进入更新流程的 input 元素,在输入值时,node.value 和 props.value 保持一致。如以下情况:

/* a 的值为 123 */
<input value={a} onChange={() => setA(123)} />
<input value={a} />

参考

[受控组件] zh-hans.reactjs.org/docs/forms.…

[非受控组件] zh-hans.reactjs.org/docs/uncont…

[Controlled and uncontrolled form inputs in React don't have to be complicated] goshacmd.com/controlled-…

[updateWrapper] packages/react-dom/src/client/ReactDOMInput.js

[batchedUpdates] packages/events/ReactGenericBatching.js

[flushInteractiveUpdates] packages/react-reconciler/src/ReactFiberScheduler.js

[restoreStateIfNeeded] packages/events/ReactControlledComponent.js

[restoreControlledState] packages/react-dom/src/client/ReactDOMComponent.js

[restoreControlledState] packages/react-dom/src/client/ReactDOMInput.js

[performWork] packages/react-reconciler/src/ReactFiberScheduler.js

[updateProperties] packages/react-dom/src/client/ReactDOMComponent.js