状态机和UI:基于“节点级”状态而非“叶”状态的渲染

时间:2018-07-06 14:34:00

标签: javascript reactjs redux react-redux state-machine

在继续之前,我想指出这个问题的标题很难表达。如果应该使用更合适的标题,请告诉我,以便我可以对其进行更改,并使该问题对其他人更有用。

好,问题解决了……我目前正在研究React / Redux项目。我做出的一个设计决策是,出于多种原因(我将不进行深入研究),几乎完全使用(分层)状态机来管理应用程序状态和UI。

我利用Redux将状态树存储在名为store.machine的子状态中。然后,其余的Redux子状态负责存储应用程序“数据”。通过这种方式,我将两个问题分离开了,以使它们不会越过边界。

除此之外,我还使用(状态组件)和[UI组件]在(React)方面分离了关注点。状态组件几乎完全处理状态流,而UI组件是那些在屏幕上呈现的组件。

我有三种类型的状态分量:

  • 节点:这种状态组件处理状态分支。它根据其当前状态(一种委托形式)确定应呈现的组件。
  • :这种状态组件存在于状态树的叶子处。它的工作只是呈现UI组件,并传递负责更新状态树的必要“ dispatch”回调。
  • 容器:这种状态组件封装了一个 Node UI 组件,以使其并排呈现。

对于我来说,我们只关心 Node Leaf 组件。我遇到的问题是,虽然UI组件是基于“叶子状态”呈现的,但是在某些情况下,“更高级别”的状态可能会影响UI的呈现方式。

采用以下简化的状态结构: simplified state structure

AppStateHome状态开始。当用户单击登录按钮时,将调度to_login操作。负责管理AppState的减速器将收到此操作,并将新的当前状态设置为Login

同样,在用户键入其凭据并完成验证之后,将分派successfail动作。同样,这由同一减速器拾取,然后减速到User_PortalLogin_Failed的适当状态。

React组件结构如下所示: React component structure

我们的顶级 Node 接收AppState作为道具,检查当前状态并渲染/委派给子 Leaf 子组件之一。

Leaf 组件随后呈现通过回调传递的具体UI组件,以允许它们调度必要的操作(如上所述)以更新状态。虚线表示“状态”和“ ui”之间的边界,并且该边界仅在 Leaf 组件处交叉。这样就可以独立地在 State UI 上工作,因此,我希望对此进行维护。

这是棘手的地方。想象一下,为了争辩,我们有一个顶级状态来描述应用程序所使用的语言-假设English和{{1 }}。我们更新后的组件结构可能如下所示: updated component structure

现在,即使描述该状态的不是 Leaf ,我们的UI组件也必须以正确的语言呈现。用于处理UI呈现的 Leaf 组件没有父状态的概念,因此也没有应用程序所使用语言的概念。因此,语言状态无法安全地传递给用户界面,而无需破坏模型。要么必须删除状态/ UI边界线,要么需要将父状态传递给子级,这都是很糟糕的解决方案。

一种解决方案是为每种语言“复制” French树结构,实质上是为每种语言创建一个全新的树结构……如下所示: whole new tree structure per language

这几乎和我上面提到的两个解决方案一样糟糕,并且需要不断增加的组件数量来管理事物。

更合适的解决方案(至少在处理诸如语言之类的东西时)是避免将其用作“状态”,而是保留一些有关它的“数据”。然后,每个组件都可以查看此数据(AppState值或使用该语言预先翻译的消息列表),以便正确呈现内容。

这个“语言”问题不是一个很好的例子,因为它可以很容易地构造为“数据”而不是“状态”。但这是证明我的难题的一种方式。也许更好的例子是可以暂停的考试。让我们来看看: exam that can be paused

让我们想象一下考试有两个问题。处于“已暂停”状态时,当前问题将被禁用(即无法进行用户互动)。如您在上面看到的,我们需要为currentLanguagePlaying下的每个问题“复制”叶子,以便传递正确的状态-由于我之前提到的原因,这是不希望的。

同样,我们可以在描述考试状态的某个地方存储一个布尔值-UI组件(Q1和Q2)可以轮询的布尔值。但是与“语言”示例不同,此布尔值在很大程度上是一个“状态”,而不是某些“数据”。因此,与语言不同,这种情况要求将该状态保留在状态树中。

正是这种情况让我感到困惑。我有什么解决方案或选项可以使我在使用未包含在叶子中的有关我们应用状态的信息时提出问题?


编辑:以上示例均使用FSM。在我的应用程序中,我创建了一些高级状态机:

  • MSM(多状态机):用于同时处于活动状态的多个状态机的容器
  • DSM(动态状态机):在运行时配置的FSM
  • DMSM(动态多状态机):一种在运行时进行配置的MSM

如果这些状态机中的任何一种都可以帮助解决我的问题,请随时告诉我。

非常感谢您的帮助!


@JonasW。这是利用MSM的结构: the structure utilizing an MSM

这种结构仍然不允许我将“可暂停的”状态信息传递给问题。

1 个答案:

答案 0 :(得分:1)

让我们尝试为您的体系结构问题提出解决方案。由于我对您的问题并不完全自信,因此不确定是否会令人满意。

让我们从您开始遇到实际问题的那一刻开始,即考试组件树。 Exam component tree

正如您所说,问题在于您需要在每个可能的“节点状态”中复制叶子。

如果您可以使树中的任何组件都可以访问某些数据该怎么办?对我来说,这听起来像是一个问题,可以使用React 16+提供的Context API

在您的情况下,我将创建一个Provider,该Provider封装了我希望与之共享上下文的树的整个应用程序/分支。 App with context

通过这种方式,您可以从任何组件访问上下文,并且可以modified dynamically并通过redux访问。

然后剩下的就是UI组件,以保持处理与给定上下文提供或计算的UI状态的逻辑。该应用程序的其余部分可以保持其结构,而无需使较低级别复杂化或复制节点,您只需添加一个包装器(提供程序)即可使上下文可用。

一些使用此方法的人的例子:

Material UI <-他们将主题作为上下文传递并随时随地访问(主题也可以动态更改)。与您显示的语言环境非常相似。 WithStyles是将状态中的组件链接到主题的HOC。这样简化:

ThemeProvider具有主题数据。在其下可以有路由,交换机,已连接的组件(如果我理解正确的话,与您的节点非常相似)。然后,您可以使用与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>
    );
  }
}

叶子-它只是一个容器,可从州获取问题并提出问题或更多...

第一季度

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

希望您的问题正确无误,并且我的话语很清楚。

如果我对您的理解不正确,或者您想进一步讨论,请纠正我。

*** 这是关于材料用户界面主题工作原理的非常模糊和笼统的解释