在继续之前,我想指出这个问题的标题很难表达。如果应该使用更合适的标题,请告诉我,以便我可以对其进行更改,并使该问题对其他人更有用。
好,问题解决了……我目前正在研究React / Redux项目。我做出的一个设计决策是,出于多种原因(我将不进行深入研究),几乎完全使用(分层)状态机来管理应用程序状态和UI。
我利用Redux将状态树存储在名为store.machine
的子状态中。然后,其余的Redux子状态负责存储应用程序“数据”。通过这种方式,我将两个问题分离开了,以使它们不会越过边界。
除此之外,我还使用(状态组件)和[UI组件]在(React)方面分离了关注点。状态组件几乎完全处理状态流,而UI组件是那些在屏幕上呈现的组件。
我有三种类型的状态分量:
对于我来说,我们只关心 Node 和 Leaf 组件。我遇到的问题是,虽然UI组件是基于“叶子状态”呈现的,但是在某些情况下,“更高级别”的状态可能会影响UI的呈现方式。
AppState
以Home
状态开始。当用户单击登录按钮时,将调度to_login
操作。负责管理AppState
的减速器将收到此操作,并将新的当前状态设置为Login
。
同样,在用户键入其凭据并完成验证之后,将分派success
或fail
动作。同样,这由同一减速器拾取,然后减速到User_Portal
或Login_Failed
的适当状态。
我们的顶级 Node 接收AppState
作为道具,检查当前状态并渲染/委派给子 Leaf 子组件之一。
Leaf 组件随后呈现通过回调传递的具体UI组件,以允许它们调度必要的操作(如上所述)以更新状态。虚线表示“状态”和“ ui”之间的边界,并且该边界仅在 Leaf 组件处交叉。这样就可以独立地在 State 和 UI 上工作,因此,我希望对此进行维护。
这是棘手的地方。想象一下,为了争辩,我们有一个顶级状态来描述应用程序所使用的语言-假设English
和{{1 }}。我们更新后的组件结构可能如下所示:
现在,即使描述该状态的不是 Leaf ,我们的UI组件也必须以正确的语言呈现。用于处理UI呈现的 Leaf 组件没有父状态的概念,因此也没有应用程序所使用语言的概念。因此,语言状态无法安全地传递给用户界面,而无需破坏模型。要么必须删除状态/ UI边界线,要么需要将父状态传递给子级,这都是很糟糕的解决方案。
一种解决方案是为每种语言“复制” French
树结构,实质上是为每种语言创建一个全新的树结构……如下所示:
这几乎和我上面提到的两个解决方案一样糟糕,并且需要不断增加的组件数量来管理事物。
更合适的解决方案(至少在处理诸如语言之类的东西时)是避免将其用作“状态”,而是保留一些有关它的“数据”。然后,每个组件都可以查看此数据(AppState
值或使用该语言预先翻译的消息列表),以便正确呈现内容。
这个“语言”问题不是一个很好的例子,因为它可以很容易地构造为“数据”而不是“状态”。但这是证明我的难题的一种方式。也许更好的例子是可以暂停的考试。让我们来看看:
让我们想象一下考试有两个问题。处于“已暂停”状态时,当前问题将被禁用(即无法进行用户互动)。如您在上面看到的,我们需要为currentLanguage
和Playing
下的每个问题“复制”叶子,以便传递正确的状态-由于我之前提到的原因,这是不希望的。
同样,我们可以在描述考试状态的某个地方存储一个布尔值-UI组件(Q1和Q2)可以轮询的布尔值。但是与“语言”示例不同,此布尔值在很大程度上是一个“状态”,而不是某些“数据”。因此,与语言不同,这种情况要求将该状态保留在状态树中。
正是这种情况让我感到困惑。我有什么解决方案或选项可以使我在使用未包含在叶子中的有关我们应用状态的信息时提出问题?
编辑:以上示例均使用FSM。在我的应用程序中,我创建了一些高级状态机:
如果这些状态机中的任何一种都可以帮助解决我的问题,请随时告诉我。
非常感谢您的帮助!
这种结构仍然不允许我将“可暂停的”状态信息传递给问题。
答案 0 :(得分:1)
让我们尝试为您的体系结构问题提出解决方案。由于我对您的问题并不完全自信,因此不确定是否会令人满意。
正如您所说,问题在于您需要在每个可能的“节点状态”中复制叶子。
如果您可以使树中的任何组件都可以访问某些数据该怎么办?对我来说,这听起来像是一个问题,可以使用React 16+提供的Context API。
在您的情况下,我将创建一个Provider,该Provider封装了我希望与之共享上下文的树的整个应用程序/分支。
通过这种方式,您可以从任何组件访问上下文,并且可以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>
);
}
希望您的问题正确无误,并且我的话语很清楚。
如果我对您的理解不正确,或者您想进一步讨论,请纠正我。
*** 这是关于材料用户界面主题工作原理的非常模糊和笼统的解释