在以前的React Hooks:初探·實踐提到過hooks更符合React編程模型,其中custom hooks更是讓人眼前一亮,只能說用過的都說香,可是一直沒找到貼合hooks的狀態管理,雖然hooks的設計挺多來自於redux,可是redux基本和hooks沒有交集,至於mobx,也推出了基於hooks的如useLocalStore、useObserve等Hooks,可是在個人使用過程當中也沒有感受引入Hooks帶了很大便利,個人我的見解是redux和mobx更強調的集中式管理狀態,而Hooks更強調狀態分享。最近找到了兩個比較有意思的狀態管理工具,一個是來自fackbook官方實驗室(非React官方)的recoil和來自螞蟻金服的hox,兩個都接觸了一下,以爲recoil要理解的概念和API挺多,沒有那麼香,下面重點介紹一下我以爲既簡單又更貼合hooks的hoxreact
hox是螞蟻金服推出的下一代react狀態管理器。git
我以爲官方這麼自信的緣由有兩點,第一點就是隻有一個API——createModel,這個和文檔同樣簡單的API確實可以吸引很多眼球,爲上手提供了條件;第二點就是hox完美的擁抱了react hooks,而hooks又是react官方不斷安利的,有react官方做爲重要支撐,綜上所述,因此單方面宣佈下一代仍是能夠接受的。github
爲何說hox更提盒hooks的狀態管理呢?下面結合源碼說一下個人我的見解!編程
對於使用而言,hox基本上只須要和一個API——createModel打交道,這一點仍是值得點個讚的。(固然還有withModel(在class組件使用)等API,不過感受都是買一送一的感受)。對於設計而言,hox很完美地利用了hooks自身強大地狀態管理,可是hooks是針對單一組件而言,因此hox巧妙地利用了react reconciler,經過自定義渲染器把散落各個組件的狀態以model爲單位收集起來,有點像是把hooks的定義往外層提,或者說是額外建立一個組件樹,經過自定義hooks和自定義渲染器實現了共享hooks,經過container容器在狀態更新時通知給各個組件。redux
直接上源碼(分三塊解析):api
import { ModelHook, UseModel } from "./types";
import { Container } from "./container";
import { Executor } from "./executor";
import React, { useEffect, useRef, useState } from "react";
import { render } from "./renderer";
export function createModel<T, P>(hook: ModelHook<T, P>, hookArg?: P) {
/**
* 第一塊:以model爲一個單位實例化一個對應的容器進行數據管理
* Container主要作了兩個事情,經過ES6的Set結構存儲了觀察者,再暴露notify方法在通知所有觀察者更新,就是
* 下面useModel方法中useEffect裏定義的subscriber函數
*/
const container = new Container(hook);
/**
* 第二塊:基於react reconciler + Executor組件自定義生成了一個渲染器,經過觀察者模式在狀態更新時通知所有觀察者,就是去執行Set結構裏存儲的subscriber函數
* react reconciler用法沒什麼特殊的,能夠直接看文檔
* Executor組件主要執行了兩個函數,一個是createModel傳進來的hook,另外一個是hook返回的data做爲參數去執行onUpdate回調
*/
render(
<Executor
onUpdate={val => {
container.data = val;
container.notify();
}}
hook={() => hook(hookArg)}
/>
);
// 第三塊:返回一個自定義的hooks,
// ①經過useState將container的狀態和React自身的狀態管理掛鉤,經過暴露depsFn函數來控制是否更新狀態
// ②經過useEffect註冊/移除了觀察者
// ③經過defineProperty劫持當前自定義hooks的data屬性,來暴露container的data,作到只讀取當前的狀態
const useModel: UseModel<T> = depsFn => {
const [state, setState] = useState<T | undefined>(() =>
container ? (container.data as T) : undefined
);
const depsFnRef = useRef(depsFn);
depsFnRef.current = depsFn;
const depsRef = useRef<unknown[]>([]);
useEffect(() => {
if (!container) return;
function subscriber(val: T) {
if (!depsFnRef.current) {
setState(val);
} else {
const oldDeps = depsRef.current;
const newDeps = depsFnRef.current(val);
if (compare(oldDeps, newDeps)) {
setState(val);
}
depsRef.current = newDeps;
}
}
container.subscribers.add(subscriber);
return () => {
container.subscribers.delete(subscriber);
};
}, [container]);
return state!;
};
Object.defineProperty(useModel, "data", {
get: function() {
return container.data;
}
});
return useModel;
}
function compare(oldDeps: unknown[], newDeps: unknown[]) {
if (oldDeps.length !== newDeps.length) {
return true;
}
for (const index in newDeps) {
if (oldDeps[index] !== newDeps[index]) {
return true;
}
}
return false;
}
複製代碼
hox用起來仍是比較省心的,能夠直接參考文檔bash
說多無益,show me the code! 下面是基於redux和hox實現的todoListide
redux: 基於reudx官方例子 + React Profiler函數
仍是我以前說的Hooks的主要成本不在於api的學習,而是在於心智模型的轉變。這一點一樣適用於hox,在redux和mbox咱們更關注的是stroe中狀態的變化,可是在hox中咱們更應該關注每個model,說白了就是被createModel包裝事後的那個custom hooks,更傾向特定的功能邏輯。像上面的todoList,咱們在寫hooks應該奔着實現添加todo,能夠改變todo狀態,還有篩選功能的todoList。
上面的兩個例子都用到了React Profiler(基於React Profiler寫了一個useProfiler)來做爲簡單的性能比對,雖然hox提供了depsFn來優化性能,在比對了兩個例子的全部操做,以爲基於hox的TodoList就組件而言多了一些沒必要要的渲染,例如第一次執行addTodo的操做,hox版本渲染了三個組件(AddTodo, TodoList, FilterFooter),而redux則渲染了一個組件(VisibleTodoList);第二次addTodo的操做,hox版本也渲染了兩個組件(AddTodo, TodoList)。這一點我也是琢磨了許久,思來想去沒想到好的優化辦法,我的以爲是hox的設計自己致使的問題。(也多是寫法有問題,須要進一步的探索,望高人指點)。可是我以爲只要劃分好model,用好depsFn這個利器,hox的寫法和複用性仍是能夠完勝redux的,因此仍是挺香的。
從學習看源碼到寫這篇文章的過程,一直關注hox的issue,感受有點不溫不火,感受大多數人都處於觀望狀態,如今最新一條的issue是關於hox v2的,這條issue挺有看頭的,因爲v1版本存在挺多的弊端,v2打算配合context,這也意味着使用者須要手動寫provider,可是相對其餘的基於context的庫,這個api也相對簡單。所以結合issuse和hox的發展形勢,我的以爲小型項目(如活動頁等用完即走的項目)能夠考慮接入一下,大型項目就感受不必了,應該保持持續地關注,但願hox能越走越遠,若是能持續搭上hooks的快車,感受hox仍是挺有前途的!再退一步說,即便hox沒能躋身狀態管理的前列,它的設計思想和源碼也是值得一讀的,值得學習!