加減數字示例
- Redux裡的Store對應Model,目前只有數字x;
- Redux裡的Action對應Msg,目前可以對狀態進行加或減;
- Redux裡的初始化對應init,目前x設為0,併發一個Msg說要進行加操作,所以介面應該顯示為1;
- Redux裡的Reducer對應update,這是唯一一個能對狀態進行更改的地方,而且fsharp本身預設都是immutable的,所以你也無法對狀態在其他任何地方進行更改;在redux裡會用immutablejs來做,但是個人使用發現挺麻煩。
- view裡面就是基於狀態model進行渲染,使用者的點選等操作通過釋出Msg來通知Program呼叫update來修改狀態;
- Program就是把init, update, view繫結在一起然後跑起來。
type Model = { x : int }
type Msg = Increment | Decrement
let init () =
{ x = 0 }, Cmd.ofMsg Increment
let update msg model =
match msg with
| Increment -> { model with x = model.x + 1 }, Cmd.none
| Decrement -> { model with x = model.x - 1 }, Cmd.none
let view model dispatch =
div []
[
div [] [str (string model.x)]
button [OnClick (fun e -> dispatch Increment)] [str "+" ]
button [OnClick (fun e -> dispatch Decrement)] [str "-" ]
]
Program.mkProgram init update view
|> Program.run
複製程式碼
整個程式真的是太簡潔易懂了,可能會有一些語法需要習慣,比如type Msg = Increment | Decrement
中的|
相當於就是或
,而match msg with
類似switch case
,但是功能強大很多。view裡面的div [放HTML標籤屬性] [放子元素]
。整個程式幾乎每一行都是在寫自己的業務,冗餘的內容很少,真是太優雅了。忍不住要把自己的第一篇文章要用來寫這個。
函數語言程式設計的語言很多,能用做前端的似乎也不少,包括fsharp, scala等。由於本人主要混在.net平臺,所以對fsharp瞭解比較多。用fsharp寫前端主要是靠Fable把fsharp編譯成js(FSharp |> Fable |> Bable |> Webpack |> js)。
Elmish 是一個設計思想,應該是和mvvm平級,理念類似redux,但是時間是應該更早一點。具體可以參看https://elmish.github.io/elmish/。所以debug的時候也可以用redux的chrome外掛檢視狀態等。
Fable只是一個編譯器,除了view其他的都是單純的邏輯,而view裡面的東西可以是很多前端的框架技術比如react(上例用的就是), react native, 也可以是原生的html,或者vue等,但是Fable社群裡面最流行的還是react。所以react生態裡的所有控制元件都可以使用,但是為了有fsharp的型別提醒需要寫一些型別宣告,這個類似於typescript的.d.ts裡寫的東西;所以社群裡也有人寫了一些工具把.d.ts的內容直接翻譯成fsharp可使用的型別(目前還沒有試過)。當然也可以自行繫結原生js,比如需要引入npm的庫的時候就要做這種事情。具體參見http://fable.io/docs/interacting.html
俄羅斯方塊
程式碼託管在https://github.com/albertwoo/Tetris
專案初始化是用社群裡一個模板生成的[https://github.com/SAFE-Stack],包括的內容很多,如自動化編譯,打包,熱更新,測試等,程式碼也有很多如Client, Server, Test等,目前不需要Server,所以目前的主要程式碼都在src/Client下面。
TetrisDomain.fs
定義了俄羅斯方塊的基本型別以及一些操作比如操作Block,檢視是否相撞,或者清除滿足條件的行等。Square是指最小的馬賽克方塊,Block是指下落的物體:
type Square = { Location: int * int; Color: int * int * int * float }
type BlockType = T | L | J | I | O | Z | RZ | X
type Block = { Type: BlockType; Squares: Square list }
type Action = Rotate | Left | Right | Down
...
複製程式碼
Tetris.fs
相當於寫了一個元件,包括了狀態,更新狀態,以及介面的一些東西:
type Model = {
SquareSize: int
Boundry: int * int
AllSquares: Square list
PreviewBlock: Block option
MovingBlock: Block option
PrectionBlock: Block option
IsOver: bool
Score: int
DefaultSpeed: int
Speed: int
SpeedCount: int }
type Msg = | Action of Action | ReachBottom | ReachLeft | ReachRight
複製程式碼
App.fs
是整個程式的入口,會把Tetris定義的東西整合進來,也包含了一些介面的佈局,開始,暫停,重新開始等操作。
type Model = {
Tetris: Tetris.Model
TimeCost: int
TouchStartPoint: (float * float) option
TouchMovingPoint: (float * float) option
TouchTime: DateTime option
IsPaused: bool
IsRestarting: bool
HideDetail: bool }
type Msg =
| TetrisMsg of Tetris.Msg
| BeginRestart | CancelRestart | Restart
| Tick
| TouchStart of float * float | TouchMove of float * float | TouchEnd of float * float
| Pause | Continue
| HideDetail
複製程式碼
整個程式的元件的拆分沒有做得很好,邏輯不是很清晰,作為學習勉強接受吧。 整個效果如下,也可以線上體驗,觸控最佳,鍵盤勉強可用:
結語
函數語言程式設計似乎離成為主流還有很長的路,在現代的伺服器方面的應用應該比較多,畢竟微服務的流行導致框架和語言的選擇變得更靈活。但是在前端方面的應用很少,android, xamarin等本地應用的開發都大量使用mvvm的設計模式。以前我開發過wpf,都是基於mvvm,現在開發angular幾乎自然上手,設計思想如出一轍。React倒是特立獨行,包括其中流行的redux狀態管理也很有獨到之處。vue給我的感覺就顯得抱負很大什麼都想做,但是個人淺嘗後最後還是選擇了angular,畢竟有typescript的完美融合。
因為專案啟動後已經使用了angular,但是也想利用redux的一些先進思想,後來嘗試ngrx,但是發現需要寫太多冗餘的程式碼,而且分散在很多不同的檔案,加之js的語言特性,寫actions, reducers, selectors的時候簡直婆婆媽媽,讓我失去耐心,冗餘程式碼幾乎趕上業務邏輯程式碼,所以最後還是放棄。不過angular本身的設計已經在我的專案夠用了,除了有點囉嗦。
物件導向還大行其道,並且對各種問題都有著成熟的解決方案,上面所言都是在下實踐所得的經驗,沒有嚴格的理論支援,純屬個人妄言,聽聽就是,也可嗤之以鼻。