此文章是翻译forms这篇React(版本v16.2.0)官方文档。

Form

在React 中,HTML 表单元素同其它DOM 元素的使用几乎没有不同,因为表单元素本身就保存一些内部状态。例如,下面这个纯HTML 表单接受一个name。

  1. <form>
  2. <label>
  3. Name:
  4. <input type="text" name="name" />
  5. </label>
  6. <input type="submit" value="Submit" />
  7. </form>

当用户提交一个表单时,表单会有会有一个默认打开一个新页面的HTML 表单行为。在React 中如果你想要这个行为,它也工作。但是在大多数情况下,通过JavaScript 函数去控制表单提交并获取用户在表单中的数据是很方便的。使用“可控的组件(controlled components)” 这种技术来实现这种标准行为。

Controlled Components

在HTML 中,像<input><textarea><select> 这些表单元素通常保留它们自己的状态并根据用户输入来更新。在React 中,可变状态(mutable state)通常保存在组件的状态属性中(state property of components),并且只能由setState() 更新。

我们可以结合这两方面,通过使React state 成为“单一数据源(single source of truth)”。然后React 组件渲染form 同时也能控制后续的用户的输入。一个输入表单元素的值通过React 这种方式控制称为“可控的组件”。

例如,如果我们想要在之前例子在提交时输出name,我们可以这样写一个表单作为可控的组件:

  1. class NameForm extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {value : ''};
  5. this.handleChange = this.handleChange.bind(this);
  6. this.handleSubmit = this.handleSubmit.bind(this);
  7. }
  8. handleChange(event) {
  9. this.setState({value: event.target.value});
  10. }
  11. handleSubmit(event) {
  12. alert('A name was submitted: ' + this.state.value);
  13. event.preventDefault();
  14. }
  15. render() {
  16. return (
  17. <form onSubmit={this.handleSubmit}>
  18. <label>
  19. Name:
  20. <input type="text" name="name" value={this.state.value} onChange={this.handleChange} />
  21. </label>
  22. <input type="submit" value="Submit" />
  23. </form>
  24. )
  25. }
  26. }

在CodePen 上尝试

由于value 特性设置在表单元素,将总是展示this.state.value 这个值,使React state 成为单一数据源。由于handleChange 总是随着每一次按键而运行去更新React state,展示的值也总是随着用户的键入而更新。

对于可控的组件,每一次状态改变(state mutation)总是有相应的句柄函数。这能更直接的修改和验证用户的输入。例如,如果我们想强制将每一个输入都变成大写,我们可以如下修改handleChange

  1. handleChange(event) {
  2. this.setState({value: event.target.value.toUpperCase()});
  3. }

The textarea Tag

在HTML 中,<textarea> 元素通过子节点定义它的文字:

  1. <textarea>
  2. Hello there, this is some text in a text area
  3. </textarea>

在React 中,<textarea> 通过使用value 特性来替代。这种方式使用<textarea> 在写法上非常类似表单使用单行输入(single-line input):

  1. class EssayForm extends React.Component {
  2. constructor(props){
  3. super(props);
  4. this.state = {
  5. value : 'Please write an essay about your favourite DOM element'
  6. };
  7. this.handleChange = this.handleChange.bind(this);
  8. this.handleSubmit = this.handleSubmit.bind(this);
  9. }
  10. handleChange(event) {
  11. this.setState({value: event.target.value});
  12. }
  13. handleSubmit(event) {
  14. alert('An essay was submitted: ' + this.state.value);
  15. event.preventDefault();
  16. }
  17. render() {
  18. return (
  19. <form onSubmit={this.handleSubmit}>
  20. <label>
  21. Essay:
  22. <textarea value={this.state.value} onChange={this.handleChange} />
  23. </label>
  24. <input type="submit" value="Submit"/>
  25. </form>
  26. )
  27. }
  28. }

注意this.state.value 是在构造函数中被初始化,所以这个文本域(text area)是以这里的文本为默认值的。

The select Tag

在HTML 中,<select> 创建一个下拉列表。例如,下面这个HTML 创建了一个鲜花的下拉列表:

  1. <select>
  2. <option value="grapefruit">Grapefruit</option>
  3. <option value="lime">Lime</option>
  4. <option selected value="coconut">Coconut</option>
  5. <option value="mango">Mango</option>
  6. </select>

注意到由于selected 特性,Coconut option 是初始化被选中的。在React 中通过在根select 标签上使用value特性来替代使用selected 特性。这对于可控的组件来说更方便,因为你只需要更新这一个位置就可以了。例如:

  1. class FlavorForm extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {value: 'coconut'};
  5. this.handleChange = this.handleChange.bind(this);
  6. this.handleSubmit = this.handleSubmit.bind(this);
  7. }
  8. handleChange(event) {
  9. this.setState({value: event.target.value});
  10. }
  11. handleSubmit(event) {
  12. alert('Your favourite flavor is: ' + this.state.value);
  13. event.preventDefault();
  14. }
  15. render() {
  16. return (
  17. <form onSubmit={this.handleSubmit} >
  18. <label>
  19. Pick your favourite La Croix flavor
  20. <select value={this.state.value} onChange={this.handleChange}>
  21. <option value="grapefruit">Grapefruit</option>
  22. <option value="lime">Lime</option>
  23. <option value="coconut">Coconut</option>
  24. <option value="mango">Mango</option>
  25. </select>
  26. </label>
  27. <input type="submit" value="Submit"/>
  28. </form>
  29. );
  30. }
  31. }

在CodePen 上尝试

总的来说,这使得<input type="text><textarea> <select>的使用非常相似-它们都可以通过接受一个value 来实现可控的组件。

注意

你可以传递一个数组给value 特性,允许你在一个select 标签中选中多个options:

  1. <select multiple={true} value={['B', 'C']}

Handling Mutiple Inputs

当你需要处理多个可控的 input 元素,你可以添加一个name 特性到每一个元素,并且让句柄函数根据event.target.name 的值去选择做什么。

例如:

  1. class Reservation extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. isGoing: true,
  6. numberOfGuests: 2
  7. };
  8. this.handleInputChange = this.handleInputChange.bind(this);
  9. }
  10. handleInputChange(event) {
  11. const target = event.target
  12. const value = target.type === 'checkbox' ? target.checked : target.value
  13. const name = target.name
  14. this.setState({
  15. [name]: value
  16. })
  17. }
  18. render() {
  19. return (
  20. <form>
  21. <label>
  22. Is going:
  23. <input
  24. name="isGoing"
  25. type="checkbox"
  26. checked={this.state.isGoing}
  27. onChange={this.handleInputChange} />
  28. </label>
  29. <br />
  30. <label>
  31. Number of guests:
  32. <input
  33. name="numberOfGuests"
  34. type="number"
  35. value={this.state.numberOfGuests}
  36. onChange={this.handleInputChange} />
  37. </label>
  38. </form>
  39. );
  40. }
  41. }

在CodePen 上尝试

注意我们如何使用ES6computed property name语法更新state key 响应给定的input name:

  1. this.setState({
  2. [name]: value
  3. });

它同ES5 代码相同:

  1. var partialState = {}
  2. partialState[name] = value
  3. this.setState(partialState)

此外,由于setState()自动将部分状态合并到当前状态,所以我们只需要调用更改的部分。

Controlled Input Null Value

在可控的组件上指定值prop 可以防止用户更改输入,除非你希望这样做。如果你指定了一个值,但输入仍然是可编辑的,则可能意外地将value设置为undefinednull

下面的代码演示了这一点。(输入一开始是锁定的,但在短时间延迟后变得可编辑。)

  1. ReactDOM.render(<input value="hi" />, mountNode);
  2. setTimeout(function() {
  3. ReactDOM.render(<input value={null} />, mountNode);
  4. }, 1000);

Alternatives to Controlled Components

有时使用可控的组件是非常令人厌烦的,因为你必须写事件句柄为每一次你的数据改变和通过React 组件来传递输入状态(input state)。尤其是在你改变之前的React 代码或者使用非Reac 库集成到React 中。在这些情况下,你可能想要看看uncontrolled components,一个替代技术实现输入表单的技术。