Форми в React: використовуємо ref

16 хв. читання

React дозволяє отримати значення <form> у два способи. Перший полягає в імплементації так званих контрольованих компонентів, а другий — в застосуванні React властивості ref.

Головною характеристикою контрольованих компонентів є те, що значення, яке відображається прив'язане до стану компоненту. Для того, аби оновити значення, потрібно виконати функцію обробника подій onChange, прикріпленого до елементу <form>. Функція оновлює стан компоненту, що у свою чергу оновлює значення елементу форми.

(Перш, ніж ми підемо далі, якщо хочете побачити усі приклади коду до цієї статті: ось вони!)

Ось приклад використання контрольованих компонентів:

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), а функція onChangehandleFullNameChange (рядки 10-14 та 27).

Головними перевагами контрольованих форм є:

  1. Все вже налаштовано для валідації вхідних даних користувача;
  2. Можна динамічно обробляти інші компоненти, базуючись на значеннях контрольованих форм. Наприклад, значення вибору користувача з меню тварин (собака, кішка і т.п.) може контролювати, які інші компоненти форми (скажімо, чекбокс видів корму) будуть показані.

Недоліком такої системи буде кількість коду. Тут нам потрібна властивість стану, яка буде передана елементу форми як props, а також потрібна функція, яка буде оновлювати значення цієї властивості.

Це не проблема для однієї форми, втім, якщо у вас велика та складна форма (яка не потребує динамічної обробки чи валідації у реальному часі), вам доведеться писати купу коду, якщо будете занадто багато використовувати контрольовані форми.

Набагато простіше було б використати властивість ref для того, аби отримати значення <input>. Різні форми і різні композиції компонентів потребують різних стратегій, тому решта цієї статті розбита на секції.

  1. Текстові та числові інпути, <select>
  2. Передача значень до батьківських елементів
  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.fullName та this.amount. Значення інпутів доступні як this.fullName.value та this.amount.value. Така ж сама стратегія працює з елементами <select>:

<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. Передача значень до батьківських елементів

Використовуючи контрольовані компоненти, передати значення з дочірнього компонента в батьківський досить просто — значення вже існує в батьку, і передається дочірньому компоненту. Функція onChange також передається і вона оновлює значення, коли користувач взаємодіє з інтерфейсом.

Побачити, як це працює можна тут.

Втім, ситуація відрізняється, коли ми використовуємо ref. У цьому разі значення зберігається в DOM вузлі, а тому воно має бути передане вверх до батька.

Для того, аби це зробити ми маємо передати дочірньому компоненту «гак», до якого він у свою чергу прикріплює 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;

Тут ми бачимо компонент форми RefsForm та компонент інпуту CustomInput. Зазвичай, стрілкова функція знаходиться на самому інпуті, але тут вона передається в якості властивості (рядки 15 та 27). Оскільки стрілкова функція знаходиться у батьку, this в this.firstName також там. Значення дочірнього інпуту присвоєно властивості батьківського компонента this.firstName, отже батько має доступ до неї.

Батьківский компонент не лише має доступ до DOM вузла інпута, а й може встановлювати значення вузла (стрічка 7 і вище). Як тільки форма буде відправлена, значення інпуту встановлюється як Got ya!.

Освоєння цього патерну може забрати деякий час, тому майте терпіння.

Замітка: радіо перемикачі та чекбокси все ж краще робити як контрольовані форми, втім, якщо ви націлені використати для них саме ref, то наступні дві секції для вас.

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>

У цьому радіо перемикачі pet є три опції: cat, dog і ferret.

Оскільки нам потрібен увесь радіо блок, навішувати ref на кожен <input> не найкращий варіант. Крім того, на жаль, немає і DOM-вузла, який би відповідав за весь блок радіо.

Отримати значення з радіо перемикача ми можемо у три етапи:

  1. Прописати ref тегу <form> (стрічка 20 і нижче);
  2. Витягти усі радіо елементи з форми. У даному випадку це pet (стрічка 9 і нижче);
    • Тут повертається список вузлів і значення. У цьому випадку цей список вузлів включає 3 вузли <input> і значення selected;
    • Майте на увазі, що список вузлів виглядає, як масив, втім у нього немає методів масиву, що приводить нас до наступного пункту.
  3. Отримати значення за допомогою крапкової нотації (рядок 13 і нижче).
import React, { Component } from 'react';

class RefsForm extends Component {

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

    //  вилучити список вузлів з форми
    //  це виглядає як масив, але не вистачає методів масиву
    const { pet } = this.form;

    // набір radio має значення
    // перевірка логів для підтвердження
    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();

    //  вилучити список вузлів з форми
    //  це виглядає як масив, але не вистачає методів масиву
    const { pet } = this.form;

    // набір radio має значення
    // перевірка логів для підтвердження
    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. Чекбокси

На відміну від <input> типу radio, чекбокси можуть мати декілька обраних значень, що робить процес отримання цих значень складнішим.

Витягти ці значення можна у п'ять кроків:

  1. Прописуємо ref тегу <form> (рядок 27 і нижче);
  2. Витягуємо усі чекбокси з форми, у цьому випадку pet (рядок 9);
    • Тут повертається список вузлів і значення. У цьому випадку цей список включає 3 вузли <input> і значення selected;
    • Майте на увазі, що список вузлів виглядає, як масив, втім у нього немає методів масиву, що приводить нас до наступного пункту.
  3. Конвертуємо список вузлів у масив, аби ми могли використовувати методи масивів (checkboxArray на рядок 12);
  4. Використовуємо Array.filter(), аби отримати чекбокси, які були обрані (checkedCheckboxes на рядок 15);
  5. Використовуємо Array.map(), аби зберегти лише значення обраних чекбоксів (checkedCheckboxesValues на рядок 19).
import React, { Component } from 'react';

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

    //  вилучити список вузлів з форми
    //  це виглядає як масив, але не вистачає методів масиву
    const { pet } = this.form;

    // конвертація списка вузлів у масив
    const checkboxArray = Array.prototype.slice.call(pet);

    // вилучити лише відмічені чекбокси
    const checkedCheckboxes = checkboxArray.filter(input => input.checked);
    console.log('checked array:', checkedCheckboxes);

    // використовуємо .map() для вилучення значень з кожного відміченного чекбокса
    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();

    //  вилучити список вузлів з форми
    //  це виглядає як масив, але не вистачає методів масиву
    const { pet } = this.form;

    // конвертація списка вузлів у масив
    const checkboxArray = Array.prototype.slice.call(pet);

    // вилучити лише відмічені чекбокси
    const checkedCheckboxes = checkboxArray.filter(input => input.checked);
    console.log('checked array:', checkedCheckboxes);

    // використовуємо .map() для вилучення значень з кожного відміченного чекбокса
    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 над контрольованими формами є те, що у більшості випадків нам потрібно писати менше коду. Винятковий випадок з чекбоксами (і в меншій мірі з радіо перемикачем), де кількість коду не набагато менша.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація