翻譯 | 玩轉 React 表單 —— Refs 的運用

React 提供了兩種從 <form> 元素中獲取值的標準方法。第一種方法是實現所謂的受控組件 (能夠看我博客裏發表的文章) ,第二種方法是使用 React 的 ref 屬性。git

受控組件很重,被展現的值和組件的 state 綁定是它的特性。咱們經過執行一個附着在 form 元素上的 onChange 事件句柄,來更新被展現的值。onChange 函數更新 state 屬性,進而更新 form 元素的值。github

(在看到下面的文章以前,若是你只是想看相應的示例代碼:請移步這裏api

受控組件示例:數組

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;

input 的值是 this.state.fullName (在第7行和第26行)。 onChange 函數是 handleFullNameChange (第 10 - 14 行和第 27 行)。函數

受控組件最主要的優點是:
一、便於驗證用戶的輸入
二、能夠根據受控組件的值動態地渲染其餘組件。例如:一個用戶在下拉列表中選擇的值(如「dog」 或者 「cat」 )能夠控制在 form 中渲染的其餘 form 組件(例如:一個設置品種的複選框)this

受控組件的缺點是要寫大量的代碼。你須要經過 props 把 state 屬性傳遞給 form 元素,還須要一個函數來更新這個屬性的值。

對於單一表單元素來講這真的不是什麼問題 —— 可是若是你須要一個龐大而且複雜的表單(不須要動態渲染或者實時驗證),過分使用受控表單會讓你書寫成噸的代碼。

從 form 元素取值的簡便的方法是使用 ref 屬性。咱們用不一樣的方式來應對不一樣的 form 元素和組件結構,因此這篇文章剩下的內容分爲如下幾個部分。

  1. 文本輸入框、數字輸入框和選擇框

  2. 子組件經過 props 傳值給父組件

  3. Radio 標籤集合

  4. Checkbox 標籤集合

一、文本輸入框、數字輸入框和選擇框

使用 ref 的最簡單的例子是文本和數字 input 元素。咱們在 input 的 ref 屬性裏添加一個把 input 自己做爲參數的箭頭函數。我喜歡把參數命名爲和元素自己同樣的的名字,就像下面的第三行那個樣子:

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

因爲該參數是 input 元素自己的別名,你能夠爲所欲爲地爲它命名:

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

接着你能夠拿到該參數,並將它賦值給當前 class 內 this 關鍵字上掛載的屬性(譯者注:這裏的 class 指的是 JSX 所處的 React 組件 class)。input(例如: 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 獲取。

二、子組件經過 props 傳值給父組件

經過受控組件,父組件獲取子組件的值十分簡單 —— 父組件中已經有這個值了(譯者注:在父組件中定義)!它被傳遞給子組件。同時 onChange 方法也被傳給子組件,用戶經過與 UI 互動(譯者注:觸發 onChange)來更新該值。

你能夠在我上篇文章的受控組件示例中看到它是如何運行的。

雖然該值已經存在於受控組件的父組件中,可是當使用 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;

經過上面的代碼,能夠看到一個 form 組件 RefForm 和一個叫作 CustomInput 的 input 組件。一般,箭頭函數都是在 input 自身上面,可是從這(15 - 27 行)能夠看到它是經過 props 傳遞的。因爲箭頭函數存在於父組件中,因此 this.firstName 中的 this 指向父組件。

input 子組件的值被賦給父組件的 this.firstName 屬性,因此父組件能夠得到子組件的值。如今,父組件中的 this.firstName 指的是子組件中的 DOM 節點(例如: CustomInput 中的 input)。

父組件不只能夠訪問 input 中的 DOM 節點,還能夠在父組件內給節點的值賦值。在上文的第 7 行能夠看到例子。一旦表單被提交, input 的值就被設置爲 「Got ya!」 。

這種方式有點讓人摸不着頭腦,因此請仔細揣摩並敲代碼實踐一下,直至徹底理解。

你可能會寫出來更好的 radio 和 checkbox  受控組件,可是若是你真的想要用 `ref` ,那麼接下來的兩部分會幫到你。

三、 Radio 標籤集合

不像 text 和 number 這類 input 元素,radio 元素是成組出現的。每組中的元素都有相同的 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>

在 「pet」 radio 標籤集合中有三個選項 —— 「cat」、「dog」 和 「ferret」。

因爲咱們關心的是整個集合的元素,因此給每一個單選框設置 ref 並非一個好主意。遺憾的是,沒有 DOM 節點是包含了 radio 集合的。

能夠經過下面的三步來檢索出 radio 集合的值:
一、在 form 標籤上設置 ref (下面的第20行)。
二、從 form 中取出這個 radio 集合。而後它應該是 pet 集合(下面的第9行)。

  • 此處返回一個節點列表和一個值。在這種狀況下,這個節點列表包含三個 input 節點和被選中的值。

  • 須要注意的是這個節點列表是個類數組,它沒有數組的方法。在下一部分中還有更多關於這個話題的內容。
    三、使用 . 方法來獲取這個集合的值(下面的第13行)。

import React, { Component } from 'react';

class RefsForm extends Component {

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

    //  從 form 中取出節點列表
    //  它是一個類數組,沒有數組的方法
    const { pet } = this.form;

    // radio 標籤集合有 value 屬性
    // 查看打印出來的數據
    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;

若是你正在用子組件寫一個表單也是可行的。儘管組件中會有更多的邏輯,可是從 radio 集合中獲取值的方法是不變的。

import React, { Component } from 'react';

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

    //  從 form 中取出節點列表
    //  它是一個類數組,沒有數組的方法
    const { pet } = this.form;

    // radio 標籤集合有 value 屬性
    // 查看打印出來的數據
    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;

四、 Checkbox 標籤集合

和 radio 標籤集合不同, Checkbox 標籤集合可能有多個值。致使獲取這些值會比獲取 radio 標籤集合的值難一些。

能夠經過下面的五步來檢索出 checkbox 標籤集合被選中的值:

一、在 form 標籤上設置 ref (下面的第27行)。
二、從 form 中取出這個checkbox 集合。而後它應該是 pet 集合(第9行)。

  • 此處返回一個節點列表和一個值

  • 須要注意的是這個節點列表是一個類數組,它沒有數組的方法,而後咱們就須要進行下面的這一步 ...
    三、把這個節點列表轉換成一個數組,而後就可使用數組的方法了(第 12 行的 checkboxArray )。

四、使用 Array.filter() 獲取選中的 checkbox (第 15 行的 checkedCheckboxes )。
五、使用 Array.map() 獲取選中的 checkbox 的惟一的值(第 19 行的 checkedCheckboxesValues

import React, { Component } from 'react';

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

    //  從 form 中取出節點列表
    //  它是一個類數組,沒有數組的方法
    const { pet } = this.form;

    // 把節點列表轉換成一個數組
    const checkboxArray = Array.prototype.slice.call(pet);

    // 僅取出被選中的 checkbox
    const checkedCheckboxes = checkboxArray.filter(input => input.checked);
    console.log('checked array:', checkedCheckboxes);

    // 使用 .map() 方法從每一個被選中的 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;

使用子組件寫 checkbox 的方法和上一部分中寫 radio 的方法是同樣的。

import React, { Component } from 'react';

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

    //  從 form 中取出節點列表
    //  它是一個類數組,沒有數組的方法
    const { pet } = this.form;

    // 把節點列表轉換成一個數組
    const checkboxArray = Array.prototype.slice.call(pet);

    // 僅取出被選中的 checkbox
    const checkedCheckboxes = checkboxArray.filter(input => input.checked);
    console.log('checked array:', checkedCheckboxes);

    // 使用 .map() 方法從每一個被選中的 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;

結論

若是你不須要:

一、實時監視 form 元素的值(例如:爲了基於用戶的輸入渲染以後的組件)
二、實時執行自定義驗證方法

那麼使用 ref 方法獲取 form 元素的值是一個很好的方法。

大多數狀況下,越過受控組件使用 ref 最主要的價值是會寫更少的代碼。 checkbox ( radio 其次)是一個特例。對於 checkbox ,使用 ref 省下的代碼量是不多的,因此沒法說是使用受控組件好仍是 ref 好。


圖片描述

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

相關文章
相關標籤/搜索