Thermite 中的相邻组件交互

Adjacent component interaction in Thermite

UI 设计模式的艰难测试结果是一项简单的任务:

Thermite的设计理念对我来说还是有点遥不可及,不过我想我明白了透镜和棱镜是如何用来组合Specs,但不是如何调用一个parent的动作。

This question was written for version 0.10.5, which may change by the reader's time.


正在构建的应用程序将是一个简单的计数器,其中递增按钮会增加计数值,递减按钮会减少计数值。我们将通过制作一个通用的 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

这意味着当我在渲染中 dispatch 时,我将使用此处提供的 _onClick 操作:

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 时,我们修改 parent 的状态。否则,我们查看 subcomponent-specific 操作,但仅 足以 判断它应该属于谁!当它属于递增或递减按钮时,我们将数据传递给它。


这是我理想场景的设计 - 稍后通过多态性委托 "details" 的决定,并让组合处理管道。但是,在对此进行测试时,React 似乎并未分派 child 组件的 parental 操作。我不确定这是否是 dispatch 的设计方式,或者实际上是什么问题,但我有 a git repo 一个最小的错误示例。

我在您的 GitHub 存储库上创建了一个 pull request。我认为您试图将操作传递给按钮会使事情过于复杂。更符合Thermite的做法是让Button发出他的action,然后在父组件中使用一个Prism将Button的action映射到父组件的action中space.

因此,您只有 2 个,而不是对父级执行 4 个操作:

data ParentAction = Increment | Decrement

然后您可以通过递增或递减您所在州的计数器以通常的方式处理这些。现在,为了用你的按钮触发这些 ParentActions,你从你的按钮发出一个简单的 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"}

瞧瞧!