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。
restoreStateIfNeeded到updateWrapper执行链路大概为: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.value 和 props.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