Взаимодействие смежных компонентов в термите
Сложный тест шаблонов проектирования пользовательского интерфейса оказывается простой задачей:
- Создать универсальный компонент (в нашем случае кнопка)
- использовать его в родительском компоненте
- заставить дочерний компонент "активировать" эффекты в родительском компоненте или соседнем брате (если он может достичь родителя, у родителя не должно быть проблем с подключением)
Философия дизайна 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-репо с рабочим минимальным примером ошибки.
1 ответ
Я создал запрос на извлечение для вашего репозитория GitHub. Я думаю, что вы слишком усложняете вещи, пытаясь передать действие Баттону. Что еще больше соответствует способу действия 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
Теперь все, что осталось, чтобы использовать эти призмы, чтобы сосредоточиться на действиях правой кнопки мыши:
inc = T.focus incButton _IncButton $ buttonSpec {_value: "Increment"}
dec = T.focus decButton _DecButton $ buttonSpec {_value: "Decrement"}
И вуаля!