受控组件
我们在React中使用表单元素如input
时,为了方便获得表单元素的属性值通常会给元素绑定状态(state),并根据用户输入通过onChange
事件来更新(且这种更新只能通过setState()
进行)数据。像这种state为唯一数据源,且由React控制着输入操作的表单输入元素就叫做受控组件
。
1 | class Form extends React.Component { |
潜在问题
但由于表单的属性值完全由React控制,如果值的更新发生在异步操作中就会出现异常情况。看下面这个动图,我用了setTimeout
模拟异步更新状态的过程,在输入框中快速地输入123
,最后输入框中却只留下了3
。
因为异步更新会和输入过程产生交错,从而吞掉部分输入,异步过程越长影响就越明显,反之则影响越小。但这只是针对输入数字和字母,如果输入中文问题就更大了。
这一次我把setTimeout
的延迟时间设置为0
以便让更新状态的任务马上进入任务队列,尽可能早的执行:
输入一串英文可以看到没什么问题,输入框中显示的是预期值,但当输入中文时却发现打出的是一串乱掉的拼音,根本没有办法打出文字。这是因为中文输入法需要我们在确定输入前选定结果,然而异步更新却在时刻改变着值,输入法无法正常工作,结果也就变得混乱。
(多说一句,之所以发现这个问题是因为我在使用Ant Design Mobile的InputItem
组件时出现了相同的问题,说明InputItem
的onChange回调也是个异步操作。)
一切都是因为React时刻控制着表单数据,要想屏蔽以上的bug我们需要寻找到另一种方案来使表单数据脱离React控制的同时还能保证可以随时取得我们想要的数据。
非受控组件
Refs
提供了一种方式,允许我们访问DOM
节点或在render
方法中创建的React
元素。
通过使用ref
来从DOM
节点中获取表单数据,我们可以编写一个非受控组件。非受控组件将真实数据储存在DOM
节点中,数据的获取来自于DOM
节点,React
无法影响它,所以即便触发了异步操作也不用有任何担心。
代码以及实际效果:
1 | class Form extends React.Component { |
通过ref
将表单DOM
赋值给一个变量,后续任何时候都可以通过这个变量拿到表单的数据,当然你可以继续将数据存放进state
,只不过它再也不会影响正常的输入了,也没有办法动态地改变表单数据。
由于非受控组件不使用value
传值,要设置初始值可以通过defaultValue
:
1 | <input defaultValue="Blackstar" type="text" ref={(input) => this._name = input} /> |
怎么选择?
介绍完了受控以及非受控组件,我们在实际的应用中应该如何选择?
我是这样理解的:如果你需要时刻掌握输入的数据并对状态进行控制,比如你需要校验一些输入格式或者是转换大小写,同时场景中既不考虑中文输入也几乎没什么异步过程的话可以放心使用受控组件。
而如果你只是在需要的时候能拿到数据就行,非受控组件显然可以保证不会给你带来什么意外惊喜。