Сложная проверка шаблонов проектирования пользовательского интерфейса оказывается простой задачей:
- Создайте универсальный компонент (в нашем случае кнопку)
- использовать его в родительском компоненте
- заставить дочерний компонент «приводить в действие» эффекты в родительском компоненте или соседнем родственном компоненте (если он может достичь родителя, у родителя не должно быть проблем с сантехникой)
Философия дизайна Thermite все еще немного недоступна для меня, но я думаю, что понимаю как линзы и призмы можно использовать для объединения Spec
, но не как вызывать родительское действие.
Этот вопрос был написан для версии
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
, мы изменяем состояние родителя. В противном случае мы изучаем действия, специфичные для подкомпонента, но только достаточно, чтобы сказать, кому он должен принадлежать! Когда он принадлежит кнопкам увеличения или уменьшения, мы передаем ему данные.
Это дизайн для моего идеального сценария — делегировать решение о «деталях» позже, с помощью полиморфизма, и пусть композиция справится с сантехникой. Однако при тестировании реакция, похоже, не отправляет родительские действия дочернего компонента. Я не уверен, предназначен ли dispatch
именно так, или в чем на самом деле проблема, но у меня есть репозиторий git с рабочим минимальным примером ошибки.