Manager
Managers 是处理全局副作用的单例。有点像中心数据 store 的 useEffect()。
默认的 manager 负责编排 Data Client
开箱即用提供的复杂异步行为。你可以通过 getDefaultManagers() 轻松配置它们,也可以用自定义的 Managers 进行扩展。
Manager 必须实现 middleware,它把 Manager 接入中心 store 的控制流。此外,cleanup() 和 init() 会接入 store 的生命周期,用于初始化和清理行为。
type Dispatch = (action: ActionTypes) => Promise<void>;
type Middleware = (controller: Controller) => (next: Dispatch) => Dispatch;
interface Manager {
middleware: Middleware;
cleanup(): void;
init?: (state: State<any>) => void;
}
生命周期
middleware
middleware 与 redux middleware 非常相似。唯一的区别在于 next() 函数会返回一个 Promise。
在使用 <DataProvider /> 时,这个 promise 会在 reducer 的更新被提交后 resolve。这是必要的,因为提交阶段是异步调度的。这样就可以构建在 DOM 更新之后、并基于新计算出的状态执行工作的 manager。
由于 redux 是完全同步的,必须在 Reactive Data Client 风格的中间件前面放一个适配器,以确保它们能够消费 promise。反过来,redux 中间件也必须修改为透传 promise。
中间件会拦截被派发的 action,并且还可能派发自己的 action。要进一步了解中间件,请参阅 redux 文档。
init(state)
在 provider 挂载后以初始状态调用。适合在启动时运行依赖于状态确实存在的初始化逻辑。
cleanup()
在 manager 不再使用后,清理所有残留的资源。
向 Reactive Data Client 添加 manager
使用 DataProvider 的 managers prop。务必将其提升到 模块级别 或包裹在 useMemo() 中,以确保它们不会被重新创建。Manager 拥有内部状态,因此不要反复重新创建它们,这一点很重要。
- Web
- React Native
- NextJS
- Expo
import { DataProvider, getDefaultManagers } from '@data-client/react';
import { createRoot } from 'react-dom/client';
import App from './App';
import MyManager from './MyManager';
const managers = [...getDefaultManagers(), new MyManager()];
createRoot(document.body).render(
<DataProvider managers={managers}>
<App />
</DataProvider>,
);
import { DataProvider, getDefaultManagers } from '@data-client/react';
import { AppRegistry } from 'react-native';
import App from './App';
import MyManager from './MyManager';
const managers = [...getDefaultManagers(), new MyManager()];
const Root = () => (
<DataProvider managers={managers}>
<App />
</DataProvider>
);
AppRegistry.registerComponent('MyApp', () => Root);
'use client';
import { getDefaultManagers } from '@data-client/react';
import { DataProvider } from '@data-client/react/nextjs';
import MyManager from './MyManager';
const managers = [...getDefaultManagers(), new MyManager()];
export default function Provider({
children,
}: {
children: React.ReactNode;
}) {
return <DataProvider managers={managers}>{children}</DataProvider>;
}
import Provider from './Provider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Provider>{children}</Provider>
</body>
</html>
);
}
import { Stack } from 'expo-router';
import { DataProvider, getDefaultManagers } from '@data-client/react';
import MyManager from './MyManager';
const managers = [...getDefaultManagers(), new MyManager()];
export default function RootLayout() {
return (
<DataProvider managers={managers}>
<Stack>
<Stack.Screen name="index" />
</Stack>
</DataProvider>
);
}
控制流
Manager 通过其生命周期和中间件与 DataProvider store 集成。它们通过拦截和派发 action 以及读取内部状态来进行交互,从而编排复杂的控制流。

middleware 的职责是派发 action、响应 action,或者两者兼而有之。
派发 Action
Controller 提供了类型安全的 action 派发器。
import type { Manager, Middleware } from '@data-client/react'; import CurrentTime from './CurrentTime'; export default class TimeManager implements Manager { declare protected intervalID?: ReturnType<typeof setInterval>; middleware: Middleware = controller => { this.intervalID = setInterval(() => { controller.set(CurrentTime, { id: 1 }, { id: 1, time: Date.now() }); }, 1000); return next => async action => next(action); }; cleanup() { clearInterval(this.intervalID); } }
读取和消费 Action
actionTypes 包含用于区分不同 action 的所有常量。
import type { Manager, Middleware } from '@data-client/react'; import { actionTypes } from '@data-client/react'; export default class LoggingManager implements Manager { middleware: Middleware = controller => next => async action => { switch (action.type) { case actionTypes.SET_RESPONSE: if (action.endpoint.sideEffect) { console.info( `${action.endpoint.name} ${JSON.stringify(action.response)}`, ); // wait for state update to be committed await next(action); // get the data from the store, which may be merged with existing state const { data } = controller.getResponse( action.endpoint, ...action.args, controller.getState(), ); console.info(`${action.endpoint.name} ${JSON.stringify(data)}`); return; } // actions must be explicitly passed to next middleware default: return next(action); } }; cleanup() {} }
在条件块中,action 的类型会被收窄,从而鼓励安全地访问其成员。
如果我们想“处理”某个 action,可以通过不调用 next 来“消费”它。
import type { Manager, Middleware, EntityInterface, } from '@data-client/react'; import { actionTypes } from '@data-client/react'; import isEntity from './isEntity'; export default class CustomSubsManager implements Manager { declare protected entities: Record<string, EntityInterface>; middleware: Middleware = controller => next => async action => { switch (action.type) { case actionTypes.SUBSCRIBE: case actionTypes.UNSUBSCRIBE: const { schema } = action.endpoint; // only process registered entities if (schema && isEntity(schema) && schema.key in this.entities) { if (action.type === actionTypes.SUBSCRIBE) { this.subscribe(schema.key, action.args[0]?.product_id); } else { this.unsubscribe(schema.key, action.args[0]?.product_id); } // consume subscription if we use it return Promise.resolve(); } default: return next(action); } }; cleanup() {} subscribe(channel: string, product_id: string) {} unsubscribe(channel: string, product_id: string) {} }
通过 return Promise.resolve(); 而不是调用 next(action),我们可以阻止排在这个 manager 之后的 manager 看到该 action。
类型:FETCH、SET、SET_RESPONSE、
RESET、SUBSCRIBE、UNSUBSCRIBE、
INVALIDATE、INVALIDATEALL、EXPIREALL
使用场景
常见 Manager 使用场景的最小示例: