Respo 組件狀態管理的思考

增長了一個視頻來解釋這篇文章的內容: http://weibo.com/1651843872/E...react

更新了 Respo 到 0.4.x, 移除了原先基於 init-state update-state mutate! 的代碼,
原先的版本是模仿的 React, 在組件曾經管理狀態, 但同時支持熱替換過程組件狀態的穩定,
如今的版本 state tree 須要手段管理, 同時和 Virtual DOM 的結構對應,
也就是說, 從原來的自動管理編程了手工管理, 其實是變麻煩了.
那麼, 爲何? 切入點從當時發的微博能夠看到:git

http://weibo.com/1651843872/E...github

我以爲 Respo 的狀態樹的設計有點問題, 已有的方案是基於節點位置信息生成的, 也就是和 DOM 結構一一對應, 實際上並不須要那麼深, 並且狀態樹也不該該依賴 DOM 結構的信息. 隨着頁面開發手動構建狀態樹應該是更準確的作法. 可能會囉嗦, 可是會更精準. ​​​​編程

http://weibo.com/1651843872/E...app

組件狀態這個事情, 按照 MVC 的套路, Model 是放到全局和 View 解耦的. 而從模塊化的思路看, 組件狀態應該在組件層面進行封裝, 開發維護和使用過程中都應該與其餘的模塊無關. 那麼, 這個場景當中所謂"局部狀態"就更應該指的是技巧上達成的局部, 而不是對簡單地 MVC 進行取捨. ​​​​框架

http://weibo.com/1651843872/E...模塊化

理解陌生技術的過程真能夠用盲人摸象來形容, 大部分人瞭解到的永遠只是局部, 而只能藉助直覺, 從局部推演總體是什麼樣子的. 好比 Cursor 這東西, 在 js 裏有 OOP 封裝的寫法, 在 cljs 裏有 Atom 封裝的寫法, 在 Mobx 裏思路相似但文檔上介紹是 Observable. 我也鬱悶了, 準確來講什麼樣? ​​​​函數

試圖解決的問題

以前 Respo 的方案當中, 組件狀態是以 state tree 的形式儲存的.
React 當中的組件狀態是單獨存儲在每一個組件當中, 在熱替換不可靠,
因而我在 Respo 當中存儲在了全局的一個 HashMap 當中,
同時, HashMap 的結構和 DOM tree 對應, 便於框架自動索引和綁定,
也就是說 state tree 對 DOM 結構存在依賴, 並且深度很大.
並且 state tree 是 Model, 這時 Model 對 View 存在依賴了, 不妥.組件化

另外一方面, 當 state tree 結構合理, 跨組件的狀態操做是可能的,
好比組件 A 的狀態在 state tree 某個位置, 經過 dispatch 進行操做,
這時 dispatch 只要知曉 A 狀態的路徑, 就能在另外的組件發起操做,
這種狀況就須要人爲對 state tree 進行設計, 以便結構可以被識別,
並且可識別的 state tree 能夠被其餘代碼處理和分析, 也是好處.
當前依賴 View 也就是 DOM 結構的 state tree 沒法達成這一點.spa

因此個人目標是提供手工設計 state tree 的方案, 以便項目自由控制,
而且最終結構清晰的 state tree 能夠在另外的場景發揮做用.
固然, 同時也要知足一些約束, 首先 state tree 是全局的不用說了,
而後, 組件化開發中, 組件狀態依然要作到解耦, 不能犧牲組件化,
整體歸納, 就是簡化 state tree 的實現, 暴露給項目代碼.

給出的方案

實際獲得的方案還有點麻煩, 能夠說開發者的工做量變大了,
涉及到 Store 和 Component 部分的修改, 加上數據傳遞, 大體有:

  • 組件能夠獲取到 states 變量, 是一個局部的組件樹,
    經過 (:data states) 能夠獲取當前組件的數據,

同時 states 還包含內部的所有子節點的狀態, 相似的格式存儲.

  • 組件能夠得到一個 cursor 變量, 表示當前的組件狀態在 state tree 的位置,
    這個 cursor 由 Respo 框架內部進行維護, 減小出錯的麻煩,

實際上就是一些 keyword 組件的 vector, 能夠用在 assoc-in 函數.

  • Store 當中約定 :states {} 字段專門用於存儲 state tree,
    這樣的好處是達成了 single app state 這樣的目標,

更新時, 拿到 cursornext-state 在 state tree 上進行數據修改.

完整的 Demo 能夠看我更新的例子, 做爲演示而言仍是太長了一些:

https://github.com/Respo/resp...

粗略能夠看一點, 調用組件的地方麻煩了許多, 注意 with-cursor,
主要專門聲明建立一個 Sub Cursor, 這裏用了 n 直接做爲 key,
同時 states 也須要手根據 key 讀取對應分支的數據:

(div {}
  (->> (range 10)
    (map-indexed (fn [index n]
      [index (with-cursor n (comp-box (get states n) n))]))))

再看這個組件 states 須要手動傳入和獲取,
cursor 經過框架傳入. 另外 state 也要手工計算獲得:

(defn render [states n]
  (fn [cursor]
    (let [state (or (:data states) initial-state)]
      (div {}
        (comp-text (str n ". ") nil)
        (comp-button "inc" (handle-click cursor (+ state n)))
        (comp-text state nil)))))

而修改狀態的步驟變麻煩了, 須要 dispatch 一個 :states 行爲,
同時帶上前面說的 cursornext-state 兩個數據:

(defn handle-click [cursor next-state]
  (fn [e dispatch!]
    (dispatch! :states [cursor next-state])))

最終由 updater 處理 state tree. 注意 mutate 只是語法糖:

(defn updater [store op op-data]
  (case op
    :states (update store :states (mutate op-data))
    :inc (update store :pointer inc)
    :dec (update store :pointer dec)
    store))

按照這些代碼, 基本上完成了 MVC 的一個循環, 並且代碼量也大了不少.
瞭解 Cursor 的同窗應該注意到這思路很像, 儘管寫法差異很大,
若是是 Cursor, 都不須要 dispatch 這個過程, 只要調用接口操做便可,
我這裏的緣由是爲了解耦, 也算是有本身的場景, 我須要 actions 獨立出來.
具體到 Cursor 只能讓參考已有的文檔了, 我這裏也描述不清楚:
https://github.com/omcljs/om/...
https://github.com/dustingetz...

依然存在不足

通過重構, 以前的目標大體算是達成了:

  • state tree 的結構能夠經過人爲控制, 更加清晰

  • 跨組件的狀態操做可以實現, 從 dispatch 裏均可以作到

  • 全局狀態和組件化寫法共存, 這是以前就達到的, 沒有破壞

  • 統一到了 single app state, 沒有以前獨立的 states 了

靈活性上其實還能夠, 試着改了以前的代碼, 基本確承認行.不過麻煩的地方主要是在膠水代碼的量太多了, 很差抽象掉,這就意味着之後犯錯的機會也會增多, 別人入門的困惑也會增多.對新手而言, 他們恨不得是語法糖, 不要費力思考, 直接就能用,並且全部人使用別人的代碼時都但願依賴的信息越少越好,從這個角度, Respo 目前狀態管理的寫法存在不少坑, 還得想辦法填一下.

相關文章
相關標籤/搜索