从榆树的子组件发送信号

时间:2015-04-17 03:10:07

标签: javascript timer architecture frp elm

我在Elm做了一个小应用程序。它在屏幕上显示一个计时器,当计时器到达零时,它会播放声音。我无法弄清楚如何从定时器向声音播放器发送消息(?)。

在架构上,我有三个模块:一个Clock模块代表定时器,一个PlayAudio模块可以播放音频,一个Main模块将Clock连接在一起} module和PlayAudio module。

理想情况下,当时钟到达零时,我想做一些事情,比如从Clock模块发送信号。当时钟到达零时, Clock会向Main发送信号,然后将信号转发至PlayAudio

然而,通过阅读Elm文档,似乎不鼓励使用除Main以外的任何其他信息来处理信号。这就引出了我的第一个问题。建模这种状态变化的好方法是什么?来自update的{​​{1}}函数是否应该返回它是否已经结束? (这就是我在下面的方式,但我会对如何做得更好的建议持开放态度。)

我的第二个问题是如何让声音发挥。我将使用原始Javascript来播放声音,我相信这意味着我必须使用端口。但是,我不确定如何与我的子模块Clock Main中定义的端口进行交互。

以下是我正在使用的代码。

PlayAudio

Clock.elm

module Clock (Model, init, Action, signal, update, view) where import Html (..) import Html.Attributes (..) import Html.Events (..) import LocalChannel (..) import Signal import Time (..) -- MODEL type ClockState = Running | Ended type alias Model = { time: Time , state: ClockState } init : Time -> Model init initialTime = { time = initialTime , state = Running } -- UPDATE type Action = Tick Time update : Action -> Model -> (Model, Bool) update action model = case action of Tick tickTime -> let hasEnded = model.time <= 1 newModel = { model | time <- if hasEnded then 0 else model.time - tickTime , state <- if hasEnded then Ended else Running } in (newModel, hasEnded) -- VIEW view : Model -> Html view model = div [] [ (toString model.time ++ toString model.state) |> text ] signal : Signal Action signal = Signal.map (always (1 * second) >> Tick) (every second)

PlaySound.elm

module PlaySound (Model, init, update, view) where import Html (..) import Html.Attributes (..) import Html.Events (..) import LocalChannel (..) import Signal import Time (..) -- MODEL type alias Model = { playing: Bool } init : Model init = { playing = False } -- UPDATE update : Bool -> Model -> Model update shouldPlay model = { model | playing <- shouldPlay } -- VIEW view : Model -> Html view model = let node = if model.playing then audio [ src "sounds/bell.wav" , id "audiotag" ] [] else text "Not Playing" in div [] [node]

Main.elm

module Main where import Debug (..) import Html (..) import Html.Attributes (..) import Html.Events (..) import Html.Lazy (lazy, lazy2) import Json.Decode as Json import List import LocalChannel as LC import Maybe import Signal import String import Time (..) import Window import Clock import PlaySound ---- MODEL ---- -- The full application state of our todo app. type alias Model = { clock : Clock.Model , player : PlaySound.Model } emptyModel : Model emptyModel = { clock = 10 * second |> Clock.init , player = PlaySound.init } ---- UPDATE ---- type Action = NoOp | ClockAction Clock.Action -- How we update our Model on a given Action? update : Action -> Model -> Model update action model = case action of NoOp -> model ClockAction clockAction -> let (newClock, hasEnded) = Clock.update clockAction model.clock newPlaySound = PlaySound.update hasEnded model.player in { model | clock <- newClock , player <- newPlaySound } ---- VIEW ---- view : Model -> Html view model = let context = Clock.Context (LC.create ClockAction actionChannel) in div [ ] [ Clock.view context model.clock , PlaySound.view model.player ] ---- INPUTS ---- -- wire the entire application together main : Signal Html main = Signal.map view model -- manage the model of our application over time model : Signal Model model = Signal.foldp update initialModel allSignals allSignals : Signal Action allSignals = Signal.mergeMany [ Signal.map ClockAction Clock.signal , Signal.subscribe actionChannel ] initialModel : Model initialModel = emptyModel -- updates from user input actionChannel : Signal.Channel Action actionChannel = Signal.channel NoOp port playSound : Signal () port playSound = ???

index.html

1 个答案:

答案 0 :(得分:1)

这种方法看起来非常有原则,符合Elm Architecture post的指导原则。在该文档的最后是一个名为One last pattern的部分,它完全按照您的要求执行操作:如果需要从组件向另一个组件发出信号,请让更新功能返回一对。
所以我认为你做得对。当然,在如此小的应用程序中如此严格地遵循这种架构确实增加了样板/相关代码比率。

无论如何,您需要进行的唯一更改是Main.elm。实际上,您并不需要Channel从子组件向Main发送消息,因为Main会启动组件并将更新功能连接在一起。因此,您可以使用组件更新功能的额外输出,并将其从模型信号中分离到端口中。

---- UPDATE ----

-- How we update our Model on a given Action?
update : Clock.Action -> Model -> (Model, Bool)
update clockAction model =
    let (newClock, hasEnded) = Clock.update clockAction model.clock  
        newPlaySound = PlaySound.update hasEnded model.player
    in ( { model | clock <- newClock
               , player <- newPlaySound }, hasEnded)

---- VIEW ----

view : Model -> Html
view model =
    div [ ]
      [ Clock.view model.clock
      , PlaySound.view model.player
      ]

---- INPUTS ----

-- wire the entire application together
main : Signal Html
main = Signal.map (view << fst) model

-- manage the model of our application over time
model : Signal Model
model = Signal.foldp update initialModel Clock.signal

initialModel : Model
initialModel = emptyModel

port playSound : Signal ()
port playSound =
  model
  |> Signal.map snd
  |> Signal.keepIf ((==) True)
  |> Signal.map (always ())

最后注释: Elm 0.15 is out,至少会简化您的导入。但更重要的是,与Elm内部的JavaScript(无端口)互操作变得更加容易,因此只要有人创建了与声音库的绑定,您就应该能够取消该端口。