UI设计模式的困难测试结果是一项简单的任务:
Thermite的设计理念对我来说仍然遥不可及,但我想我理解透镜和棱镜如何用于组合 Spec
s,但不是如何调用父母的行动。
此问题是针对版本
0.10.5
编写的,可能会在读者的时间内发生变化。
正在构建的应用程序将是一个简单的计数器,其中增量按钮增加计数值,减量按钮减少它。我们将通过创建通用button
组件,然后在counter
组件中使用多个组件来完成此操作。设计如下:
counter:
- CounterState = { count :: Int
, incButton :: ButtonState
, decButton :: ButtonState
}
- init = { count: 0
, incButton: initButton
, decButton: initButton
}
- CounterAction = Increment
| Decrement
| IncButton (ButtonAction CounterAction)
| DecButton (ButtonAction CounterAction)
button:
- ButtonState = Unit
- initButton = unit
- ButtonAction parentAction = Clicked parentAction
在按钮ButtonAction
中,我说我需要parentAction
才能在Clicked
上执行。我将此保留为类型参数以维护通用接口。但是,这意味着我们必须从某个地方提供一个,所以我在按钮的规范中允许参数:
buttonSpec :: forall eff props parentAction
. { _onClick :: parentAction }
-> Spec eff ButtonState props (ButtonAction parentAction)
buttonSpec parentActions = T,simpleSpec performAction renderButton
这意味着我会在渲染中_onClick
时使用此处提供的dispatch
操作:
renderButton :: Render ButtonState props (ButtonAction parentAction)
renderButton dispatch _ state _ =
[ -- ... html stuff
, button [onClick $ dispatch $ Clicked parentActions._onClick]
[] -- with fill text etc
]
现在,棘手的部分是将两个buttonSpec
集成到一个counterSpec
中。为此,我们利用两个镜头incButton
和decButton
,以及两个棱镜_IncButton
和_DecButton
,它们执行明显的状态和操作相关任务:
counterSpec :: forall eff props
. Spec eff CounterState props CounterAction
counterSpec =
T.simpleSpec performAction render
where
incButton = focus incButton _IncButton
$ buttonSpec Increment
decButton = focus decButton _DecButton
$ buttonSpec Decrement
我们将在计数器的performAction
和render
函数中使用,使用镜头和棱镜Thermite提供烘焙:
render :: Render CounterState props CounterAction
render dispatch props state children =
[ text $ "Count: " <> state.count
] <> (incButton ^. _render) dispatch props state children
<> (decButton ^. _render) dispatch props state children
performAction :: PerformAction eff CounterState props CounterAction
performAction Increment _ _ =
modifyState $ count %~ (\x -> x + 1)
performAction Decrement _ _ =
modifyState $ count %~ (\x -> x - 1)
performAction action@(IncButton _) props state =
(incButton ^. _performAction) action props state
performAction action@(DecButton _) props state =
(decButton ^. _performAction) action props state
这应该是非常直截了当的。当我们真正想要Increment
或Decrement
时,我们会修改父状态。否则,我们会查看特定于子组件的操作,但只有足够来判断它应该属于谁!当它属于递增或递减按钮时,我们将数据传递给它。
这是我理想情景的设计 - 委托决定&#34;详细信息&#34;后来,通过多态,让组合处理管道。但是,在测试时,反应似乎并没有调度子组件的父母行为。我不确定这是dispatch
的设计方式,也不确定问题是什么,但我a git repo提供了错误的最小例子。
答案 0 :(得分:2)
我在你的GitHub回购中创建了一个pull request。我认为你通过尝试将动作传递给Button来使事情过于复杂。更符合Thermite做事方式的是让Button发出他的动作,然后在父组件中使用Prism将Button的动作映射到父动作空间。
所以不要对父母进行4次操作,而只需要2:
data ParentAction = Increment | Decrement
然后通过递增或递减状态中的计数器来处理这些通常的方法。现在,为了使用您的按钮触发这些ParentAction
,您可以从按钮发出简单的Clicked
动作,并使用棱镜将这些动作映射到增量或减量:
_IncButton :: Prism' CounterAction ButtonAction
_IncButton = prism' (const Increment) $ case _ of
Increment -> Just Clicked
_ -> Nothing
_DecButton :: Prism' CounterAction ButtonAction
_DecButton = prism' (const Decrement) $ case _ of
Decrement -> Just Clicked
_ -> Nothing
现在剩下的就是使用这些Prism专注于右键单击操作:
inc = T.focus incButton _IncButton $ buttonSpec {_value: "Increment"}
dec = T.focus decButton _DecButton $ buttonSpec {_value: "Decrement"}
并且vóila!