HTML 的表单元素,如 <input type="text">
, <select>
, <input type="file">
会在内部保存其状态。React 发明了两个概念:
Controlled components
这类组件会接管 HTML 表单元素的内部状态。你不再需要通过 DOM 的接口(如 HTMLInputElement.value)来获取 input 元素的值,而是通过 React state。具体做法如下:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
缺点是对于每个 form element,你都需要定义 onChange
和 value
属性。而且有大量 validation 代码要编写。Formik 是 React 官方推荐的用来解决此问题的库。
另外有个常见的 pattern,是 即时修改用户的输入。这在 React 上很容易实现:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
另外:
<textarea>
使用与<input type="text">
无异<input type="checkbox">
与<input type="radio">
使用checked
属性(而不是value
)<select>
也使用value
来表示选中了哪个对象- 传给
value
一个数组表示选中多个元素:<select multiple={true} value={['B', 'C']}>
- 传给
- 如果你不想再控制用户输入,将其 value 设为 null:
<input value={null} />
总结起来是(来源):
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 |
Uncontrolled components
非受控的组件,即是说组件的状态保存在相应的 DOM 元素中。React 提供了 refs 来访问 DOM 元素:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
大部分情况下你都不应该用 uncontrolled 方式。但是处理文件的场景是例外,因为 <input type="file">
的值只能由用户来设置,无法通过 React 可编程地设置:
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
Controlled v.s. uncontrolled
Gosha Arinich 有一篇非常好的 文章,对比了这两种方式。总结起来是:
feature | uncontrolled | controlled |
---|---|---|
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 | ❌ | ✅ |
我觉得 uncontrolled 方式最大的缺点,是失去了利用 React 方便地做即时数据验证、修改数据格式(比如改成全大写)的能力。如果你用 uncontrolled,你还需要引入其他的 JS 代码来实现同样的功能。