Как получить текущее время в Elm 0.17/0.18?
Я уже задал этот вопрос:
Как получить текущее время в Elm?
И ответил на это, написав мой собственный (теперь устаревший) вариант start-app:
http://package.elm-lang.org/packages/z5h/time-app/1.0.1
Конечно, архитектура Вяз изменилась, и мой старый способ делать вещи больше не работает, потому что нет сигналов или Time.timestamp
.
Так....
Предположим, что я создаю приложение со стандартной сигнатурой функции обновления:
update : Msg -> Model -> (Model, Cmd Msg)
Я хотел бы создать временную метку моей модели со временем при обновлении. Одно неприемлемое решение - подписаться на Time.every
. Концептуально это не то, что я хочу. Это обновляет модель со временем, а также отдельно обновляет модель с сообщениями.
Что я хочу, так это написать функцию обновления с подписью:
updateWithTime : Msg -> Time -> Model -> (Model, Cmd Msg)
Я начал пытаться решить эту проблему, добавив дополнительные сообщения:
Msg = ... When | NewTime Time
И создание новой команды:
timeCmd = perform (\x -> NewTime 0.0) NewTime Time.now
Таким образом, в любом действии я могу запустить дополнительную команду для получения времени. Но это становится беспорядочным и из рук в руки.
Любые идеи о том, как я могу это очистить?
Ответы
Ответ 1
Один из вариантов без необходимости делать выборку времени для каждого пути обновления - это обернуть ваш Msg
в другой тип сообщения, который будет извлекать время, а затем вызвать ваш обычный update
с указанием времени. Это измененная версия http://elm-lang.org/examples/buttons, которая обновляет временную метку модели при каждом обновлении.
import Html exposing (div, button, text)
import Html.App exposing (program)
import Html.Events exposing (onClick)
import Task
import Time exposing (Time)
main =
program { init = (Model 0 0, Cmd.none), view = view, update = update, subscriptions = (\_ -> Sub.none) }
type alias Model =
{ count: Int
, updateTime : Time
}
view model =
Html.App.map GetTimeAndThen (modelView model)
type Msg
= GetTimeAndThen ModelMsg
| GotTime ModelMsg Time
update msg model =
case msg of
GetTimeAndThen wrappedMsg ->
(model, Task.perform (\_ -> Debug.crash "") (GotTime wrappedMsg) Time.now)
GotTime wrappedMsg time ->
let
(newModel, cmd) = modelUpdate wrappedMsg time model
in
(newModel, Cmd.map GetTimeAndThen cmd)
type ModelMsg = Increment | Decrement
modelUpdate msg time model =
case msg of
Increment ->
({model | count = model.count + 1, updateTime = time}, Cmd.none)
Decrement ->
({model | count = model.count - 1, updateTime = time}, Cmd.none)
modelView model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model.count) ]
, button [ onClick Increment ] [ text "+" ]
, div [] [ text (toString model.updateTime) ]
]
Ответ 2
Я нашел то, что считаю более элегантным, чем принятый ответ. Вместо двух отдельных моделей сообщение GetTimeAndThen
содержит обработчик, который возвращает сообщение. Код выглядит намного более естественным и похожим на вяза, и его можно использовать более общим образом:
module Main exposing (..)
import Html exposing (div, button, text)
import Html.App as App
import Html.Events exposing (onClick)
import Task
import Time exposing (Time)
main =
App.program
{ init = ( Model 0 0, Cmd.none )
, view = view
, update = update
, subscriptions = (\_ -> Sub.none)
}
view model =
div []
[ button [ onClick decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick increment ] [ text "+" ]
]
increment =
GetTimeAndThen (\time -> Increment time)
decrement =
GetTimeAndThen (\time -> Decrement time)
type Msg
= Increment Time
| Decrement Time
| GetTimeAndThen (Time -> Msg)
type alias Model =
{ count : Int, updateTime : Time }
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GetTimeAndThen successHandler ->
( model, (Task.perform assertNeverHandler successHandler Time.now) )
Increment time ->
( { model | count = model.count + 1, updateTime = time }, Cmd.none )
Decrement time ->
( { model | count = model.count - 1, updateTime = time }, Cmd.none )
assertNeverHandler : a -> b
assertNeverHandler =
(\_ -> Debug.crash "This should never happen")
Ответ 3
import Time exposing (Time)
import Html exposing (..)
import Html.Events exposing (onClick)
import Task
type Msg
= GetTime
| NewTime Time
type alias Model =
{ currentTime : Maybe Time
}
view : Model -> Html Msg
view model =
let
currentTime =
case model.currentTime of
Nothing ->
text ""
Just theTime ->
text <| toString theTime
in
div []
[ button [ onClick GetTime ] [ text "get time" ]
, currentTime
]
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GetTime ->
model ! [ Task.perform NewTime Time.now ]
NewTime time ->
{ model | currentTime = Just time } ! []
main : Program Never Model Msg
main =
program
{ init = init
, update = update
, view = view
, subscriptions = always Sub.none
}
init : ( Model, Cmd Msg )
init =
{ currentTime = Nothing } ! []
Ответ 4
После обсуждения этого вопроса на Slack, здесь есть альтернативная реализация без функций в Msg
. Как и в случае принятого ответа, модель обновляется только тогда, когда успешно завершается Time.now
Task
.
import Html exposing (div, button, text)
import Html.App as App
import Html.Events exposing (onClick)
import Task
import Time exposing (Time)
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = (\_ -> Sub.none)
}
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
type Msg
= NoOp
| Increment
| Decrement
| GetTimeSuccess Msg Time
| GetTimeFailure String
type alias Model =
{ count : Int, updateTime : Result String Time }
init : (Model , Cmd Msg)
init =
( { count = 0
, updateTime = Err "No time yet!"
}
, Task.perform GetTimeFailure (GetTimeSuccess NoOp) Time.now
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp -> (model, Cmd.none)
Increment ->
( model
, Task.perform GetTimeFailure (GetTimeSuccess Increment) Time.now
)
Decrement ->
( model
, Task.perform GetTimeFailure (GetTimeSuccess Decrement) Time.now
)
GetTimeSuccess Increment time ->
( { model | count = model.count + 1, updateTime = Ok time}
, Cmd.none
)
GetTimeSuccess Decrement time ->
( { model | count = model.count - 1, updateTime = Ok time}
, Cmd.none
)
GetTimeSuccess _ time ->
( { model | updateTime = Ok time}
, Cmd.none
)
GetTimeFailure msg ->
( { model | updateTime = Err msg}
, Cmd.none
)
Ответ 5
У меня есть ответ на мой собственный вопрос (на основе предложения amilner42). Я использую это решение в своем текущем коде.
Мне очень нравится решение от @w.brian, но функции в сообщениях прерывают отладчик.
Мне нравится решение от @robertjlooby, и это очень похоже, хотя оно устраняет лишний тип и обновляется на 0,18.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
model ! []
TickThen msg ->
model ! [ Task.perform (Tock msg) Time.now ]
Tock msg time ->
updateTimeStampedModel msg { model | time = time }
otherMsg ->
update (TickThen msg) model
updateTimeStampedModel : Msg -> Model -> ( Model, Cmd Msg )
updateTimeStampedModel msg model =
case msg of
NoOp ->
update msg model
TickThen _ ->
update msg model
Tock _ _ ->
update msg model
-- ALL OTHER MESSAGES ARE HANDLED HERE, AND ARE CODED TO ASSUME model.time IS UP-TO-DATE.
Ответ 6
Вы можете создать собственный модуль, а затем выставить функцию timestamp
, которая получает время от Date.now()
в JavaScript.
Это примерно то, на что это будет выглядеть:
Timestamp.elm
module Timestamp exposing (timestamp)
import Native.Timestamp
timestamp : () -> Int
timestamp a = Native.Timestamp.timestamp a
Native/Timestamp.js
var _YourRepoUserName$your_repo$Native_Timestamp = function() {
return { timestamp: function(a) {return Date.now()}
}
Main.elm
port module Main exposing (..)
import Timestamp exposing (timestamp)
то вы можете использовать (timestamp ()
) в любом месте в Elm, чтобы получить текущую метку времени как Int.
Примечание. Я использовал timestamp : () -> Int
, потому что я не мог заставить его работать иначе. timestamp : Int
просто вернул твердое время первой загрузки.
Сообщите мне, если это можно улучшить.