iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

大家好。今天和大家一起看 Introducing Components

昨天寫完 halogen html,今天終於開始 component 了。一開始就來個 example:

module Main where

import Prelude

import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE

data Action = Increment | Decrement

component =
  H.mkComponent
    { initialState
    , render
    , eval: H.mkEval H.defaultEval { handleAction = handleAction }
    }
  where
  initialState _ = 0

  render state =
    HH.div_
      [ HH.button [ HE.onClick \_ -> Decrement ] [ HH.text "-" ]
      , HH.text (show state)
      , HH.button [ HE.onClick \_ -> Increment ] [ HH.text "+" ]
      ]

  handleAction = case _ of
    Decrement ->
      H.modify_ \state -> state - 1

    Increment ->
      H.modify_ \state -> state + 1

可以看出幾個東西:

  1. Component 可以有 state
  2. UI events 透過 html element 的第一個 parameter 可以登記 handler,但 handler 只能 emit action,似乎不能做其他 side effect
  3. Actions 在 eval 裡面提供 handleAction function 處理,裡面似乎可以做 side effect。

接下來 tutorial 嘗試一步步的把這個 component 分拆,再把該有的 type 加回去。

type State = Int

這種使用了 Type Synonyms 把 component state 定義為 Int。這個使用方法跟 typescript 的 type keyword 是一樣的。

接下來是定義 Action:

data Action = Increment | Decrement

這個 data keyword 可能就有點陌生了。data 實際上是所謂的 algebraic data types (ADTs),在 typescript 裡面就是所謂的 tagged unions。(發現 purescript 好像也改叫 tagged unions 了⋯) 如果用 Typescript 去定義 Action 的話大槪會長這樣:

enum ActionType {
    Increment,
    Decrement,
}

type Action = IncrementAction | DecrementAction

type IncrementAction = {
    type: ActionType.Increment,
}

type DecrementAction = {
    type: ActionType.Decrement,
}

在介紹 handleAction 的 type signature 時有提到還有 input、output 跟 child components 的 type parameters,似乎在以後會再介紹。

接下來介紹 render 跟 event handling,這部份我覺得蠻直觀的,就不詳述了。

比較有趣的地方是 Component 有分外部跟內部的 type definition,而 mkComponent 會幫忙生外部的 type definition,蠻有趣的。而 public component type 包括 effect monad 的,這是我比較感興趣的地方,希望之後會有詳細的說明。

最後是分拆好的 component!

module Main where

import Prelude

import Effect (Effect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.VDom.Driver (runUI)

main :: Effect Unit
main = HA.runHalogenAff do
  body <- HA.awaitBody
  runUI component unit body

type State = Int

data Action = Increment | Decrement

component :: forall query input output m. H.Component query input output m
component =
  H.mkComponent
    { initialState
    , render
    , eval: H.mkEval H.defaultEval { handleAction = handleAction }
    }

initialState :: forall input. input -> State
initialState _ = 0

render :: forall m. State -> H.ComponentHTML Action () m
render state =
  HH.div_
    [ HH.button [ HE.onClick \_ -> Decrement ] [ HH.text "-" ]
    , HH.text (show state)
    , HH.button [ HE.onClick \_ -> Increment ] [ HH.text "+" ]
    ]

handleAction :: forall output m. Action -> H.HalogenM State Action () output m Unit
handleAction = case _ of
  Decrement ->
    H.modify_ \state -> state - 1

  Increment ->
    H.modify_ \state -> state + 1

到這裡今天就完成了。到這裡要寫些簡單的 component 應該是沒問題了。


上一篇
[Day 7] 撰寫 halogen HTML
系列文
從零開始的 Purescript8
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言