@lightfish/react-model
v0.0.1
Published
Bridge a custom React hook to Context with Provider and useModel.
Readme
@lightfish/react-model
将一个自定义 React Hook 桥接到 React Context,自动生成 Provider 和 useModel。避免 prop drilling,同时保持 hook 的可测试性和可组合性。
安装
pnpm add @lightfish/react-modelpeerDependencies 要求:
react>= 18
快速开始
1. 定义 model(一个普通 hook)
// models/counter.ts
import { useState } from "react";
export function useCounter(initial: { count: number }) {
const [count, setCount] = useState(initial.count);
return {
count,
increment: () => setCount((c) => c + 1),
decrement: () => setCount((c) => c - 1),
};
}2. 桥接到 Context
// models/counter.ts
import { createCustomModel } from "@lightfish/react-model";
export const { Provider: CounterProvider, useModel: useCounterModel } =
createCustomModel(useCounter);3. 在组件树顶层注入
// App.tsx
import { CounterProvider } from "./models/counter";
function App() {
return (
<CounterProvider value={{ count: 0 }}>
<Child />
</CounterProvider>
);
}4. 在任意子组件消费
// Child.tsx
import { useCounterModel } from "./models/counter";
function Child() {
const { count, increment, decrement } = useCounterModel();
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}为什么需要这个?
常规 Context + hook 的写法需要手动声明 Context、Provider、Consumer hook,样板代码重复:
// 常规写法:每次都要手动写这些
const CounterContext = createContext<ReturnType<typeof useCounter>>(undefined!);
function CounterProvider({ children, value }: { children: ReactNode; value: { count: number } }) {
const state = useCounter(value);
return <CounterContext.Provider value={state}>{children}</CounterContext.Provider>;
}
function useCounterModel() {
const ctx = useContext(CounterContext);
if (!ctx) throw new Error("...");
return ctx;
}createCustomModel 把这段样板收为一行调用,保证类型安全的同时减少重复。
API
createCustomModel(useHook)
参数
| 参数 | 类型 | 说明 |
|---|---|---|
| useHook | (props: P) => T | 任意自定义 hook。其第一个参数类型 P 会映射为 Provider 的 value prop 类型。 |
返回值
| 名称 | 类型 | 说明 |
|---|---|---|
| Provider | (props: { children, value?: P }) => JSX.Element | 运行 hook 并将返回值注入 Context。若 hook 无参数则不需要传 value。 |
| useModel | () => T | 读取 Context 中的 hook 返回值。必须在 Provider 内部调用。 |
类型参数
createCustomModel<F, T, P>(useHook: F)| 参数 | 推导来源 | 说明 |
|---|---|---|
| F | 传入的 hook 函数类型 | 约束 hook 签名 |
| T | ReturnType<F> | Context 中保存的值类型(hook 返回值) |
| P | Parameters<F>[0] | Provider 的 value prop 类型(hook 的第一个参数) |
最佳实践
不需要参数的 hook
如果 hook 不接受参数,Provider 无需传 value:
function useUser() {
const [user] = useState({ name: "Alice" });
return user;
}
const { Provider, useModel } = createCustomModel(useUser);
// 使用
<Provider>
<Child />
</Provider>多个 model 组合
不同的业务领域分别定义各自的 model,在组件树中按需嵌套:
function App() {
return (
<CounterProvider value={{ count: 0 }}>
<UserProvider>
<Main />
</UserProvider>
</CounterProvider>
);
}与外部状态库配合
createCustomModel 不关心 hook 内部实现,可以传入使用了 zustand、jotai、Redux 等外部库的 hook:
import { useStore } from "zustand";
function useZustandStore() {
return useStore(myStore);
}
const { Provider, useModel } = createCustomModel(useZustandStore);License
MIT
