Три подхода к распределению состояния по компонентам в React

Я работал над типичным экраном CRUD, когда в сотый раз спросил себя: «Следует ли мне сохранить состояние в этом компоненте или переместить его в родительский?».

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

Давайте рассмотрим это на простом примере вместе с тремя подходами к исправлению. Первые два подхода - обычная практика, третий - менее традиционный.

Эта проблема

Чтобы показать вам, что я имею в виду, я воспользуюсь простым экраном CRUD с книгой (настолько простым, что в нем нет операций создания и удаления).

У нас есть три компонента. <BookList/> - это компонент, который показывает список книг и кнопки для их редактирования. <BookForm/>, который имеет два входа и кнопку для сохранения изменений в книге. И <BookApp/>, который содержит два других компонента.

Итак, какое у нас состояние? Что ж, <BookApp/> следует отслеживать список книг и что-нибудь, чтобы идентифицировать книгу, которую в настоящее время редактируют. <BookList/> не имеет состояния. И <BookForm/> должен сохранять текущее состояние входов, пока не будет нажата кнопка «Сохранить».

Мне кажется, это хорошо, но есть проблема: это не работает.

Мы инициализируем состояние <BookForm/> при создании экземпляра компонента, поэтому, когда другая книга выбирается из списка, родитель не имеет возможности сообщить ему, что ему нужно изменить ее.

Так как же это исправить?

Подход 1: контролируемый компонент

Один из распространенных подходов - поднять состояние, преобразовав <BookForm/> в управляемый компонент. Мы удаляем <BookForm/> состояние, добавляем activeBook к <BookApp/> состоянию и добавляем onChange опору к <BookForm/>, которую мы вызываем каждый раз, когда происходит изменение любого ввода.

Теперь это работает, но мне кажется неправильным поднимать состояние <BookForm/>. <BookApp/> не заботится о каких-либо изменениях в книге, пока пользователь не нажмет «Сохранить», так зачем же ему сохранять ее в собственном состоянии?

Подход 2: синхронизация состояний

Другой вариант - сохранить состояние в <BookForm/>, но синхронизировать его с опорой book всякий раз, когда она изменяется, используя componentWillReceiveProps.

Такой подход часто считается плохой практикой, потому что он противоречит идее React о едином источнике истины. Я не уверен, что это так, однако синхронизировать состояние не всегда просто. Кроме того, я стараюсь по возможности избегать использования методов жизненного цикла.

Подход 3: Компонент, управляемый ключом

Но почему мы пытаемся переработать старое состояние? Разве не имеет смысла создавать новый экземпляр с совершенно новым состоянием каждый раз, когда пользователь выбирает книгу?

Для этого нам нужно сказать React, чтобы он прекратил использование старого экземпляра <BookForm/> и создал новый. Вот для чего нужна опора key.

Если элемент имеет key, отличный от последнего рендера, React создает для него новый экземпляр. Итак, когда пользователь выбирает новую книгу, key из <BookForm/> изменяется, создается новый экземпляр компонента, и состояние инициализируется из реквизита.

В чем подвох? Повторное использование экземпляров компонентов означает меньше изменений в DOM, что означает лучшую производительность. Итак, когда мы заставляем React создать новый экземпляр компонента, мы получаем некоторые накладные расходы на дополнительные мутации DOM. Но эти накладные расходы минимальны для таких случаев, когда key не меняется слишком быстро, а компонент небольшой.

Спасибо за прочтение.