HTML表单元素和 React里的其他DOM元素有些不同,因为它们会保留一些内部的状态。举个例子,这个普通的表单接受唯一的name值:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
这个表单具有默认的表单行为,当用户提交表单就会跳转到新页面。如果你想要在React里实现此行为,它自然而然就会实现。但是在大多数情况下,定义一个控制表单提交并且有能力控制用户输入的表单数据的js函数会更方便。实现这个的标准方法是一种叫“受控组件”的技术。
受控组件
在HTML里,<input>,<textarea>和<select>这类元素通常包含它们自己的状态并且会基于用户输入而更新状态。在React中,易变的状态通常会保存在组件的state属性里,并且只能使用setState()来更新。
我们可以联合两种状态通过设置React state为“单一数据源”。然后React组件会渲染表单也会控制随后用户的输入。这样子的表单元素输入的值被React控制的方法叫做“受控组件”。
举个例子,如果我们想要让上一个例子当提交的时候记录name值,我们可以把表单写成一个受控组件:
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> ); } }
只要value属性被设置在表单元素上,被显示的value值就永远会是this.state.value,使得React的state是单一数据源。当每次修改表单值的时候handleChange就会更新React的state,只要输入改变显示的值就会更新。
通过控制组件,每一个state的变化都会有一个联合的处理函数。这让它可以明确的修改或者确认用户输入。举个例子,如果我们想要强制name值都用大写字母来写,那么我们应该像这样写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>能够像这样写,跟使用单行input的表单类似:
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>
注意Coconut选项是初始就被选择的,因为selected属性。在React里,我们不使用selected属性,而是在select标签里使用value属性。在控制组件里这样更加方便因为你只需要在一个地方更新默认选择项即可。举个例子:
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> ); } }
总得来说,<input type="text">,<textarea>和<select>都类似,他们都接受一个value属性来完成一个控制组件。
file input 标签
在HTML当中,<input type="file"> 允许用户从他们的存储设备中选择一个或多个文件以提交表单的方式上传到服务器上, 或者通过 Javascript 的 File API 对文件进行操作 。
由于该标签的 value 属性是只读的, 所以它是 React 中的一个非受控组件。我们会把它和其他非受控组件一起在后面的章节进行详细的介绍。
处理多个输入
当你需要处理多个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的“计算出的属性名”语法来更新state key通过对应的input name:
this.setState({ [name]: value });
ES5语法这样写:
var partialState = {}; partialState[name] = value; this.setState(partialState);
同样,只要setState()自动的将部分state合并到了当前的state,我们只需要调用它来更新被改变的部分。
控制组件的可选方案
有时使用受控组件可能很繁琐,因为您要为数据可能发生变化的每一种方式都编写一个事件处理程序,并通过一个组件来管理全部的状态。当您将预先存在的代码库转换为React或将React应用程序与非React库集成时,这可能变得特别烦人。在以上情况下,你或许应该看看非受控组件,这是一种表单的替代技术。