Как организовать состояние 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),
            };
        //
    }
}

использование дискриминируемых союзов дополнительно обеспечивает безопасность типа для родительских и дочерних редукторов.