javascript - 从榆树中的子组件发送信号

标签 javascript timer architecture frp elm

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

在架构上,我有三个模块:代表计时器的 Clock 模块,可以播放音频的 PlayAudio 模块,以及 MainClock 模块和 PlayAudio 模块联系在一起的模块。

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

但是,通过阅读 Elm 文档,似乎不鼓励使用 Main 以外的任何方式处理信号。所以这引出了我的第一个问题。对这种状态变化建模的好方法是什么? Clock 中的 update 函数是否应该返回它是否已经结束? (这就是我在下面的做法,但我非常愿意接受有关如何做得更好的建议。)

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

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

时钟.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:

<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <script src="js/elm.js" type="text/javascript"></script>
  <link rel="stylesheet" href="style.css">
 </head>
 <body>
        <script type="text/javascript">
                 var todomvc = Elm.fullscreen(Elm.Main);
                todomvc.ports.playSound.subscribe(function() {
                                setTimeout(function() {
                                        document.getElementById('audiotag').play();
                                }, 50);
                });
        </script>
 </body>
</html>

最佳答案

这种方法看起来非常有原则并且符合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 的互操作变得更加容易,因此一旦有人创建了声音库的绑定(bind),您就应该能够取消该端口。

关于javascript - 从榆树中的子组件发送信号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29689567/

相关文章:

c++ - 在没有分析器的情况下在 C++ 中测试代码速度的最佳方法,或者尝试没有意义?

多个对象的 iOS 淡入淡出动画

c# - 使用单个值对象的不同表示

java - 无法刷新 JFrame

c# - 架构问题

c++ - 检查先决条件应该由谁负责?

javascript - Polymer - Vaadin 日期选择器; Vaadin 网格 : How to change date format?

javascript - 如何使用node.js或express显示控制台中文件夹中所有文件的内容

javascript - 使用 VBA 将表格从 Web 导入到 Excel

javascript - 浏览器不在启用 CORS 的情况下跨域发送 cookie