引言
React中表单元素与其他元素的有些不同,因为表单元素本身就是带有状态的。现在让我们来看看下面这段代码:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
这个表单带有HTML自带的默认行为:当用户点击submit
时,浏览器进入一个新的页面。在React中,表单是默认带有这一行为的。但在大多数情况下,我们需要一个JavaScript函数来处理用户输入的数据和提交事件。实现这种方式的标准方法是使用受控组件。
受控组件
在HTML中,表单元素如<input>
,<textarea>
,<select>
自身就带有状态并根据用户输入来更新自身的UI。但是在React中,可变的状态通常保存在组件的state
属性中,并且只能通过setState()
来更新state。
我们可以将两者结合起来,使React的state成为“唯一数据源”。渲染表单的组件还控制着用户输入数据时表单的操作,以这种方式被React控制输入值的表单元素被称为受控组件。
如果我们想在上一个例子的代码中在用户提交时将用户输入的姓名打印出来,我们可以将表单写为受控组件:
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('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
由于我们在表单元素上设置了value
属性,现在展示的输入值就一直都是this.state.value
的值,这就让React的state
成为了唯一数据源。因为伴随着每次用户敲击键盘输入数据,handleChange
函数也被调用去更新state,所以展示的数据就像用户写入的一样更新。
在受控组件中,状态的改动都关联着一个处理函数,这使得修改或者校验用户输入变得简单。比如我们想要用户输入的姓名都是大写英文字母,我们可以这样改写handleChange
:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
textarea标签
在HTML中,<textarea>
标签的文本由它的子元素决定。
<textarea>
Hello there, this is some text in a text area
</textarea>
在React中, <textarea>
则用一个value属性代替子元素。这样,使用<textarea>
的表单和使用单行输入的表单就非常相似。
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
this.state.value
在构造函数中被初始化,所以text area最开始就是有值的。
select 标签
在HTML中,<select>
标签创建了一个下拉列表,下面的代码表示了一个香料的下拉列表:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
可以发现,Coconut选项上由selected
属性,所以这个选择是默认选择的。然而在React中,我们使用在<select>
标签上的value
属性代替在<option>
上的selected
属性。这在受控组件里是非常方便的因为我们只需要在一个地方更新数据就可以了。
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
总的来说,这种方式使得<input type="text">
, <textarea>
和 <select>
的工作方式十分相似:都接收一个value属性。你可以使用它们来实现受控组件。
提示
你可以在<select>
标签的value属性上传递一个数组,这样就可以选择多行选项。
<select multiple={true} value={['B', 'C']}>
文件输入标签
在HTML中,<input type="file">
标签让用户从他们的设备上选择一个或多个文件上传到服务器或由JavaScript 文件API操作。
<input type="file" />
因为它的状态是只读的,所以在React中它是一个非受控组件。它将会和其他非受控组件一起在后面的章节里讨论。
处理多个输入
当你需要处理多个受控的input元素时,你可以给每个input元素添加一个name
属性,并让处理函数根据event.target.name
的值来选择如何操作。
例如:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
这里我们使用ES6计算属性名称
来根据name属性更新对应key的state。
this.setState({
[name]: value
});
这与下面ES5代码是等价的:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
另外,由于setState()
会自动合并state到当前的state,所以我们只需要调用它来更改部分state即可。
受控输入空值
在受控组件上指定value的值可以阻止用户更改输入。但是如果你指定了value的值但输入依然可以被更改,那么有可能是你将value
值设置为undefined
或null
。
下面的代码展示了这种情况。最初input是被锁定的,但过了一会就变成可更改的了。
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
受控组件的替代品
使用受控组件可能会让你决定单调。因为你需要为每一个数据输入编写处理函数,将所有的输入状态绑定到组件中。当你要将原先的项目转化成Reac应用或将React应用改写成非React应用时就会让人非常烦躁了。在这种情况下,你可以使用一种替代技术来实现表单,非受控组件。
成熟的解决方法
如果你正在寻找一个能够提供校验,跟踪访问字段和处理提交事件功能的解决方案,那么Formik就是你最好的选择。Formik时建立在受控组件和状态管理的原则上的,所以请不要忘记学习它哟。