08. 表单
受控组件(双向数据绑定)
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()
来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Name 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> ) } }
|
类似于vue中的双向绑定,v-model
textarea 标签(使用value定义值)
在 HTML 中, <textarea>
元素通过其子元素定义其文本:
1 2 3
| <textarea> 你好, 这是在 text area 里的文本 </textarea>
|
在 React 中,<textarea>
使用 value
属性赋值。这样,可以使得使用 <textarea>
的表单和使用单行 input 的表单非常类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class EssayForm extends React.Component { constructor(props) { super(props); this.state = { value: '请撰写一篇关于你喜欢的 DOM 元素的文章.' }; 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> 文章: <textarea value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="提交" /> </form> ); } }
|
注意,this.state.value
初始化于构造函数中,因此文本区域默认有初值
select 标签 (value代替选中属性)
React 在根 select
标签上使用 value
属性代替option
元素上的selected
属性(选中属性)
在 HTML 中,<select>
创建下拉列表标签。例如,如下 HTML 创建了水果相关的下拉列表:
1 2 3 4 5 6
| <select> <option value="grapefruit">葡萄柚</option> <option value="lime">酸橙</option> <option selected value="coconut">椰子</option> <option value="mango">芒果</option> </select>
|
请注意,由于 selected
属性的缘故,椰子选项默认被选中。React 并不会使用 selected
属性,而是在根 select
标签上使用 value
属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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('你喜欢的风味是: ' + this.state.value); event.preventDefault(); }
render() { return ( <form onSubmit={this.handleSubmit}> <label> 选择你喜欢的风味: <select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">葡萄柚</option> <option value="lime">酸橙</option> <option value="coconut">椰子</option> <option value="mango">芒果</option> </select> </label> <input type="submit" value="提交" /> </form> ); } }
|
select多选
你可以将数组传递到 value
属性中,以支持在 select
标签中选择多个选项:
1
| <select multiple={true} value={['B', 'C']}>
|
小总结
总的来说,这使得 <input type="text">
, <textarea>
和 <select>
之类的标签都非常相似—它们都接受一个 value
属性,你可以使用它来实现受控组件。
在 HTML 中,<input type="file">
允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。
因为它的 value 只读,所以它是 React 中的一个非受控组件。将与其他非受控组件在后续文档中一起讨论。
处理多个输入
当需要处理多个 input
元素时,我们可以给每个元素添加 name
属性,并让处理函数根据 event.target.name
的值选择要执行的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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> 参与: <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} /> </label> <br /> <label> 来宾人数: <input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }
|
受控输入空值
在受控组件上指定 value 的 prop 会阻止用户更改输入。如果你指定了 value
,但输入仍可编辑,则可能是你意外地将value
设置为 undefined
或 null
。
下面的代码演示了这一点。(输入最初被锁定,但在短时间延迟后变为可编辑。)
1 2 3 4 5
| ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() { ReactDOM.render(<input value={null} />, mountNode); }, 1000);
|
受控组件的替代品 (非受控组件)
有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数,并通过一个 React 组件传递所有的输入 state。当你将之前的代码库转换为 React 或将 React 应用程序与非 React 库集成时,这可能会令人厌烦。在这些情况下,你可能希望使用非受控组件, 这是实现输入表单的另一种方式。
成熟的解决方案
如果你想寻找包含验证、追踪访问字段以及处理表单提交的完整解决方案,使用 Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上 —— 所以不要忽视学习它们。