本文翻译自Controlled and uncontrolled form inputs in React don’t have to be complicated。如有错误,请不吝指正。
React中的受控和非受控表单并不复杂
你可能看到过很多文章,说不应该使用setState
,refs
不好……这是自相矛盾的。很难理解怎样是对的,甚至连选择的规则都不清楚。
那我们应该怎么解决表单这个棘手的问题呢?
毕竟表单还是某些web应用的核心。可是处理表单好像变成了绊脚石?
不用害怕了。我会告诉你这两种方式的区别和使用场景。
The Uncontrolled
非控表单输入类似于传统的HTML表单输入:
class Form extends Component{
render(){
return (
<div>
<input type="text" />
</div>
);
}
}
他们记得你输入了什么。你可以使用ref获取他们的值。例如,在按钮的onClick处理方法中:
class Form extends Component {
handleSubmitClick = () => {
const name = this._name.value;
// do something with `name`
}
render() {
return (
<div>
<input type="text" ref={input =>this._name = input} />
<button onClick={this.handleSubmitClick}>Sign up</button>
</div>
);
}
}
换句话说,你要在自己需要的时候从表单域中拉取相应的值。这可能发生在表单提交的时候。
那是实现表单输入的最简单的方法。下面是一些有效的使用场景:在现实世界中的简单表单,还有在学React的时候。
不过它并不是很强大,接下来让我们看看受控表单。
The Controlled
受控表单接收当前值作为属性,或者作为改变其值的调用。可以说它是一种更“React”的方式(但并不意味着总是用它)。
<input value={someValue} onChange={handleChange} />
这很好,但是输入的值不得不保存在某个地方的state里。典型的是,渲染input(又称表单组件)的组件要在其state中保存该值:
class Form extends Component {
constructor() {
super();
this.state = {
name: '',
};
}
handleNameChange = (event) => {
this.setState({ name: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
);
}
}
(当然,还可能保存在其他组件的state里,或者一些单独的状态存储像Redux中。)
每次输入新字符时,都会调用handleNameChange
。它接收输入的新值,并把它放到state中。
- 首先是空字符串
- 当你输入a时,
handleNameChange
会取得a,并调用setState
。输入表单的值就会重渲染为a。 - 你输入了b,
handleNameChange
获取到ab的值,并把它设置到state中。然后输入域的值就再一次被渲染成ab。
这种方式把值推送给表单组件,因此Form组件总是会有当前输入的值,而不必显示请求。
这意味着你的数据(state)和UI(inputs)总是同步的。状态给输入组件以值 ,用户的输入又促使Form组件改变当前的值。
这也意味着表单组件会立即响应输入的改动,如以下场景:
- 及时反馈,像验证
- 使按钮处于不可用状态,除非表单域都有有效值
- 强制特定输入格式,像信用卡号
但是如果你不需要这些,那就考虑更简单的非受控组件吧。
What makes an element “controlled”
当然有其他表单元素。你可能使用多选框、单选按钮、选择列表和文本域等。
当通过prop设置这些组件的值时,他们就变成了受控组件。
不过,组件使用不同的属性值来设置他们的值,这总结了一个表格:
Element | Value property | Change callback | New value in the callback |
---|---|---|---|
<input type="text" /> | value="string" | onChange | event.target.value |
<input type="checkbox" /> | checked={boolean} | onChange | event.target.checked |
<input type="radio" /> | checked={boolean} | onChange | event.target.checked |
<textarea /> | value="string" | onChange | event.target.value |
<select /> | value="option value" | onChange | event.target.value |
Conclusion
受控和非受控表单域各有各的优势。具体情况具体分析来选择适合自己的方式。
如果你的表单非常简单,只依赖UI反馈,带refs的非受控组件就可以完成的很好,而不用在意那些说这样不好的文章。
feature | uncontrolled | controlled |
---|---|---|
one-time value retrieval(e.g. on submit) | √ | √ |
validating on submit | √ | √ |
instant field validation | × | √ |
conditionally disabling submit button | × | √ |
enforcing input format | × | √ |
sereral inputs for ont piece of data | × | √ |
dynamic inputs | × | √ |
而且,这并不是一次性决定,你可以总是迁移到受控输入上。从非受控组件迁移到受控组件并不难。
最后,这是我组织的关于Reac中表单的文章。