在Thermite

时间:2017-01-21 07:18:22

标签: reactjs frp event-delegation purescript lenses

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中。为此,我们利用两个镜头incButtondecButton,以及两个棱镜_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

我们将在计数器的performActionrender函数中使用,使用镜头和棱镜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

这应该是非常直截了当的。当我们真正想要IncrementDecrement时,我们会修改父状态。否则,我们会查看特定于子组件的操作,但只有足够来判断它应该属于谁!当它属于递增或递减按钮时,我们将数据传递给它。

这是我理想情景的设计 - 委托决定&#34;详细信息&#34;后来,通过多态,让组合处理管道。但是,在测试时,反应似乎并没有调度子组件的父母行为。我不确定这是dispatch的设计方式,也不确定问题是什么,但我a git repo提供了错误的最小例子。

1 个答案:

答案 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!