博客 / 詳情

返回

【Clojure】re-frame for SPA

re-frame介紹

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

clipboard.png

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…這些都被當做數據來處理、註冊。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.