React 表单:使用 Refs

Avatar of Loren Stewart
Loren Stewart

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费信用额度!

React 提供了两种标准方法来获取来自<form>元素的值。第一种方法是实现所谓的受控组件(请参阅我的关于该主题的博文),第二种方法是使用 React 的ref属性。

受控组件功能强大。受控组件的定义特征是显示的值绑定到组件状态。要更新值,您需要执行附加到表单元素上的onChange事件处理程序的函数。onChange函数更新状态属性,这反过来又更新表单元素的值。

(在我们深入之前,如果您只想查看本文的代码示例:请点击此处!

这是一个受控组件的示例

import React, { Component } from 'react';

class ControlledCompExample extends Component {
  constructor() {
    super();
    this.state = {
      fullName: ''
    }
  }
  handleFullNameChange = (e) => {
    this.setState({
      fullName: e.target.value
    })
  }
  handleSubmit = (e) => {
    e.preventDefault();
    console.log(this.state.fullName)
  }
  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <label htmlFor="fullName">Full Name</label>
            <input
              type="text"
              value={this.state.fullName}
              onChange={this.handleFullNameChange}
              name="fullName" />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

export default ControlledCompExample;

输入的值是this.state.fullName(第 7 行和第 26 行)。onChange函数是handleFullNameChange(第 10 – 14 行和第 27 行)。

受控组件的主要优点是

  1. 您可以轻松地验证用户输入。
  2. 您可以根据受控组件的值动态渲染其他组件。例如,用户从下拉列表中选择的值(例如“狗”或“猫”)可以控制在表单中渲染哪些其他表单组件(例如品种复选框集)。

受控组件的缺点是您必须编写的代码量。您需要一个状态属性作为props传递给表单元素,并且需要一个函数来更新此属性的值。

对于一个表单元素来说,这不是问题——但是如果您有一个大型的复杂表单(不需要动态渲染或实时验证),那么如果您过度使用受控组件,您会发现自己编写了大量的代码。

获取表单元素值的一种更简单、更省力的方法是使用ref属性。不同的表单元素和组件组合需要不同的策略,因此本文的其余部分分为以下几个部分。

  1. 文本输入、数字输入和选择
  2. 从子组件传递 props 到父组件
  3. 单选按钮组
  4. 复选框组

1. 文本输入、数字输入和选择

文本和数字输入提供了使用ref的最直接的示例。在输入的ref属性中,添加一个箭头函数,该函数将输入作为参数。我倾向于将参数命名为与元素本身相同的名称,如第 3 行所示

<input
  type="text"
  ref={input => this.fullName = input} />

由于它是输入元素本身的别名,因此您可以根据需要命名参数

<input
  type="number"
  ref={cashMoney => this.amount = cashMoney} />

然后,您获取参数并将其分配给附加到类的this关键字的属性。输入(即 DOM 节点)现在可作为this.fullNamethis.amount访问。输入的值可作为this.fullName.valuethis.amount.value访问。
相同的策略适用于选择元素(即下拉列表)。

<select
  ref={select => this.petType = select}
  name="petType">
  <option value="cat">Cat</option>
  <option value="dog">Dog</option>
  <option value="ferret">Ferret</option>
</select>

所选的值可作为this.petType.value访问。

2. 从子组件传递 props 到父组件

使用受控组件,从子组件获取值到父组件非常简单——值已经存在于父组件中!它被传递到子组件。一个onChange函数也被传递下来,并在用户与 UI 交互时更新值。

您可以在我之前的文章中的受控组件示例中看到这一点。

虽然值在受控组件中已经存在于父组件的状态中,但在使用ref时并非如此。使用ref,值驻留在 DOM 节点本身中,并且必须向上传递到父组件。

要将此值从子组件传递到父组件,父组件需要将一个“钩子”(如果可以这样说)传递到子组件。然后,子组件将一个节点附加到“钩子”上,以便父组件可以访问它。

在进一步讨论之前,让我们看一些代码。

import React, { Component } from 'react';

class RefsForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    console.log('first name:', this.firstName.value);
    this.firstName.value = 'Got ya!';
  }
  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <CustomInput
            label={'Name'}
            firstName={input => this.firstName = input} />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

function CustomInput(props) {
  return (
    <div>
      <label>{props.label}:</label>
      <input type="text" ref={props.firstName}/>
    </div>
  );
}

export default RefsForm;

在上面,您看到了一个表单组件RefForm和一个名为CustomInput的输入组件。通常,箭头函数位于输入本身,但在这里它作为 prop 传递(参见第 15 行和第 27 行)。由于箭头函数驻留在父组件中,因此this.firstNamethis位于父组件中。

子组件输入的值被分配给父组件的this.firstName属性,因此子组件的值可供父组件使用。现在,在父组件中,this.firstName指的是子组件中的 DOM 节点(即CustomInput中的输入)。

父组件不仅可以访问输入的 DOM 节点,还可以从父组件内部分配节点的值。这在上面的第 7 行进行了演示。表单提交后,输入的值将设置为“Got ya!”。

这种模式有点令人费解,因此请仔细观察它一段时间,并尝试使用代码,直到理解为止。

您可能最好将单选按钮和复选框设为受控组件,但如果您确实想使用refs,接下来的两节内容适合您。

3. 单选按钮组

与文本和数字输入元素不同,单选按钮以组的形式出现。组中的每个元素都具有相同的name属性,如下所示

<form>
  <label>
    Cat
    <input type="radio" value="cat" name="pet" />
  </label>
  <label>
    Dog
    <input type="radio" value="dog" name="pet" />
  </label>
  <label>
    Ferret
    <input type="radio" value="ferret" name="pet" />
  </label>
  <input type="submit" value="Submit" />
</form>

“宠物”单选按钮组中有三个选项——“猫”、“狗”和“雪貂”。

由于整个组是我们关注的对象,因此在每个单选按钮输入上设置ref并不是理想的选择。而且,不幸的是,没有 DOM 节点封装一组单选按钮。

可以通过三个步骤获取单选按钮组的值

  1. <form>标签上设置 ref(如下所示的第 20 行)。
  2. 从表单中提取单选按钮组。在本例中,它是pet组(如下所示的第 9 行)。
    • 这里返回一个节点列表和一个值。在本例中,此节点列表包含三个输入节点和所选的值。
    • 请记住,节点列表看起来像数组,但它不是数组,并且缺少数组方法。下一节将详细介绍此主题。
  3. 使用点表示法获取组的值(如下所示的第 13 行)。
import React, { Component } from 'react';

class RefsForm extends Component {

  handleSubmit = (e) => {
    e.preventDefault();

    //  extract the node list from the form
    //  it looks like an array, but lacks array methods
    const { pet } = this.form;

    // a set of radios has value property
    // checkout out the log for proof
    console.log(pet, pet.value);
  }

  render() {
    return (
      <div>
        <form
          onSubmit={this.handleSubmit}
          ref={form => this.form = form}>
          <label>
            Cat
            <input type="radio" value="cat" name="pet" />
          </label>
          <label>
            Dog
            <input type="radio" value="dog" name="pet" />
          </label>
          <label>
            Ferret
            <input type="radio" value="ferret" name="pet" />
          </label>
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

export default RefsForm;

即使您是从子组件组合表单,此方法也适用。尽管组件中有更多逻辑,但从单选按钮组获取值的技巧保持不变。

import React, { Component } from 'react';

class RefsForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();

    //  extract the node list from the form
    //  it looks like an array, but lacks array methods
    const { pet } = this.form;

    // a set of radios has value property
    // checkout out the log for proof
    console.log(pet, pet.value);
  }

  render() {
    return (
      <div>
        <form
          onSubmit={this.handleSubmit}
          ref={form => this.form = form}>
          <RadioSet
            setName={'pet'}
            setOptions={['cat', 'dog', 'ferret']} />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

function RadioSet(props) {
  return (
    <div>
      {props.setOptions.map(option => {
        return (
          <label
            key={option}
            style={{textTransform: 'capitalize'}}>
            {option}
            <input
              type="radio"
              value={option}
              name={props.setName} />
          </label>
        )
      })}
    </div>
  );
}

export default RefsForm;

4. 复选框组

与单选按钮组不同,复选框组可以选择多个值。这使得提取这些值比提取单选按钮组的值稍微复杂一些。

可以通过以下五个步骤检索复选框组的选定值

  1. <form>标签上设置 ref(如下所示的第 27 行)。
  2. 从表单中提取复选框组。在本例中,它是pet组(第 9 行)。
    • 这里返回一个节点列表和一个值。
    • 请记住,节点列表看起来像数组,但它不是数组,并且缺少数组方法,这将我们带到下一步……
  3. 将节点列表转换为数组,以便可以使用数组方法(第 12 行的checkboxArray)。
  4. 使用Array.filter()仅获取选中的复选框(第 15 行的checkedCheckboxes)。
  5. 使用Array.map()仅保留选中的复选框的值(第 19 行的checkedCheckboxesValues)。
import React, { Component } from 'react';

class RefsForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();

    //  extract the node list from the form
    //  it looks like an array, but lacks array methods
    const { pet } = this.form;

    // convert node list to an array
    const checkboxArray = Array.prototype.slice.call(pet);

    // extract only the checked checkboxes
    const checkedCheckboxes = checkboxArray.filter(input => input.checked);
    console.log('checked array:', checkedCheckboxes);

    // use .map() to extract the value from each checked checkbox
    const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
    console.log('checked array values:', checkedCheckboxesValues);
  }

  render() {
    return (
      <div>
        <form
          onSubmit={this.handleSubmit}
          ref={form => this.form = form}>
          <label>
            Cat
            <input type="checkbox" value="cat" name="pet" />
          </label>
          <label>
            Dog
            <input type="checkbox" value="dog" name="pet" />
          </label>
          <label>
            Ferret
            <input type="checkbox" value="ferret" name="pet" />
          </label>
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

export default RefsForm;

使用复选框组子组件的工作方式与上一节中的单选按钮组示例相同。

import React, { Component } from 'react';

class RefsForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();

    //  extract the node list from the form
    //  it looks like an array, but lacks array methods
    const { pet } = this.form;

    // convert node list to an array
    const checkboxArray = Array.prototype.slice.call(pet);

    // extract only the checked checkboxes
    const checkedCheckboxes = checkboxArray.filter(input => input.checked);
    console.log('checked array:', checkedCheckboxes);

    // use .map() to extract the value from each checked checkbox
    const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
    console.log('checked array values:', checkedCheckboxesValues);
  }

  render() {
    return (
      <div>
        <form
          onSubmit={this.handleSubmit}
          ref={form => this.form = form}>
          <CheckboxSet
            setName={'pet'}
            setOptions={['cat', 'dog', 'ferret']} />
          <input type="submit" value="Submit" />
        </form>
      </div>
    );
  }
}

function CheckboxSet(props) {
  return (
    <div>
      {props.setOptions.map(option => {
        return (
          <label
            key={option}
            style={{textTransform: 'capitalize'}}>
            {option}
            <input
              type="checkbox"
              value={option}
              name={props.setName} />
          </label>
        )
      })}
    </div>
  );
}

export default RefsForm;

结论

如果您不需要

  1. 实时监控表单元素的值(例如,为了根据用户输入渲染后续组件),或者
  2. 实时执行自定义验证,

那么使用ref获取表单元素数据是一个不错的选择。

与受控组件相比,使用ref的主要价值在于,在大多数情况下,您将编写更少的代码。特殊情况是复选框组(以及在较小程度上单选按钮组)。对于复选框组,使用 refs 可以节省的代码量很少,因此使用受控组件还是ref并不那么明确。