Контролируемые и неконтролируемые компоненты в React не должны быть сложными

Привет, Хабр! Представляю вашему вниманию перевод статьи «Controlled and uncontrolled form inputs in React don't have to be complicated» автора Gosha Arinich.

Возможно, вы видели много статей, говорящих: “вы не должны использовать “setState”", в то время, когда документы утверждают, что “refs — это плохо”. Всё это очень противоречиво. Иногда, трудно понять, как сделать все правильно, а так же каковы критерии выбора между этими методами.

Так как же делать делать формы? В конце концов, формы занимают центральное место во многих веб-приложениях. И все же, обработка формы в React является краеугольным камнем, не так ли?

Однако все не так сложно. Позвольте мне показать вам различия между этими подходами, а также то, когда вы должны использовать каждый из них.

Неуправляемые компоненты


Неуправляемые компоненты похожи на обычные HTML-формы:

class Form extends Component {
  render() {
    return (
      <div>
        <input type="text" />
      </div>
    );
  }
}

Они запоминают всё, что вы печатали. Затем вы можете получить их значение, используя ref. Например, в обработчике onClick:


class Form extends Component {
  handleSubmitClick = () => {
    const name = this._name.value;
    // do something with `name`
  }
  render() {
    return (
      <div>
        <input type="text" ref={input => this._name = input} />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>
    );
  }
}

Другими словами, вам необходимо «вытащить» значения из поля, когда вам это нужно. Это можно сделать при отправке формы.

Это самый простой способ реализации форм. Конечно, должны быть веские основания для его использования, а именно: самые простейшие формы либо во время изучения React.
Однако этот способ не такой гибкий, поэтому давайте лучше посмотрим на управляемые компоненты.

Управляемые компоненты:


Управляемый компонент принимает свое текущее значение в качестве пропсов, а также коллбэк для изменения этого значения. Вы можете сказать, что это более “реактивный” способ управления компонентом, однако это не означает, что вы всегда должны использовать этот метод.

<input value={someValue} onChange={handleChange} />

Это все очень хорошо, но значение формы ввода должно существовать в неком state. Как правило, компонент, который рэндерит форму ввода (т.е. форма), сохраняет их в своем state:


class Form extends Component {
  constructor() {
    super();
    this.state = {
      name: '',
    };
  }

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

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.name}
          onChange={this.handleNameChange}
        />
      </div>
    );
  }
}

(Конечно, он может находиться в state другого компонента или даже в отдельном хранилище состояний, например Redux).

Каждый раз, когда вы вводите новый символ, вызывается handleNameChange. Он принимает новое значение формы ввода и записывает его в state.

image

  • Все начниается с пустой строки — ‘’;
  • Вы вводите букву ‘a’, и handleNameChange получает её и вызывает setState. Затем форма ввода заново рендерится со значением ‘a’;
  • Вы вводите букву ‘b’, и handleNameChange получает значение ‘ab’ и устанавливает его в state. Форма ввода опеть рендерится, но теперь со значением ‘ab’.

Этот поток словно «заталкивает» изменения в форму, именно поэтому компонент всегда имеет текущее значение входных данных, не требуя явного запроса на обновление.

Это означает, что ваши данные (state) и пользовательский интерфейс (форма ввода) всегда синхронизированы. State дает значение форме, в то время как форма изменяет текущее значение state.

Это также означает, что формы могут незамедлительно реагировать на изменения, что в свою очередь необходимо для:

  • быстрой обратной связи, например валидации;
  • отключения определенной кнопки, до тех пор пока все поля формы не будут валидны;
  • обеспечения обработки определенных форматов полей ввода, например номеров кредитных карт.

Но если вам это не нужно и вы считаете, что неуправляемые компоненты намного проще, то используйте их.

Что делает компонент “управляемым”?


Конечно, есть и другие элементы формы, такие как: checkboxes, radio, textarea и select.
Компонент становится управляемым, когда вы устанавливаете его значение используй props. Вот и всё.

Однако каждый из элементов формы имеет различные способы установки значения, поэтому вот небольшая таблица для общего понимания:

Элемент Значение Коллбэк для изменения Новое значение в коллбэке
<input type="text" />
value=«string» onChange event.target.value
<input type="checkbox" />
checked={boolean} onChange event.target.checked
<input type="radio" />
checked={boolean} onChange event.target.checked
<textarea />
value=«string» onChange event.target.value
<select />
value=«option value» onChange event.target.value

Выводы


Как управляемые, так и неуправляемые компоненты имеют свои достоинства и недостатки. Оцените конкретную ситуацию и выберите подход — если для вас это работает, то почему бы не воспользоваться этим?

Если ваша форма невероятно проста с точки зрения взаимодействия с пользовательским интерфейсом, то неуправляемые компоненты прекрасно подойдут вам. Вам не обязательно слушать то, что различные статьи говорят, что это плохо.

image

Кроме того, выбор типа компонента — это не то решение, которое принимается раз и навсегда: вы всегда можете заменить неуправляемые компоненты на управляемые. Переход от одного к другому не так уж и сложен.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 11

    0
    А где же хуки?
      0
      Какая разница, хуки или нет, суть не в этом.
        0
        Статья для начинающих же, почему бы и не показать, тем более и кода меньше получится.
        И есть проблемы с асинхронным обновлением стейта.

        Вот цитата со страницы реакта:
        «Хуки не меняют ваши знания о концепциях в React. Вместо этого, хуки предоставляют более прямой доступ к API уже знакомых вам понятий: пропсов, состояния, контекста, рефов, и жизненного цикла. Мы также рассмотрим мощный способ компоновать эти понятия с помощью хуков.»

        Вот ваш компонент на хуках:
        const Form = () => {
            const [name, setName] = useState("");
            const handleNameChange = e => setName(e.target.value);
        
            return (
                <div>
                    <input
                        type="text"
                        value={name}
                        onChange={handleNameChange}
                    />
                </div>
            );
        }

        Ну разве не красота?
          0
          Ну разве не красота?

          Нет) В реальной жизни при использовании реактовского стейт менеджмента получается убожество во всей красе.
          А красота это вот: codesandbox.io/s/cool-leaf-xnh59?file=/src/App.tsx
            0
            Ну вот, вы же сами про реактовский стейт менеджмент статью писали (кстати форму валидировать самое то, разве нет? а после валидации и модель обновить. Да и переиспользуемые компоненты без локального стора сложновато написать. Да и бывают свойства в компоненте отвечающие чисто за косметику, не тянуть же их в глобальный стор), а хуки лично мне много приятнее использовать, в сравнении с классами (после них и мысли не возникает использовать классы). А теперь отходите в сторону. Я согласен, что есть решения для глобального стейта и получше (реакт же позиционируется как бы для презентаций), но раз пошел такой разговор, то еще лучшее mobx-state-tree. Там и патчи (очень помогает если вы свою модель с сервером синхронизируете, когда один документ редактируют несколько пользователей одновременно например), и снапшуты, и много чего еще вкусного.
              0
              Пример который я скинул, там именно локальный стейт компонента, а не глобальный. MobX заменяет идеально и тот и другой.

              то еще лучшее mobx-state-tree. Там и патчи (очень помогает если вы свою модель с сервером синхронизируете, когда один документ редактируют несколько пользователей одновременно например), и снапшуты, и много чего еще вкусного.

              В mobx есть autorun и reaction — пожалуйста, синхронизируйтесь с чем угодно, хоть с сервером, хоть с query параметрами урла. Так же используя их делайте снапшоты сколько угодно и как угодно. mobx-state-tree не дает ничего крутого на самом деле, вам только может так казаться, но это связано с тем что попросту можете не знать возможности mobx'a. Вообщем mobx-state-tree нафиг не нужен, он только мешает и делает код придурковатым, вместо того чтобы писать код максимально простым и понятным. Любые ваши желания и фантазии можно с легкостью реализовать с помощью mobx'a. А для описания моделей более чем достаточно и более правильно использовать Typescript.

              Внимательнее глянул ваш пример, вы сами хуки пользуете. А новичкам предлагаете примеры с классами. Почему так?

              Я использую хук useState только для того, чтобы получить инстанс MobX класса, чтобы это был локальный стейт компонента, а не потому что я использую реактовский стейт менеджмент на хуках.
                0
                MST реализовал все велосипеды, которые вы скорее всего напишете поверх mobX, если столкнетесь с более сложными структурами данных, кстати он тоже написан поверх mobX. Эти подходы идут по возрастании сложности данных: стейт реакта-> mobX-> MST. Кстати Typescript тоже поддерживается в MST.
                Вот простой пример. Допустим у нас есть документ с данными, он хранится в массиве на сервере. С ним одновременно работают несколько человек в реальном времени. Один пользователь поменял у себя в модели какой-то элемент массива. Вопрос: как вы передадите изменения от этого пользователя всем остальным? как организовать удаление/обновление/создание новых элементов сразу у всех пользователей данного документа (будете писать экшены в духе редакса?)? а когда сложность и глубина структуры данных начнет расти?
                MST знает о структуре данных, в отличие от mobX, что сильно упрощает работу с ними.

                И про хуки. Ваша статья называется «Контролируемые и неконтролируемые компоненты в React не должны быть сложными». функции + хуки -> меньше кода, выше наглядность -> снижение сложности.
                  0
                  Вот простой пример. Допустим у нас есть документ с данными, он хранится в массиве на сервере. С ним одновременно работают несколько человек в реальном времени. Один пользователь поменял у себя в модели какой-то элемент массива. Вопрос: как вы передадите изменения от этого пользователя всем остальным? как организовать удаление/обновление/создание новых элементов сразу у всех пользователей данного документа (будете писать экшены в духе редакса?)? а когда сложность и глубина структуры данных начнет расти?

                  Ну вообще это элементарно, когда под рукой есть Javascript и WebSocket's(не обязательно, но с ними гораздо лучше). И без разница какой уровень вложенности в ваших массивах и объектах.

                  MST знает о структуре данных, в отличие от mobX, что сильно упрощает работу с ними.

                  Typescript / Flow знает о структуре данных.

                  И про хуки. Ваша статья называется «Контролируемые и неконтролируемые компоненты в React не должны быть сложными». функции + хуки -> меньше кода, выше наглядность -> снижение сложности.

                  Это не моя статья =)

                  Вообщем в дальнейшей дискуссии не вижу смысла. И доказывать вам что-то, тоже не вижу смысла. Пользуйтесь чем угодно на здоровье и как угодно, все равно мне с вашим кодом не работать =)

                  P.S. в духе редакса я ничего не пишу и не делаю, этот «дух» мне противен и меня удивляет что до сих пор в новых проектах кто-то берет Redux и пишет эту кашу с лапшой.
                    0
                    Ну вообще это элементарно, когда под рукой есть Javascript и WebSocket's(не обязательно, но с ними гораздо лучше). И без разница какой уровень вложенности в ваших массивах и объектах.
                    — я видимо недостаточно ясно выразился. с механизмом доставки нет проблем, сокеты или что-то еще. вопрос был про данные, как вы предлагаете отслеживать изменения в данных и обновлять небольшие их фрагменты, только то что изменилось средствами голого mobx? или будете передавать весь документ целиком, и не важно что некоторые пользователи уже тоже вносят изменения?
                    Typescript / Flow знает о структуре данных.
                    — вы путаете мягкое и теплое, типизация не добавит вам более безопасных способов работы с данными, в отличии от MST, который и в рантайме будет это делать.
                    Вообщем в дальнейшей дискуссии не вижу смысла. И доказывать вам что-то, тоже не вижу смысла.
                    т.е. вы видите смысл в разбрасывании голословными утверждениями вроде
                    Вообщем mobx-state-tree нафиг не нужен, он только мешает и делает код придурковатым, вместо того чтобы писать код максимально простым и понятным.
                    не приведя никаких аргументов?
                    я тоже не собираюсь вам ничего доказывать, это комментарии для новичков, которые только начинают погружаться в реакт, но нахожу несколько странным вашу позицию вроде «статья не моя, я лишь перевел, и все равно что там написано — я не при делах, и вообще пишу код не так как предлагают в этой статье».
              0
              Внимательнее глянул ваш пример, вы сами хуки пользуете. А новичкам предлагаете примеры с классами. Почему так?
                0
                Возможно из-за того, что в проектах, которые старше 2018 года используются классовые компоненты, а таких проектов много. И вероятность того, что новичка посадят на дебаг старого проекта куда выше, чем вероятность того, что его пустят на новый проект

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое