re-frame介紹
re-frame是一個幫助我們快速開發WEB單頁面應用的框架,是一個基於數據驅動的框架。其主要流程是如下的一個永無止境大循環:
dispatch——事件分發
事件來源:用户輸入、用户點擊、定時器、遠程調用響應等。事件作為一個數據被投入到類似於事件總線的隊列,其數據結構為:
[event-id event-arg1 event-arg2 ...]
re-frame事件分發函數:
(re-frame.core/dispatch [event-id event-arg1 event-arg2])
event-handler——事件處理
對於分發過來的事件,re-frame會根據事件ID(`event-id)找到註冊的事件處理函數,進行處理。event-handler是整個應用的業務核心,re-frame使得事件處理過程變成了一個純函數(穩定、可測試)。
(defn remove-item
"刪除元素事件處理"
[coeffects event] ;; `coeffects` holds the current state of the world.
(let [item-id (second event) ;; extract id from event vector
db (:db coeffects)] ;; extract the current application state
{:db (dissoc-in db [:items item-id])
:request {:request-id :some-thing}}))))
;; 註冊事件處理
(re-frame.core/reg-event-fx ;; a part of the re-frame API
:remove-item ;; event id
remove-item)
事件處理函數的輸入參數:[coeffects event]
-
coeffects:包含了事件處理函數執行時當前"世界"的全部狀態,包括
db(應用的本地狀態)以及其他事件處理上下文數據,如當前時間,隨機數等...{:db app-state ;; 當前app的狀態,re-frame默認設置的 :now 事件執行時的時刻 ;; 需要自定義註冊 :random-num 隨機數 ;; 需要自定義註冊 ... } - event:事件,參見上述
事件數據結構。
事件處理函數拿都輸入參數中的數據,通過計算之後,輸出effects,如:
{:db new-db ;; 更新應用狀態的效果
:request request-data ;; 遠程調用的效果
:cookie cookie-data ;; 更新cookie的效果
:timer timer-data ;; 啓動或關閉定時器的效果
...}
即用一個數據結構來描述事件處理之後要做的一些"副作用"操作,而不是直接在事件處理函數中調用這些副作用操作,如修改數據庫,執行遠程調用,修改cookie的值,這樣會使得事件處理函數變成非純函數。
PS:上述結構中,key為effect的ID,標誌一類effect。
effects handler——效果處理
re-frame會根據effect id 找到事先註冊好的效果處理函數,對效果進行處理。如對於:db這個默認的效果,re-frame會用新的db替換舊的db(這是re-frame的默認處理)。對於那些自定義效果,則需要註冊自定義處理函數,以遠程調用效果request為例:
(defn request-effect-handler
"遠程調用效果處理函數"
[{:keys [event params resp-event] :as request-data}]
(case event
:query-user-balance
;; 模擬遠程調用
(js/setTimeout #(re-frame/dispatch [resp-event 20000]) 5000)))
;; 註冊效果處理函數
(re-frame/reg-fx
:request ;; effect id
request-effect-handler)
效果處理完之後,應用的狀態被更新改變。
query——查詢應用狀態中用於顯示的相關數據
應用狀態被更新之後,負責顯示的視圖層會從新的狀態中,查找出它展示所需要的數據:
(defn query-fn
"定義從應用狀態db中查詢出所需數據函數"
[db] ;; db is current app state
(:items db)) ;; not much of a materialised view
;; 註冊查詢函數
(re-frame/reg-sub
:query-items ;; 查詢ID :query-id
query-fn)
;; 因為clojure中關鍵字也可以作為函數,所以上面的代碼等價於:
(re-frame/reg-sub :query-items :items)
view——生成新的視圖
視圖層根據query-id訂閲所需要的數據,然後根據數據生成新的視圖:
(defn items-view
"定義視圖生成函數"
[]
(let [items (re-frame/subscribe [:query-items])] ;; source items from app state
[:div (map item-render @items)])) ;; assume item-render already written
subscribe函數嚮應用狀態中訂閲query-id(:query-items)指定的數據,每當應用狀態中這個數據發生改變時,都會重新生成新的視圖。
Reagent/React——渲染視圖
生成的新的視圖會被重新渲染到DOM,展現給用户:
(reagent/render [views/items-view] (.getElementById js/document "app"))
示例Demo1——re-frame simple template
示例Demo2——Demo1重構後的版本
為什麼重構?
re-frame應該作為一個集成性架構來使用,不應該分散在代碼的各個單元、甚至是函數中,"污染“了代碼。- view單元和event-handler單元,應該都是以純函數形式存在。
- 精簡代碼。
re-frame核心設計思想:數據
- 應用狀態
db驅動頁面渲染,db數據是怎樣,頁面就渲染成什麼樣。 - 事件驅動:事件一個數據結構的形式在應用中流動,驅動這應用狀態的前進。
- 在re-frame中,事件、事件處理函數、效果、效果處理函數、應用的狀態、coeffects…這些都被當做數據來處理、註冊。