Elm启发了Redux和Vuex,在github上有专门的 elm-architecture 主题。
Elm本身是一个函数式编程语言,同时也是一个Web框架。整个Web框架的核心数据流,类似于一个reduce函数:当前状态 -> 输入参数 -> 新的状态
Elm把这个架构叫作 MVU, Model View Update
下面是一个简单的例子。这里可以看到Elm将整个应用变成函数式的思路。
module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }
-- MODEL
type alias Model = Int
init : Model
init =
0
-- UPDATE
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]
这里的核心只有update和view两个函数,从这两个函数的签名可以看出来,每个函数都是纯函数。
Re-frame与Elm、Redux出现的时间都比较接近,它们之间有明显的相似性和历史渊源。
Re-frame基于reagent/react,最终的DOM更新靠的是react,但整个框架支持完全函数式的编程。通过re-frame,可以看到响应式编程(reactive programming), 函数式编程和不可变数据是怎么在一个实际的应用里使用起来的。
Re-frame将整个的数据流处理分为6个步骤:
可以看到,re-frame通过effect handler,将(修改外部的)副作用做了清晰的隔离。
理论上讲,在一个re-frame的应用中,应该将所有的副作用,都隔离在effect handler中。
re-frame内置了:db
和:dispatch
两个effect handler,分别用于更新全局状态(db)和发送新的事件(event)。这样,event handler的执行,就不需要去修改外部状态,而是返回一个数据。返回的数据再由框架层通过effect handler去修改外部状态。
effect handler架构解决了修改外部世界的副作用问题。读取外部世界的副作用,在re-frame中定义为coeffect,用类似的架构处理。
注册event handler的时候,如果需要读取外部世界的数据,可以声明一个coeffect注入请求。框架在执行这个事件处理方法之前,就会先去执行对应的coeffect handler,将相应的数据准备好。
re-frame将整个应用的状态,保存在一个map中,而subscription机制,可以使得其中的一个指定的key,变成reactive programming中的stream(signal)。view函数可以订阅这样的一个值,并在生成hiccup时使用它。re-frame会在map中的值发生变化时,自动更新hiccup,从而更新DOM。
实现这个机制的核心,是reagent中的reaction/ratom。
基于上面的概念,re-frame应用的结构大致如下:
::events/initialize-db
,用于初始化应用状态(db)
可以通过一个序列图来示意: