【Clojure】re-frame for SPA

re-frame介紹

re-frame是一個幫助咱們快速開發WEB單頁面應用的框架,是一個基於數據驅動的框架。其主要流程是以下的一個永無止境大循環:git

clipboard.png

dispatch——事件分發

事件來源:用戶輸入、用戶點擊、定時器、遠程調用響應等。事件做爲一個數據被投入到相似於事件總線的隊列,其數據結構爲:github

[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使得事件處理過程變成了一個純函數(穩定、可測試)。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 隨機數  ;; 須要自定義註冊
     ...
     }
  • event:事件,參見上述事件數據結構

事件處理函數拿都輸入參數中的數據,經過計算以後,輸出effects,如:app

{:db new-db ;; 更新應用狀態的效果
 :request request-data ;; 遠程調用的效果
 :cookie cookie-data ;; 更新cookie的效果
 :timer timer-data ;; 啓動或關閉定時器的效果
 ...}

即用一個數據結構來描述事件處理以後要作的一些"反作用"操做,而不是直接在事件處理函數中調用這些反作用操做,如修改數據庫,執行遠程調用,修改cookie的值,這樣會使得事件處理函數變成非純函數。框架

PS:上述結構中,key爲effect的ID,標誌一類effect。dom

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…這些都被當作數據來處理、註冊。
相關文章
相關標籤/搜索