re-frame是一個幫助咱們快速開發WEB單頁面應用的框架,是一個基於數據驅動的框架。其主要流程是以下的一個永無止境大循環:git
事件來源:用戶輸入、用戶點擊、定時器、遠程調用響應等。事件做爲一個數據被投入到相似於事件總線的隊列,其數據結構爲:github
[event-id event-arg1 event-arg2 ...]
re-frame事件分發函數:數據庫
(re-frame.core/dispatch [event-id event-arg1 event-arg2])
對於分發過來的事件,re-frame會根據事件ID(`event-id)找到註冊的事件處理函數,進行處理。event-handler是整個應用的業務核心,re-frame使得事件處理過程變成了一個純函數(穩定、可測試)。cookie
(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 隨機數 ;; 須要自定義註冊 ... }
事件數據結構
。事件處理函數拿都輸入參數中的數據,經過計算以後,輸出effects,如:app
{:db new-db ;; 更新應用狀態的效果 :request request-data ;; 遠程調用的效果 :cookie cookie-data ;; 更新cookie的效果 :timer timer-data ;; 啓動或關閉定時器的效果 ...}
即用一個數據結構來描述事件處理以後要作的一些"反作用"操做,而不是直接在事件處理函數中調用這些反作用操做,如修改數據庫,執行遠程調用,修改cookie的值,這樣會使得事件處理函數變成非純函數。框架
PS:上述結構中,key爲effect的ID,標誌一類effect。dom
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)
效果處理完以後,應用的狀態被更新改變。
應用狀態被更新以後,負責顯示的視圖層會重新的狀態中,查找出它展現所須要的數據:
(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)
視圖層根據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)指定的數據,每當應用狀態中這個數據發生改變時,都會從新生成新的視圖。
生成的新的視圖會被從新渲染到DOM,展示給用戶:
(reagent/render [views/items-view] (.getElementById js/document "app"))
re-frame
應該做爲一個集成性架構來使用,不該該分散在代碼的各個單元、甚至是函數中,"污染「了代碼。db
驅動頁面渲染,db數據是怎樣,頁面就渲染成什麼樣。