• 表单
    • 受控组件
    • textarea 标签
    • select 标签
    • 文件 input 标签
    • 处理多个输入
    • 受控输入空值
    • 受控组件的替代品
    • 成熟的解决方案

    表单

    在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:

    1. <form>
    2. <label>
    3. 名字:
    4. <input type="text" name="name" />
    5. </label>
    6. <input type="submit" value="提交" />
    7. </form>

    此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。

    受控组件

    在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

    我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

    例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:

    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('提交的名字: ' + this.state.value);
    13. event.preventDefault();
    14. }
    15. render() {
    16. return (
    17. <form onSubmit={this.handleSubmit}>
    18. <label>
    19. 名字:
    20. <input type="text" value={this.state.value} onChange={this.handleChange} />
    21. </label>
    22. <input type="submit" value="提交" />
    23. </form>
    24. );
    25. }
    26. }

    在 CodePen 上尝试

    由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。

    对于受控组件来说,每个 state 突变都有一个相关的处理函数。这使得修改或验证用户输入变得简单。例如,如果我们要强制要求所有名称都用大写字母书写,我们可以将 handlechange 改写为:

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

    textarea 标签

    在 HTML 中, <textarea> 元素通过其子元素定义其文本:

    1. <textarea>
    2. 你好, 这是在 text area 里的文本
    3. </textarea>

    而在 React 中,<textarea> 使用 value 属性代替。这样,可以使得使用 <textarea> 的表单和使用单行 input 的表单非常类似:

    1. class EssayForm extends React.Component {
    2. constructor(props) {
    3. super(props);
    4. this.state = {
    5. value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
    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('提交的文章: ' + this.state.value);
    15. event.preventDefault();
    16. }
    17. render() {
    18. return (
    19. <form onSubmit={this.handleSubmit}>
    20. <label>
    21. 文章:
    22. <textarea value={this.state.value} onChange={this.handleChange} />
    23. </label>
    24. <input type="submit" value="提交" />
    25. </form>
    26. );
    27. }
    28. }

    请注意,this.state.value 初始化于构造函数中,因此文本区域默认有初值。

    select 标签

    在 HTML 中,<select> 创建下拉列表标签。例如,如下 HTML 创建了水果相关的下拉列表:

    1. <select>
    2. <option value="grapefruit">葡萄柚</option>
    3. <option value="lime">酸橙</option>
    4. <option selected value="coconut">椰子</option>
    5. <option value="mango">芒果</option>
    6. </select>

    请注意,由于 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('你喜欢的风味是: ' + 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>
        );
      }
    }

    在 CodePen 上尝试

    总的来说,这使得 <input type="text">, <textarea><select> 之类的标签都非常相似—它们都接受一个 value 属性,你可以使用它来实现受控组件。

    注意

    你可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:

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

    文件 input 标签

    在 HTML 中,<input type=“file”> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。

    <input type="file" />

    因为它的 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>
              参与:
              <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>
        );
      }
    }

    在 CodePen 上尝试

    这里使用了 ES6 计算属性名称的语法更新给定输入名称对应的 state 值:

    例如:

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

    等同 ES5:

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

    另外,由于 setState() 自动将部分 state 合并到当前 state, 只需调用它更改部分 state 即可。

    受控输入空值

    在受控组件上指定 value 的 prop 可以防止用户更改输入。如果指定了 value,但输入仍可编辑,则可能是意外地将value 设置为 undefinednull

    下面的代码演示了这一点。(输入最初被锁定,但在短时间延迟后变为可编辑。)

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

    受控组件的替代品

    有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数,并通过一个 React 组件传递所有的输入 state。当你将之前的代码库转换为 React 或将 React 应用程序与非 React 库集成时,这可能会令人厌烦。在这些情况下,你可能希望使用非受控组件, 这是实现输入表单的另一种方式。

    成熟的解决方案

    如果你想寻找包含验证、追踪访问字段以及处理表单提交的完整解决方案,使用 Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上 —— 所以不要忽视学习它们。