Как организовать состояние Redux для повторно используемых компонентов?
TL; DR: в случае многоразового компонента, который имеет некоторую сложную логику для управления собственным состоянием (подумайте: текстовое поле комментария facebook с автокомплетером, emoji и т. д.), Как использовать магазин, действия и редукторы для управления состоянием нескольких экземпляров этого компонента, разбросанных по всему веб-сайту?
рассмотрим реальный пример из официального РЕПО redux. В нем мы имеем:
- a RepoPage, который отображает список пользователей, которые снялись в определенном РЕПО,
- a UserPage, который отображает список репозиториев, которые помечены определенным пользователем
- a список, который является достаточно общим, чтобы он мог отображать список пользователей или репозиториев, при условии
items
и способrenderItem
. В частностиRepoPage
используетUser
компонент для отображения каждого из пользователей, которые снялись РЕПО, иUserPage
используетRepo
компонент для отображения каждого из Звездных РЕПО.
предположим, что я действительно хочу!--21-->все государства, чтобы быть в Redux.
в частности, я хочу, чтобы состояние каждого списка на каждом RepoPage и UserPage управлялось Redux. Об этом уже позаботилось в Примере умное трехуровневое глубокое дерево:
- на верхнем уровне ключ говорит, какие это данные компонента (в Примере он называется
store.pagination
) - затем для каждого конкретного типа контекста существует ветвь, в которой компонент может быть (
store.pagination.starredByUser
,store.pagination. stargazersByRepo
) - тогда существует столько ключей, сколько уникальных контекстов (
store.pagination.starredByUser[login]
,store.pagination. stargazersByRepo[repo]
)
я чувствую, что эти три уровня соответствуют также: тип компонента, родительский тип, Родительский идентификатор.
но я не знаю, как расширить эту идею, чтобы справиться с делом, в котором у самого компонента списка было много детей, с состоянием, которое стоит отслеживать Возвращение.
в частности, я хочу знать, как реализовать решение, в котором:
-
User
компонент остается неизменным -
Repo
компонент имеет кнопку, которая переключает свой цвет фона - состояние каждого
Repo
компонент управляется Redux
(я рад использовать некоторые расширения для Redux, которые все еще используют редукторы, но не хотят идти с "просто держать его в локальном состоянии React", для цель этого вопроса)
мои исследования до сих пор:
- похоже, что в Elm действия (сообщения) являются алгебраическими типами данных, которые могут быть вложены таким образом, что родительский компонент может распаковать "внешний конверт" сообщения и доставить внутреннее действие, предназначенное для дочернего редуктора (updater).
- поскольку это соглашение в Redux использовать строку в качестве типа действия, естественный перевод выше идея заключается в использовании префиксов, и это, кажется, то, что присм (foremly известный как redux-elm) делает:
action.type
состоит из подстрок, которые указывают путь через дерево компонентов. ОТОХ в комментарий автор prism томкис объясняет, что самая важная часть архитектуры вяза, которую Redux отсутствует, - это композиция действий - два вышеуказанных подхода, по-видимому, являются расширенными версиями подходов, описанных в Повторно Использовать Редуктор Логика!--26-->
- я не совсем понял, как redux-fly работает внутри, но, похоже, использует полезную нагрузку, а не
action.type
чтобы идентифицировать экземпляр компонента по его пути монтирования вstore
что также соответствует пути в дереве компонентов из-за того, как он построен вручную компонентами - WinAPI, который мне кажется очень похож на Redux, если вы косите, использует уникальный
hWnd
идентификатор для каждого элемента управления, который делает его очень легко проверить, еслиaction
был предназначен для вас, и решить, где должно быть ваше состояние вstore
. - вышеуказанная идея, вероятно, может привести к чему-то описанному в предложение/обсуждение документации: повторное использование логики редуктора где каждый тип компонента имеет собственное плоское поддерево, индексируемое уникальным идентификатором.
- другая идея, описанная в связанной нити, связанной выше, - написать редуктор для определенного типа компонента один раз, а затем пусть редуктор для родительского компонента вызывает его (что также означает, что родитель может решить, где в хранилище находится состояние ребенка-опять же, это похоже на архитектуру Elm для меня)
- очень интересная дискуссия подробнее о возможности повторного использования пользовательских компонентов в котором представлены детали предложения, аналогичные приведенному выше
- в частности, на обсуждение содержит предложение пользователем nav, организовать дерево хранения рекурсивно таким образом, чтобы состояние компонента было поддеревом в двух видах ветвей: одна для частного материала, а другая для "таблиц" дочерних компонентов, где каждый класс дочернего компонента имеет свою собственную "таблицу", и каждый экземпляр дочернего компонента имеет уникальный ключ в этой таблице, где его состояние рекурсивно хранится. Уникальные ключи, которые дают доступ к этим детям, хранятся в разделе "частные". Это действительно похоже на то, как я представляю WinAPI :)
- другое предложение, вдохновленное вязом пользователь sompylasar из того же потока должен использовать действия, которые содержат действия для детей в качестве полезной нагрузки в стиле "matrioshka", который, на мой взгляд, имитирует, как конструкторы алгебраических типов вложены в Elm
- redux-подпространство рекомендовано обсуждение глобальных действий для prism, как библиотека, которая вдохновлена Вязом и позволяет вам иметь глобальный действия.
1 ответов
я попытаюсь объяснить одну из идей, которая вдохновлена Elm lang и была портирована на Typescript:
предположим, у нас есть очень простой компонент со следующим состоянием
interface ComponentState {
text: string
}
компонент можно уменьшить с помощью следующих 2 действий.
interface SetAction {
type: 'SET_VALUE', payload: string
}
interface ResetAction {
type: 'RESET_VALUE'
}
Type union для этих 2 действий (пожалуйста, посмотрите на дискриминируемые союзы Typescript):
type ComponentAction = SetAction | ResetAction;
редуктор для этого должен иметь следующую подпись:
function componentReducer(state: ComponentState, action: ComponentAction): ComponentState {
// code
}
теперь чтобы "встроить" этот простой компонент в более крупный компонент, нам нужно инкапсулировать модель данных в родительский компонент:
interface ParentComponentState {
instance1: ComponentState,
instance2: ComponentState,
}
поскольку типы действий в redux должны быть глобально уникальными, мы не можем отправлять отдельные действия для экземпляров компонентов, поскольку они будут обрабатываться обоими экземплярами. Одна из идей заключается в том, чтобы обернуть действия отдельных компонентов в родительское действие со следующей техникой:
interface Instance1ParentAction {
type: 'INSTNACE_1_PARENT',
payload: ComponentAction,
}
interface Instance2ParentAction {
type: 'INSTNACE_2_PARENT',
payload: ComponentAction,
}
родительское объединение действий будет иметь следующую подпись:
type ParentComponentAction = Instance1ParentAction | Instance2ParentAction;
и самое главное в этой технике - родитель редуктор:
function parentComponentReducer(state: ParentComponentState, action: ParentComponentAction): ParentComponentState {
switch (action.type) {
case 'INSTNACE_1_PARENT':
return {
...state,
// using component reducer
instance1: componentReducer(state.instance1, action.payload),
};
//
}
}
использование дискриминируемых союзов дополнительно обеспечивает безопасность типа для родительских и дочерних редукторов.