Взаимодействие смежных компонентов в термите

Сложный тест шаблонов проектирования пользовательского интерфейса оказывается простой задачей:

  • Создать универсальный компонент (в нашем случае кнопка)
  • использовать его в родительском компоненте
  • заставить дочерний компонент "активировать" эффекты в родительском компоненте или соседнем брате (если он может достичь родителя, у родителя не должно быть проблем с подключением)

Философия дизайна 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"}

И вуаля!

Другие вопросы по тегам