建议配合官网文档Forms进行使用。

Forms

HTML 表单的工作方式和 React 表单的工作方式略有不同,因此 HTML 的表单元素有一些固有的性质,如下面这个纯 HTML 表单:

<form >
  <label>
		Name:
		<input type="text" name="name">
	</label>
	<input type="submit" value="submit" />
</form>

该表单有一个默认的 HTML 行为,即在用户提交表单后浏览到新页面,如果你希望在 React 中也实现这样的行为,那它确实能正常工作,但是在大多数情况下,使用 JavaScript 函数来控制表单的提交和获取用户填写的数据是更优的做法。比较标准的做法是使用“受控组件controlled components的方法。

Controlled Components

在 HTML 中,表单元素例如<input>, <textarea>, and <select>通常维护自己的状态,并根据用户的输入更新。对于 React 而言,可变状态一般保存在组件的 state 中,并使用 setState() 函数进行 更新。

我们可以将 HTML 保存的状态和 React 组件保存的状态绑定在一起,使之同源,也就是说, React 即渲染 UI 页面,也控制表单数据。

我们将上面的纯 HTML 表单修改为下面的组件:

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>
    );
  }
}

上面的input表单元素的 value始终为组件的 this.state.value, 这使得 React 组件状态和 HTML 维护的状态保持一致,handleChange()函数会在每次用户输入时调用,因此 React 每次都会同步更新 UI。

The textarea Tag

在 HTML 中, <textarea> 元素的内容由其孩子确定。

<textarea>
  Hello there, this is some text in a text area
</textarea>

在 React 中,textarea使用value属性定义其内容,和input的使用方法保持一致:

class EssayForm extends React.Component {
  constructor(props){
    super(props);
    this.state = {value: 'Please write an essay about your favorite DOM element'};
  }
  handleChange = () => {
    this.setState({value: event.target.value});
  }
  handleSubmit = () => {
    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>
    )
  }
}

因为在constructor中初始化了this.state.value,因此textarea中一开始就会显示文字。

The select Tag

在 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>

注意,HTML 使用selected属性来初始化选中的文本, React 则和 textarea的初始化类似,使用valueconstructor中初始化选中的文本,

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'Lime'};
  }

  handleChange = () => {
    this.setState({value: event.target.value});
  }

  handleSubmit = () => {
    alert("Submit a flavor: " + 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="Lime">Lime</option>
            <option value="coconut">coconut</option>
            <option value="mango">mango</option>
          </select>
        </label>
        <input type="submit" value="Submit"/>
      </form>
    )
  }
}

对于<input>, textarea, select来说, React 均使用value属性来实现对应的受控组件。

对于多选框来说,可以使用数组来初始化选中项:

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

但实际上无法进行多选,笔者目前还没有搞清楚原理,后续查到实现方式再来补全

The file input Tag

在 HTML 中,<input type="file" />允许用户上传文件。因此上传文件是只读的,因此在React中这不是受控组件,我们将和其他非受控组件一起讲解。

Handling Multiple Inputs

如果你想处理多个input元素,你可以给每个元素添加name属性,并且通过event.targer.name处理指定的input

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: false,
      numberOfGuests: 2
    };
  }

  handleInputChange = () => {
    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:以下两种语法是等价的:

ES6:

this.setState({
  [name]: value
})

ES5:

let partialState = {};
partialState[name] = value;
this.setState(partialState);

setState()会将新state和旧state合并。

Controlled Input Null Value

如果将确定的值赋给受控组件的value,会导致用户无法改变输入,除非你本亦如此。如果你希望如此,但指定了常量后,用户仍可编辑,可能是你指定的值为null或者undefined

ReactDOM.render(<input value="hi" />, document.getElementById('root'));

setTimeout(function() {
  ReactDOM.render(<input value={null} />, document.getElementById('root'));
}, 5000);

上述的输入框在5秒钟后由不可编辑状态变为可编辑状态。

Alternatives to Controlled Components

有时使用受控组件会显得十分繁杂,因为你必须为每个组件都编写不同的handleChange,并且记录每一个组件的状态。这在你讲已有代码重构为React代码时尤为繁杂,这时你可以采用非受控组件来来实现.

Fully-Fledged Solutions

如果你正在寻找一个成熟的解决方案,包括合法性检验、追踪访问,提交处理,使用 Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上 —— 所以不要忽视学习它们。