Государственные машины и пользовательский интерфейс: рендеринг на основе состояний "уровня узла" вместо состояний "листа"

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

Хорошо, к проблеме... Я сейчас работаю над проектом React/Redux. Я принимал конструктивное решение, заключавшееся в том, чтобы управлять состоянием приложения и пользовательским интерфейсом почти полностью с (иерархическими) государственными машинами по ряду причин (которые я не буду вникать).

Я воспользовался Redux для хранения моего дерева состояний в store.machine под названием store.machine. Затем остальные подстанции Redux отвечают за хранение данных приложения. Таким образом, я выделил две проблемы, чтобы они не пересекали границы.

Исходя из этого, я также разделял проблемы на стороне (React) - используя "компоненты состояния" и "компоненты пользовательского интерфейса". Компоненты состояния почти полностью связаны с потоком состояния, а компоненты пользовательского интерфейса - это те компоненты, которые отображаются на экране.

У меня есть три типа компонентов состояния:

  • Узел: этот государственный компонент имеет дело с ветвлением состояния. Он определяет, какой компонент должен быть создан на основе его текущего состояния (формы делегирования).
  • Лист: эта форма состояния существует в листьях дерева состояний. Его задача состоит в том, чтобы отображать компонент пользовательского интерфейса, передавая необходимые "обратные вызовы", ответственные за обновление дерева состояний.
  • Контейнер: этот компонент состояния инкапсулирует компонент узла и интерфейса, который будет отображаться бок о бок.

Для моих обстоятельств нас интересуют только узловые и листовые компоненты. Проблема, с которой я сталкиваюсь, заключается в том, что, хотя компоненты пользовательского интерфейса визуализируются на основе "состояний листа", могут быть сценарии, в которых состояния более высокого уровня могут влиять на то, как должен отображаться пользовательский интерфейс.

Возьмите эту упрощенную государственную структуру: simplified state structure

AppState начинается в состоянии " Home. Когда пользователь нажимает кнопку входа в систему, to_login действие to_login. Редуктор, AppState управлять AppState, получит это действие и установит новое текущее состояние для Login в Login.

Аналогично, после того, как пользователь наберет свои учетные данные и проведет проверку, будет отправлено либо success либо fail действие. Опять же, это получает тот же редуктор, который затем переходит к соответствующему состоянию: User_Portal или Login_Failed.

Структура компонента React будет выглядеть примерно так: React component structure

Наш узел верхнего уровня получает AppState в качестве опоры, проверяет текущее состояние и отображает/делегирует один из компонентов дочернего листа.

Затем компоненты "Лист" отображают конкретные компоненты пользовательского интерфейса, проходящие по обратным вызовам, чтобы позволить им отправлять необходимые действия (описанные выше) для обновления состояния. Пунктирная линия представляет границу между "состоянием" и "ui", и эта граница пересекается только на компонентах Листа. Это позволяет работать независимо от штата и пользовательского интерфейса, и поэтому я хочу что-то поддерживать.

Здесь все становится сложным. Представьте себе, что для аргументации у нас есть состояние верхнего уровня для описания языка, на котором находится приложение, - скажем, на English и French. Наша обновленная структура компонентов может выглядеть так: updated component structure

Теперь наши компоненты пользовательского интерфейса должны отображаться на правильном языке, хотя состояние, описывающее это, не является листом. Компоненты Leaf, которые имеют отношение к отображению пользовательского интерфейса, не имеют понятия о родительских состояниях и, следовательно, не содержат понятия языка, в котором находится приложение. И, таким образом, состояние языка нельзя безопасно передать в пользовательский интерфейс без нарушения модели. Либо можно будет удалить граничную линию состояния/интерфейса, или родительское состояние должно быть передано детям, оба из которых являются ужасными решениями.

Одним из решений является "копировать" AppState дерева AppState для каждого языка, по существу создавая целую новую древовидную структуру на язык... например: whole new tree structure per language

Это почти так же плохое решение, как и два описанных выше, и для управления вещами потребуется увеличение количества компонентов.

Более подходящее решение (по крайней мере, когда речь идет о чем-то вроде языков) заключается в том, чтобы воздерживаться от использования его как "состояния" и вместо этого хранить некоторые "данные" об этом. Затем каждый компонент может посмотреть на эти данные (либо значение currentLanguage либо список сообщений, предварительно переведенных на этом языке), чтобы правильно отображать вещи.

Эта проблема "языков" не очень хороший пример, потому что ее можно легко структурировать как "данные", а не "состояние". Но это послужило способом продемонстрировать мою загадку. Возможно, лучшим примером является экзамен, который может быть приостановлен. Давайте посмотрим: exam that can be paused

Представьте себе, что у экзамена два вопроса. Когда в состоянии "приостановлено" текущий вопрос отключается (т.е. Взаимодействие с пользователем не может быть выполнено). Как вы можете видеть выше, нам нужно "дублировать" листья для каждого вопроса в разделе " Playing и " Paused чтобы можно было передать правильное состояние - что-то нежелательное из-за причин, о которых я упоминал ранее.

Опять же, мы могли бы хранить логическое значение где-то, что описывает состояние экзамена - то, что могут выполнить опросы компонентов UI (Q1 и Q2). Но, в отличие от примера "Языки", это логическое значение - это "состояние", а не часть "данных". И поэтому, в отличие от языков, этот сценарий требует, чтобы это состояние хранилось в дереве состояний.

Именно такой сценарий меня озадачил. Какие решения или варианты у меня есть, что может позволить мне передать мои вопросы при использовании информации о состоянии нашего приложения, которое не содержится в Листе?


Изменить: приведенные выше примеры используют FSM. В моей заявке я создал еще несколько достижений государственных машин:

  • MSM (многогосударственный автомат): контейнер для нескольких состояний, которые активны одновременно
  • DSM (динамический конечный автомат): FSM, который настраивается во время выполнения
  • DMSM (динамическая многостанционная машина): МСМ, который настраивается во время выполнения

Если любой из этих типов государственных машин может помочь решить проблему, пожалуйста, сообщите мне об этом.

Любая помощь высоко ценится!


@JonasW. Вот структура, использующая МСМ: the structure utilizing an MSM

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

Ответ 1

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

Возьмем вашу проблему с того момента, когда у вас возникнут реальные проблемы, дерево компонентов экзамена. Exam component tree

Как вы заявили, проблема в том, что вам нужно копировать свои листья в каждом возможном "состоянии узла".

Что делать, если вы можете сделать некоторые данные доступными для любого из компонентов в дереве? Для меня это звучит как проблема, которая может использовать Контекстный API, который предоставляет React 16+.

В вашем случае я создам Провайдера, который обертывает все мое приложение/ветвь дерева, которое меня интересует в обмене контекстом: App with context

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

Затем для ваших компонентов пользовательского интерфейса просто осталось оставить логику для обработки состояния пользовательского интерфейса, предоставленного или рассчитанного с заданным контекстом. Остальная часть приложения может сохранять структуру без усложнения нижних уровней или дублирующих узлов, вам просто нужно добавить оболочку (Поставщик), чтобы сделать доступным контекст.

Некоторые примеры использования этого человека:

Материал UI <- Они передают тему как контекст и получают доступ к ней всякий раз и везде (тема может также изменяться динамически). Очень похоже на случай локали, который вы показали. WithStyles - это HOC, который связывает компонент с темой в состоянии. Чтобы упростить:

ТемаProvider содержит данные темы. Под ним могут быть Routes, Switch, Connected компоненты (очень похоже на ваши узлы, если я правильно понял). И тогда у вас есть компоненты, которые используются с withStyles, имеют доступ к данным темы или могут использовать данные темы для вычисления чего-то, и они вставляются в компонент в качестве опоры. ***

И только для того, чтобы закончить, я могу создать вид реализации в нескольких строках (я не пробовал, но это просто для объяснения с использованием объяснения контекста):

QuestionStateProvider

export const QuestionState = React.createContext({
  status: PLAYING,
  pause: () => {},
});

AppContainer

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      status : PLAYING,
    };

    this.pause = () => {
      this.setState(state => ({
        status: PAUSE,
      }));
    };
  }

  render() {
    return (
      <Page>
        <QuestionState.Provider value={this.state}>
          <Routes ... />
          <MaybeALeaf />
        </ThemeContext.Provider>
        <Section>
          <ThemedButton />
        </Section>
      </Page>
    );
  }
}

Лист - это просто контейнер, который получает вопросы от государства и ставит вопрос или даже больше...

Q1

function Question(props) {
  return (
    <ThemeContext.Consumer>
      {status => (
        <button
          {...props}
          disable={status === PAUSED}
        />
      )}
    </ThemeContext.Consumer>
  );
}

Надеюсь, я правильно понял ваш вопрос, и мои слова достаточно ясны.

Поправьте меня, если я вас неправильно понял или вы хотите обсудить дальше.

*** Это крайне неопределенное и общее объяснение того, как работает материал