Привет, Хабр! Представляю вашему вниманию перевод статьи «Controlled and uncontrolled form inputs in React don't have to be complicated» автора Gosha Arinich.
Возможно, вы видели много статей, говорящих: “вы не должны использовать “setState”", в то время, когда документы утверждают, что “refs — это плохо”. Всё это очень противоречиво. Иногда, трудно понять, как сделать все правильно, а так же каковы критерии выбора между этими методами.
Так как же делать делать формы? В конце концов, формы занимают центральное место во многих веб-приложениях. И все же, обработка формы в React является краеугольным камнем, не так ли?
Однако все не так сложно. Позвольте мне показать вам различия между этими подходами, а также то, когда вы должны использовать каждый из них.
Неуправляемые компоненты похожи на обычные HTML-формы:
Они запоминают всё, что вы печатали. Затем вы можете получить их значение, используя ref. Например, в обработчике onClick:
Другими словами, вам необходимо «вытащить» значения из поля, когда вам это нужно. Это можно сделать при отправке формы.
Это самый простой способ реализации форм. Конечно, должны быть веские основания для его использования, а именно: самые простейшие формы либо во время изучения React.
Однако этот способ не такой гибкий, поэтому давайте лучше посмотрим на управляемые компоненты.
Управляемый компонент принимает свое текущее значение в качестве пропсов, а также коллбэк для изменения этого значения. Вы можете сказать, что это более “реактивный” способ управления компонентом, однако это не означает, что вы всегда должны использовать этот метод.
Это все очень хорошо, но значение формы ввода должно существовать в неком state. Как правило, компонент, который рэндерит форму ввода (т.е. форма), сохраняет их в своем state:
(Конечно, он может находиться в state другого компонента или даже в отдельном хранилище состояний, например Redux).
Каждый раз, когда вы вводите новый символ, вызывается handleNameChange. Он принимает новое значение формы ввода и записывает его в state.
![image](https://d33wubrfki0l68.cloudfront.net/d92387dc57d2689c2b02e5f511b2b13c6b22433b/dbafb/assets/images/controlled-vs-uncontrolled/controlled-flow.png)
Этот поток словно «заталкивает» изменения в форму, именно поэтому компонент всегда имеет текущее значение входных данных, не требуя явного запроса на обновление.
Это означает, что ваши данные (state) и пользовательский интерфейс (форма ввода) всегда синхронизированы. State дает значение форме, в то время как форма изменяет текущее значение state.
Это также означает, что формы могут незамедлительно реагировать на изменения, что в свою очередь необходимо для:
Но если вам это не нужно и вы считаете, что неуправляемые компоненты намного проще, то используйте их.
Конечно, есть и другие элементы формы, такие как: checkboxes, radio, textarea и select.
Компонент становится управляемым, когда вы устанавливаете его значение используй props. Вот и всё.
Однако каждый из элементов формы имеет различные способы установки значения, поэтому вот небольшая таблица для общего понимания:
Как управляемые, так и неуправляемые компоненты имеют свои достоинства и недостатки. Оцените конкретную ситуацию и выберите подход — если для вас это работает, то почему бы не воспользоваться этим?
Если ваша форма невероятно проста с точки зрения взаимодействия с пользовательским интерфейсом, то неуправляемые компоненты прекрасно подойдут вам. Вам не обязательно слушать то, что различные статьи говорят, что это плохо.
![image](https://i.imgur.com/D5u8pqE.png)
Кроме того, выбор типа компонента — это не то решение, которое принимается раз и навсегда: вы всегда можете заменить неуправляемые компоненты на управляемые. Переход от одного к другому не так уж и сложен.
Возможно, вы видели много статей, говорящих: “вы не должны использовать “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](https://d33wubrfki0l68.cloudfront.net/d92387dc57d2689c2b02e5f511b2b13c6b22433b/dbafb/assets/images/controlled-vs-uncontrolled/controlled-flow.png)
- Все начниается с пустой строки — ‘’;
- Вы вводите букву ‘a’, и handleNameChange получает её и вызывает setState. Затем форма ввода заново рендерится со значением ‘a’;
- Вы вводите букву ‘b’, и handleNameChange получает значение ‘ab’ и устанавливает его в state. Форма ввода опеть рендерится, но теперь со значением ‘ab’.
Этот поток словно «заталкивает» изменения в форму, именно поэтому компонент всегда имеет текущее значение входных данных, не требуя явного запроса на обновление.
Это означает, что ваши данные (state) и пользовательский интерфейс (форма ввода) всегда синхронизированы. State дает значение форме, в то время как форма изменяет текущее значение state.
Это также означает, что формы могут незамедлительно реагировать на изменения, что в свою очередь необходимо для:
- быстрой обратной связи, например валидации;
- отключения определенной кнопки, до тех пор пока все поля формы не будут валидны;
- обеспечения обработки определенных форматов полей ввода, например номеров кредитных карт.
Но если вам это не нужно и вы считаете, что неуправляемые компоненты намного проще, то используйте их.
Что делает компонент “управляемым”?
Конечно, есть и другие элементы формы, такие как: checkboxes, radio, textarea и select.
Компонент становится управляемым, когда вы устанавливаете его значение используй props. Вот и всё.
Однако каждый из элементов формы имеет различные способы установки значения, поэтому вот небольшая таблица для общего понимания:
Элемент | Значение | Коллбэк для изменения | Новое значение в коллбэке |
---|---|---|---|
|
value=«string» | onChange | event.target.value |
|
checked={boolean} | onChange | event.target.checked |
|
checked={boolean} | onChange | event.target.checked |
|
value=«string» | onChange | event.target.value |
|
value=«option value» | onChange | event.target.value |
Выводы
Как управляемые, так и неуправляемые компоненты имеют свои достоинства и недостатки. Оцените конкретную ситуацию и выберите подход — если для вас это работает, то почему бы не воспользоваться этим?
Если ваша форма невероятно проста с точки зрения взаимодействия с пользовательским интерфейсом, то неуправляемые компоненты прекрасно подойдут вам. Вам не обязательно слушать то, что различные статьи говорят, что это плохо.
![image](https://i.imgur.com/D5u8pqE.png)
Кроме того, выбор типа компонента — это не то решение, которое принимается раз и навсегда: вы всегда можете заменить неуправляемые компоненты на управляемые. Переход от одного к другому не так уж и сложен.
Alexprintme
А где же хуки?
MaZaAa
Какая разница, хуки или нет, суть не в этом.
Alexprintme
Статья для начинающих же, почему бы и не показать, тем более и кода меньше получится.
И есть проблемы с асинхронным обновлением стейта.
Вот цитата со страницы реакта:
Вот ваш компонент на хуках:
Ну разве не красота?
MaZaAa
Нет) В реальной жизни при использовании реактовского стейт менеджмента получается убожество во всей красе.
А красота это вот: codesandbox.io/s/cool-leaf-xnh59?file=/src/App.tsx
Alexprintme
Ну вот, вы же сами про реактовский стейт менеджмент статью писали (кстати форму валидировать самое то, разве нет? а после валидации и модель обновить. Да и переиспользуемые компоненты без локального стора сложновато написать. Да и бывают свойства в компоненте отвечающие чисто за косметику, не тянуть же их в глобальный стор), а хуки лично мне много приятнее использовать, в сравнении с классами (после них и мысли не возникает использовать классы). А теперь отходите в сторону. Я согласен, что есть решения для глобального стейта и получше (реакт же позиционируется как бы для презентаций), но раз пошел такой разговор, то еще лучшее mobx-state-tree. Там и патчи (очень помогает если вы свою модель с сервером синхронизируете, когда один документ редактируют несколько пользователей одновременно например), и снапшуты, и много чего еще вкусного.
MaZaAa
Пример который я скинул, там именно локальный стейт компонента, а не глобальный. MobX заменяет идеально и тот и другой.
В mobx есть autorun и reaction — пожалуйста, синхронизируйтесь с чем угодно, хоть с сервером, хоть с query параметрами урла. Так же используя их делайте снапшоты сколько угодно и как угодно. mobx-state-tree не дает ничего крутого на самом деле, вам только может так казаться, но это связано с тем что попросту можете не знать возможности mobx'a. Вообщем mobx-state-tree нафиг не нужен, он только мешает и делает код придурковатым, вместо того чтобы писать код максимально простым и понятным. Любые ваши желания и фантазии можно с легкостью реализовать с помощью mobx'a. А для описания моделей более чем достаточно и более правильно использовать Typescript.
Я использую хук useState только для того, чтобы получить инстанс MobX класса, чтобы это был локальный стейт компонента, а не потому что я использую реактовский стейт менеджмент на хуках.
Alexprintme
MST реализовал все велосипеды, которые вы скорее всего напишете поверх mobX, если столкнетесь с более сложными структурами данных, кстати он тоже написан поверх mobX. Эти подходы идут по возрастании сложности данных: стейт реакта-> mobX-> MST. Кстати Typescript тоже поддерживается в MST.
Вот простой пример. Допустим у нас есть документ с данными, он хранится в массиве на сервере. С ним одновременно работают несколько человек в реальном времени. Один пользователь поменял у себя в модели какой-то элемент массива. Вопрос: как вы передадите изменения от этого пользователя всем остальным? как организовать удаление/обновление/создание новых элементов сразу у всех пользователей данного документа (будете писать экшены в духе редакса?)? а когда сложность и глубина структуры данных начнет расти?
MST знает о структуре данных, в отличие от mobX, что сильно упрощает работу с ними.
И про хуки. Ваша статья называется «Контролируемые и неконтролируемые компоненты в React не должны быть сложными». функции + хуки -> меньше кода, выше наглядность -> снижение сложности.
MaZaAa
Ну вообще это элементарно, когда под рукой есть Javascript и WebSocket's(не обязательно, но с ними гораздо лучше). И без разница какой уровень вложенности в ваших массивах и объектах.
Typescript / Flow знает о структуре данных.
Это не моя статья =)
Вообщем в дальнейшей дискуссии не вижу смысла. И доказывать вам что-то, тоже не вижу смысла. Пользуйтесь чем угодно на здоровье и как угодно, все равно мне с вашим кодом не работать =)
P.S. в духе редакса я ничего не пишу и не делаю, этот «дух» мне противен и меня удивляет что до сих пор в новых проектах кто-то берет Redux и пишет эту кашу с лапшой.
Alexprintme
— вы путаете мягкое и теплое, типизация не добавит вам более безопасных способов работы с данными, в отличии от MST, который и в рантайме будет это делать.
т.е. вы видите смысл в разбрасывании голословными утверждениями вроде не приведя никаких аргументов?
я тоже не собираюсь вам ничего доказывать, это комментарии для новичков, которые только начинают погружаться в реакт, но нахожу несколько странным вашу позицию вроде «статья не моя, я лишь перевел, и все равно что там написано — я не при делах, и вообще пишу код не так как предлагают в этой статье».
Alexprintme
Внимательнее глянул ваш пример, вы сами хуки пользуете. А новичкам предлагаете примеры с классами. Почему так?
maks_danilau Автор
Возможно из-за того, что в проектах, которые старше 2018 года используются классовые компоненты, а таких проектов много. И вероятность того, что новичка посадят на дебаг старого проекта куда выше, чем вероятность того, что его пустят на новый проект