前言
自從React16
版本發佈Hooks
以來,大家紛紛上車嚐鮮。毫無疑問,Hooks
在一定程度上解決了組件間功能和邏輯複用的問題,在組件間的邏輯的封裝和複用確實真香,但Hooks
在數據狀態的共享方法略有不足,雖然可以使用useReducer
實現數據狀態管理,但在一定程度上是對redux
的思想的複用。我們知道redux
、Flux
、dva
等這些React
狀態管理的工具,實際上都是對action
、dispatch
、reducer
、useStore
、Provider
、Context
這些概念的排列組合,概念太多,學習入手成本較高,項目使用都差不多,會有產生許多的模版代碼。
hox
既然如此是否有學習成本比較低,入手簡單的針對Hooks
的狀態管理器呢?答案是有,其中來自來自螞蟻金服體驗技術部hox就是這樣一種工具。下面我們從學習、上手、原理幾個方法聊聊這個被定爲爲下一代React
狀態管理器,看看其是否符合其定位的目標。
學習
hox
來自螞蟻金服體驗技術部,其背後的團隊在React
各種實踐場景上都有很豐富的經驗,因此其後續的維護和迭代還是很靠譜的。可能因爲其只有一個API
,因此其文檔也是十分簡單的,一眼就能看到頭了。這對於我們前端的開發者而言就是很友好的,由於千變萬化的前端,各種輪子、各種技術層出不窮,前端的娃娃們表示學不動了。而這種只有一個API
的工具,我們表示還是可以學的動的。hox
的詳細文檔可以參看github
上的readme
支持中英文,鏈接如下:
- 中文文檔:https://github.com/umijs/hox/blob/master/README-cn.md
- 英文文檔:https://github.com/umijs/hox/blob/master/README.md
特性
hox
作爲下一代的狀態管理器,其具有如下特性:
- 只有一個 API,簡單高效,幾乎無需學習成本
- 使用 custom Hooks 來定義 model,完美擁抱 React Hooks
- 完美的 TypeScript 支持
- 支持多數據源,隨用隨取
上手
hox
的上手使用體驗還是很不錯的,因爲十分簡單。talk is cheap,show me code。我們直接上碼看看。
import { useState } from "react";
import { createModel } from "hox";
function useCounter() {
const [count, setCount] = useState(0);
const decrement = () => setCount(count - 1);
const increment = () => setCount(count + 1);
return {
count,
decrement,
increment
};
}
export default createModel(useCounter);
import useCounterModel from "../models/counter";
function App(props) {
const { count, increment, decrement } = useCounterModel();
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
使用hox
就是這麼簡單,通過createModel
包裝一下將custom hook
變成share hook
,就可以在各個組件之間共享數據狀態,並實現邏輯封裝和複用。
原理
createModel
會創建一個Executor
組件的實例,並在執行custom hook
, custom hook
的執行結果會被保存起來。最後,它會返回一個新的share hook
即是model hook
實現數據和邏輯的共享。源碼如下:
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) {
// 實例化一個容器,通過發佈訂閱模式實現對狀態改變的推送
const container = new Container(hook);
// 實例化 Executor 組件,當數據發生改變時,notify 所有訂閱者進行更新
render(
<Executor
onUpdate={val => {
container.data = val;
container.notify();
}}
hook={() => hook(hookArg)}
/>
);
// useModel 這是 share hook
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!;
};
// share hook 代理 custom hook 返回的值
Object.defineProperty(useModel, "data", {
get: function() {
return container.data;
}
});
return useModel;
}
// 這是 hook 依賴項對比函數
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
大道至簡,只有一個API
,但其既能滿足邏輯的封裝和複用,又能滿足狀態複用和管理,值得嘗試的狀態管理器。