javascript - 状态机和 UI:基于“节点级”状态而不是“叶”状态进行渲染
问题描述
在继续之前,我想指出这个问题的标题很难用词来表达。如果应该使用更合适的标题,请告诉我,以便我可以更改它并使这个问题对其他人更有用。
好的,关于这个问题……我目前正在做一个 React/Redux 项目。我做出的一个设计决定是几乎完全使用(分层)状态机来管理应用程序状态和 UI,原因有很多(我不会深入研究)。
我利用 Redux 将我的状态树存储在一个名为store.machine
. 然后其余的 Redux 子状态负责存储应用程序“数据”。通过这种方式,我将这两个关注点分开,这样它们就不会跨越界限。
在此基础上,我还分离了(React)方面的关注点——使用“状态组件”和“UI 组件”。状态组件几乎完全处理状态流,而 UI 组件是那些在屏幕上呈现的组件。
我有三种类型的状态组件:
- Node:这种状态组件处理状态分支。它根据组件的当前状态(一种委托形式)确定应该呈现哪个组件。
- Leaf:这种状态组件存在于状态树的叶子上。它的工作只是渲染一个 UI 组件,传递负责更新状态树的必要“调度”回调。
- Container:这种状态组件封装了一个Node和UI组件以并排呈现。
在我的情况下,我们只关心Node和Leaf组件。我遇到的问题是,虽然 UI 组件是基于“叶状态”呈现的,但在某些情况下,“更高级别”状态可能会影响 UI 的呈现方式。
AppState
在该Home
州开始。当用户单击登录按钮时,将to_login
调度一个操作。负责管理的 reducerAppState
将收到此操作并将新的当前状态设置为Login
。
同样,在用户键入他们的凭据并完成验证后,将分派asuccess
或action。fail
同样,这被同一个 reducer 拾取,然后继续切换到适当的状态:User_Portal
或Login_Failed
.
我们的顶级节点AppState
作为道具接收,检查当前状态是什么,并渲染/委托给子叶组件之一。
Leaf组件然后渲染具体的 UI 组件,并传递回调,以允许它们调度必要的操作(如上所述)来更新状态。虚线表示“状态”和“用户界面”之间的边界,该边界仅在叶组件处交叉。这使得独立处理State和UI成为可能,因此是我想要维护的东西。
这就是事情变得棘手的地方。想象一下,为了论证,我们有一个顶级状态来描述应用程序所使用的语言——比如说English
and French
。我们更新的组件结构可能如下所示:
现在我们的 UI 组件必须以正确的语言呈现,即使描述它的状态不是Leaf。处理 UI 渲染的Leaf组件没有父状态的概念,因此没有应用程序所在语言的概念。因此,语言状态不能在不破坏模型的情况下安全地传递给 UI。要么必须删除状态/UI 边界线,要么需要将父状态传递给子状态,这两者都是糟糕的解决方案。
一种解决方案是“复制”每种语言的AppState
树结构,本质上是为每种语言创建一个全新的树结构……就像这样:
这几乎与我上面描述的两个解决方案一样糟糕,并且需要越来越多的组件来管理事物。
更合适的解决方案(至少在处理语言之类的东西时)是避免将其用作“状态”,而是保留一些关于它的“数据”。然后,每个组件都可以查看此数据(一个currentLanguage
值或以该语言预翻译的消息列表)以正确呈现事物。
这个“语言”问题不是一个很好的例子,因为它可以很容易地构造为“数据”而不是“状态”。但它作为一种方式来证明我的难题。也许一个更好的例子是可以暂停的考试。让我们来看看:
假设考试有两个问题。当处于“暂停”状态时,当前问题被禁用(即无法进行用户交互)。正如你在上面看到的,我们需要为下面的每个问题“复制”叶子,Playing
以便Paused
可以传递正确的状态——由于我之前提到的原因,这是不受欢迎的。
同样,我们可以在某处存储一个描述考试状态的布尔值——UI 组件(Q1 和 Q2)可以轮询的东西。但与“语言”示例不同的是,这个布尔值在很大程度上是一个“状态”,而不是一些“数据”。因此与语言不同的是,这种情况要求将此状态保存在状态树中。
正是这种情景让我难过。我有哪些解决方案或选项可以让我在利用未包含在 Leaf 中的有关我们应用程序状态的信息时呈现我的问题?
编辑:以上示例均使用 FSM。在我的应用程序中,我创建了一些更先进的状态机:
- MSM(多状态机):同时处于活动状态的多个状态机的容器
- DSM(动态状态机):在运行时配置的 FSM
- DMSM(动态多状态机):在运行时配置的 MSM
如果这些类型的状态机中的任何一种可以帮助解决我的问题,请随时告诉我。
任何帮助深表感谢!
这样的结构仍然不允许我将“可暂停”状态信息传递给问题。
解决方案
让我们尝试为您的架构问题提出一个解决方案。不确定它是否会令人满意,因为我对我对您的问题的理解并不完全有信心。
正如您所说,问题是您需要在每个可能的“节点状态”中复制您的叶子。
如果您可以使树中的任何组件都可以访问某些数据怎么办?对我来说,这听起来像是一个可以使用React 16+ 提供的Context API的问题。
在您的情况下,我将创建一个 Provider 来包装我有兴趣与之共享上下文的整个应用程序/树的分支:
通过这种方式,您可以从任何组件访问您的上下文,并且可以通过 redux动态修改它。
然后留给你的 UI 组件来保持逻辑来处理提供的 UI 状态或使用给定的上下文计算。应用程序的其余部分可以保持其结构,而不会使较低级别或重复节点复杂化,您只需添加一个包装器(提供者)以使上下文可用。
人们使用它的一些例子:
Material UI <- 他们将主题作为上下文传递并随时随地访问它(主题也可以动态更改)。与您展示的语言环境案例非常相似。WithStyles是一个将组件链接到状态中的主题的 HOC。所以简化了:
ThemeProvider 有主题数据。在它下面可以有 Routes、Switch、Connected 组件(如果我理解正确的话,与您的节点非常相似)。然后你有与 withStyles 一起使用的组件可以访问主题数据或可以使用主题数据来计算某些东西,并将其作为道具注入组件中。***
最后,我可以用几行草拟一个实现(我没有尝试过,但它只是为了使用上下文解释进行解释):
QuestionStateProvider
export const QuestionState = React.createContext({
status: PLAYING,
pause: () => {},
});
应用容器
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>
);
}
}
Leaf - 它只是一个从状态获取问题并呈现问题或更多问题的容器......
第一季度
function Question(props) {
return (
<ThemeContext.Consumer>
{status => (
<button
{...props}
disable={status === PAUSED}
/>
)}
</ThemeContext.Consumer>
);
}
我希望我的问题是正确的,并且我的话足够清楚。
如果我对您的理解有误或您想进一步讨论,请纠正我。
***这是对 Material ui 主题如何工作的极其模糊和笼统的解释
推荐阅读
- android-studio - Android Studio 3.5.1 中的软包装编辑器
- react-native - 是否可以自定义 react-native-media-controls 样式?
- tastypie - 如何使用一个 URI 获取多个资源?
- c# - Crystal Reports - 连接到另一个项目中的数据源
- ibm-watson - IBM watson 助手:将 IBM watson 聊天机器人与 android API 17 集成
- python-3.x - django-restframework 中的图像上传 crud 操作
- amazon-web-services - 如何使用一个 ansible 命令重置多个 AWS 帐户的 IAM 用户密码
- android - CollapsingToolbarLayout 过度滚动并卡住
- c# - 如何将列表从方法传递到另一个方法
- docker - 链接命令时 Docker 运行使用主机 PATH