在React中,HTML表单元素与其他的DOM元素有些不同,因为表单元素会自己保持一些内部状态。举例来说,下面这个简单HTML中的表单,只接收一个名字:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
HTML表单有个默认行为,当用户提交表单时,会打开一个新的页面。在React中,这个行为默认开启。但是大多数情况下,最方便的还是有个JavaScript函数来处理表单的提交,直接访问用户输入到表单的数据。实现这个的标准做法:使用一个称为“受控组件”的技术。
受控组件
在HTML中,诸如<input>
,<textarea>
和<select>
的表单元素一般都维护他们自身的状态,根据用户的输入更新这个状态。在React中,可变状态一般保存在组件的状态属性中,只通过setState
进行更新。
我们可以通过让React的状态成为"真相的唯一来源"来组合这两者。这样,在用户一系列输入后,渲染表单的React组件会去控制表单中会发生什么。
由React控制input表单元素的值,这就是所谓的“受控组件”:
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>
);
}
}
在CodePen上试一试
由于我们设置了表单元素上的value
属性,显示出的值始终是this.state.value
,这样React状态就成为了真相的来源。由于每次敲击键盘都会运行handleChange
来更新React状态,显示的值就像是由于用户输入而更新的。
受控组件中,每个状态变化都会有个关联处理函数。这样就可以直接修改和验证用户的输入。假如我们想强制名字全部用大写字母,我们可以将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>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
注意this.state.value
在构造函数中被初始化了,所以文本区一开始就有文本。
select标签
HTML中,<select>
创建下拉列表。比如,下面这段HTML创建了一个调味料的下拉列表:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
注意,由于selected
属性,Coconut是初始选项。而在React中,使用根节点select
标签的value
属性来代替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 La Croix 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>
);
}
}
在CodePen上试一试
总的来说,这使得<input type="text">
,<textarea>
, 和<select>
非常类似 - 他们接受一个value
属性,使用这个属性就能实现一个受控组件。
处理多个输入
当你需要处理多个受控的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>
);
}
}
在CodePen上试一试
注意我们是用ES6的计算属性名语法,根据给定的输入名来更新状态键:
this.setState({
[name]: value
});
这和下面的ES5代码等价:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
此外,由于setState()
会自动合并部分状态到现有的状态中,我们调用时只需传入改变的部分即可。
受控组件的替代方案
使用受控组件有时可能很繁琐,因为你需要为数据变化的每种可能编写一个事件处理器,并通过React组件传输所有的输入状态。当你需要将已有代码转到React,或将React应用与非React库整合在一起时,这会变得特别烦人。在这些情况中,你可能需要审视下非受控组件,一个实现输入表单的替代技术。