引言
Zustand 是目前 React 生態中最流行的狀態管理庫之一。它以極簡著稱,也是我個人非常喜歡的庫。 但在長期的使用中,我常常產生一種違和感:我們在一個名為“函數式”的庫裏,費力地模擬着面向對象。
那個無處不在的 get()
來看看經典的 Zustand 寫法:
const useStore = create((set, get) => ({
count: 0,
inc: () => set({ count: get().count + 1 }),
actionB: () => {
// 調用另一個 Action
get().inc();
// 獲取當前狀態
const val = get().count;
}
}))
仔細審視這個 get():
- 它就是
this:它的作用就是訪問當前實例的上下文。 - 它是“二等公民” :你必須顯式地調用它
get(),而且它打破了 JS 引擎對this的自然優化。 - 心智負擔:在寫複雜邏輯時,你滿屏都是
get().xxx,這並不比this.xxx優雅,反而增加了一層函數調用括號的視覺噪音。
那個黑盒般的 set
set 函數的設計初衷是好的(提供類似 setState 的原子更新),但在複雜場景下,它顯得不夠“坦誠”:
- 語義模糊:
set隱藏了更新的細節。是合併?是替換?是深拷貝?你必須去查文檔或看源碼才能確定它是 "Auto Merging" 的。 - 邏輯斷層:當你想複用一段邏輯(比如 Private Method)時,你發現你很難在
create的閉包裏優雅地定義私有輔助函數,往往只能寫在外面,破壞了 Store 的內聚性。
為了函數式而函數式?
我們推崇函數式編程(FP),是因為它有 純函數、無副作用、引用透明 等數學上的美感。
但 Zustand 的 Store 定義是純函數嗎?顯然不是。它是一個包含了狀態(State)和行為(Action)的容器。 在計算機科學中,狀態 + 行為 = 對象(Object) 。
既然我們本質上是在構建一個對象,為什麼要回避 JS 語言原生提供的、經過幾十年打磨的構建對象的最佳工具——Class?
我們為了避嫌 "OOP",發明了一套 (set, get) => ({...}) 的 DSL。這不僅犧牲了 Class 的繼承、屬性訪問器(Getter/Setter)等高級能力,還增加了一層理解成本。
這是否是一種形式上的函數式正確,而非工程上的務實選擇?
另一種可能性
如果在 React 狀態管理中,我們不再視 class 為洪水猛獸,而是承認它作為 "Model" 載體的合理性,會發生什麼? 這或許值得我們深思。