Respo Hooks 寫法的原由和示例

近期對 Respo 的狀態管理方案進行了一次更新,
具體的代碼能夠看 https://github.com/Respo/resp...git

原由

Respo 的 States 方案爲了方便熱替換時保存狀態, 作了一些限制,github

  • 組件掛載的時候以及渲染過程中不能 dispatch 事件,
  • states 以樹形的方式存儲, 須要手動調用 cursor-> 在組件之間傳遞分支,
  • 修改狀態使用的路徑, 也就是 cursor, 跟組件對應, 方便簡寫.

React Hooks 出來的時候, 我固然意識到了 Respo 功能的不足,
以前比較久了, 我爲 Respo 加上了基礎的 Effects, 能夠加一些 DOM 操做,
React Hooks 能夠把部分的組件狀態抽到插件裏去, Respo 不行.
Respo 的 cursor 是跟組件綁定的, 插件寫法沒法傳遞 cursor.app

另外一方面, 我在 Phlox 項目當中因爲須要, 設計了個簡單的 states tree 方案,
這個方案裏 cursor 是須要用戶手工傳遞的, 比較囉嗦, 可是勉強夠用.
後來我仔細想一想, 這個方案對於 Respo 來講, 也是夠用的.
雖然寫起來會囉嗦, 可是用戶能夠手動傳遞 cursor, 也就意味着能夠拆成插件複用.函數

代碼示例

文檔上寫得也不詳細, 這邊稍微再描述一下.
首先 Respo 的狀態樹, 大體上是這樣一個結構,
其中 :data 專門用於存儲節點的數據, :cursor 做爲保留字段,
這就是一個樹形節奏, 跟組件直接對應, 可是大體跟狀態分支對應上:ui

{
  :cursor []
  :files {
    :ns {
      :add {
        :data {:show? false, :failure nil}
        :modal {
          :data {:text nil, :failure nil}
        }
      }
      "app.rude" {
        ":rmapp.rude" {
          :data {:show? false}
        }
      }
    }
    "app.rude" {
      :add {
        :data {:show? false, :failure nil}
        :modal {
          :data {:text nil, :failure nil}
        }
      }
    }
  }
}

須要更新狀態時, 須要一個 cursor, 能夠是 [:file :ns "app.rude" ":rmapp.rude"],
後面還有個 :data, 有了 cursor, 就能定位到數據作更新了.
後面大體想象, 組件用的就是一個分支的數據, 而 cursor 對應分支的路徑.
具體在代碼當中比較囉嗦, 不過比較容易能夠維護二者的對應關係.
最終獲得相似組件局部狀態的一個效果.spa

爲了方便書寫, 我增長了一個 >> 函數, 把 states 和 cursor 一塊兒傳遞.
通過雜七雜八的抽象之後, 最終獲得這樣效果的代碼:
https://github.com/Respo/aler...
這中間是省略了好多的過程, 也不打算很詳細描述了, 具體要看文檔(還沒補好).插件

相應地, 更新狀態的部分我加了 update-states 函數, 做爲一個簡寫.
狀態更新做爲 dispatch action 的一種特殊狀況, 跟 dispatch 一塊兒被處理.
若是沒看以前, 我明確一下, Respo 當中 states 是跟 store 一塊兒存儲在全局的.
能分支讀取, 能維護 cursor 作更新, store 當中能響應, 整個流程串起來了.設計

延伸的影響

增長這塊功能主要的目標, 跟 React Hooks 相似, 爲了邏輯的複用,
Respo 的組件跟 React 相似, 不容許從外部操做狀態,
這就意味着我封裝出來的 Modal 組件顯得比較奇怪了, 或者說死板,
要麼我外邊維護一個 visible 狀態, 傳進去, 而且加上 on-change 作切換,
要麼我把觸發打開關閉的部分也放進組件裏邊, 這樣使用起來就有點僵化了.code

而 Hooks 形態的寫法, 開始容許狀態被抽取到一個獨立的函數當中,
好比我調整過的 prompt 用法, 就能夠抽出的一個插件當中,
https://github.com/Respo/aler...事件

(defn use-prompt [states options]
  (let [cursor (:cursor states), state (or (:data states) {:show? false, :failure nil})]
    {:ui (comp-prompt-modal
          (>> states :modal)
          options
          (:show? state)
          (fn [text d!]
            (if (some? @*next-prompt-task) (@*next-prompt-task text))
            (reset! *next-prompt-task nil)
            (d! cursor (assoc state :show? false)))
          (fn [d!] (d! cursor (assoc state :show? false)) (reset! *next-prompt-task nil))),
     :show (fn [d! next-task]
       (reset! *next-prompt-task next-task)
       (d! cursor (assoc state :show? true)))}))

插件暴露 uishow 方法兩部分, ui 用於渲染, show 方法用戶更新狀態.
這樣, 以往代碼當中至關多的彈層的邏輯就能夠抽出作複用了.

後續代碼會繼續更新. 目前也認識到 Respo Hooks 相比 React Hooks 比較侷限比較多,特別是 Effects 那塊, React 作得比較強大了, Respo 這方面功能很弱.但願目前來講這個功可以用, 這樣我能對 Calcit Editor 遺留的代碼作一些整理.

相關文章
相關標籤/搜索