前端【免费下载链接】rematchThe Redux Framework项目地址https://gitcode.com/gh_mirrors/re/rematch点击查看免费下载本文基于 Rematch 官方 API 文档 Plugins 展开系统讲解 Rematch 插件机制的六个组成部分config、exposed、createMiddleware、onReducer、onRootReducer、onModel、onStoreCreated并结合 packages/core 的源码与官方插件实现说明每个属性在 store 创建流程中的真实执行时机、参数含义与返回值语义。读完后你能够独立编写一个完整插件理解插件如何注入模型、覆盖 reducer 链、向 store 暴露方法以及 TypeScript 泛型如何为插件扩展的类型提供保障。插件机制在 Rematch 中的作用插件Plugin是 Rematch 提供的一套标准扩展点用于在不修改核心代码的前提下扩展功能覆盖 Redux 配置、向 store 添加额外模型、注入自定义中间件甚至替换最终生成的 store。官方文档明确指出插件可以覆盖配置、添加新模型、甚至整体替换 store其设计目标是让 Rematch 团队维护的官方插件immer、select、persist、loading、updated、typed-state与社区插件共用同一套机制。从源码结构看一个 Rematch store 的创建链路为init()接收用户配置调用createConfig()补全默认值createConfig()在生成配置的过程中合并所有插件的config声明模型、redux 配置createRematchStore()构建 Rematch bag内部工具包收集createMiddleware产物创建 Redux store逐个模型执行onModel最后以onStoreCreated收尾。init的入口在 packages/core/src/index.ts它只做两件事createConfig(initConfig || {})和createRematchStore(config)。插件正是插在这两个阶段的各个环节中的。插件对象的完整属性根据文档插件是一个对象可以包含以下属性。类型定义见 packages/core/src/types.tsPlugin接口继承自PluginHooks四个钩子 createMiddleware并额外允许config与exposed两个属性。config向 store 注入模型与 Redux 配置config是一个形如{ models, redux }的对象形状与 init 方法接受的配置一致分别参见 Models 与 Redux 文档。它的两个用途models让插件向 store 注入额外模型。这是 Rematch 插件添加基础设施状态的正规方式例如rematch/updated插件就是通过config.models注入一个名为updated的模型来记录各 effect 的最后执行时间见 packages/updated/src/index.ts。redux覆盖 Redux 层配置如combineReducers、initialState、reducers、middlewares、enhancers等。config的合并逻辑在 packages/core/src/config.ts 中实现具体规则是子项合并方式源码行为config.models浅合并用户模型优先config.models merge(config.models, plugin.config.models)config.redux.initialState浅合并用户优先merge(original, pluginValue)config.redux.reducers/rootReducers浅合并用户优先同上config.redux.enhancers/middlewares追加[...config, ...pluginValue]数组拼接config.redux.combineReducers/createStore替换用户优先config.X \|\| plugin.config.X其中merge是一个浅合并函数给原对象用户配置以优先权merge 实现return extra ? { ...extra, ...original } : original。这意味着如果用户在init中定义了与插件同名的模型或 reducer key用户定义会覆盖插件的注入。middlewares与enhancers则是多插件累加关系每个插件的中间件都会加入 Redux 的中间件链。单元测试验证了这些行为插件注入单个/多个模型should add a model / should add multiple models以及initialState的合并should merge plugin configs into configs见 packages/core/test/plugins.test.ts。exposed向 store 暴露插件方法exposed是一个{ [string]: ((rematchStore, ...args) any) | object }形式的映射用于在 store 上挂属性实现插件与插件之间、插件与业务代码之间的通信。文档特别强调exposed的执行早于onModel与onStoreCreated钩子所以在钩子里可以直接使用暴露出来的属性。每个 exposed 项的取值规则源码在 packages/core/src/rematchStore.ts 的addExposed若项是函数store 上得到的是一个包装函数调用时rematchStore会作为第一个参数自动传入其余参数透传若项是对象store 上得到的是Object.create(plugin.exposed[key])—— 基于该对象的原型创建的新对象而非直接引用因此插件可以在后续钩子如onModel中往这个对象上安全地写属性而不影响插件内部定义。类型层面types.ts 用ObjectNotAFunction这个约束型{ [k: string]: any } ({ bind?: never } | { call?: never })确保对象不能带有bind/call等函数特征ExposedFunction则声明了函数必须接收RematchStore作为第一个参数。典型案例是rematch/select插件它通过exposed: { select, sliceState, selectorCreator }把select工厂暴露出去packages/select/src/index.ts随后在onModel中往select[model.name]上逐个模型地挂选择器最后在onStoreCreated里执行store.select select。这一系列操作能成立正依赖于 exposed 先于两个钩子执行。createMiddleware需要访问 bag 的自定义中间件createMiddleware的签名是(bag) Redux.Middleware。与其他中间件的区别在于它可以拿到 Rematch 的内部工具包bag见下文从而访问bag.effects、bag.models、bag.reduxConfig等内部状态。文档同时提示如果不需要 bag也可以直接把中间件放到config.redux.middlewares即插件的config或init的redux配置里走更简单的路径。执行位置在 rematchStore.ts// collect middlewares from plugins bag.forEachPlugin(createMiddleware, (createMiddleware) { bag.reduxConfig.middlewares.push(createMiddleware(bag)) })所有插件返回的中间件会 push 进reduxConfig.middlewares与config.redux.middlewares注入的中间件一起在createReduxStore中统一经Redux.applyMiddleware(...)组合packages/core/src/reduxStore.ts。核心自带的一个参考实现是 effects 中间件createEffectsMiddleware它先执行 reducer 分支next(action)再执行对应的 effect 并返回其结果。测试用例 should add middleware 用一个把所有 action payload 改写为 100 的中间件验证了插件中间件确实进入了 action 处理链plugins.test.ts。onReducer包装单个模型的 reducer签名(reducer, modelName, bag) reducer | void。在 Rematch 为每个模型构建base reducer即把模型所有reducers合并、并先跑baseReducer的组合函数之后触发返回新 reducer 则整体替换 Rematch 生成的版本。调用点位于模型 reducer 构建流程末尾packages/core/src/reduxStore.tsbag.forEachPlugin(onReducer, (onReducer) { reducer onReducer(reducer, model.name, bag) || reducer }) bag.reduxConfig.reducers[model.name] reducer|| reducer的写法明确了语义返回 falsyvoid/undefined时保持原 reducer返回函数时替换。注意此时传入的 reducer 已经包好了模型的baseReducer与reducers映射插件拿到的是模型级别的最终 reducer。rematch/persist是典型用法onReducer按modelName查找嵌套持久化配置命中则返回persistReducer(reducerConfig, reducer)未命中返回undefinedpackages/persist/src/index.ts。onRootReducer包装根 reducer签名(reducer, bag) reducer | void触发时机在根 reducer 组装完毕之后。Rematch 先用combineReducers用户或插件可覆盖合并所有模型 reducer再套上rootReducers包装层最后交给插件链createRootReducerbag.forEachPlugin(onRootReducer, (onRootReducer) { rootReducer onRootReducer(rootReducer, bag) || rootReducer })rematch/persist用它把整个根 reducer 换成persistReducer(persistConfig, rootReducer)实现全量状态持久化packages/persist/src/index.ts。对比onReducer按模型粒度拦截、onRootReducer对整个 state 树拦截可以清楚区分两者的适用场景嵌套持久化部分模型与根级持久化整个 store。onModel模型就绪后介入签名(namedModel, rematchStore) void返回值为 void。文档说明其触发时机是整个模型装配完成——reducer 与 dispatcher 均已就绪并且每次动态添加模型store.addModel时同样会执行。典型用途收集模型 reducer/effects 信息、改写它们或创建新属性。执行点在 enhanceModelfunction enhanceModel(rematchStore, bag, model) { createEffectDispatcher(rematchStore, bag, model) bag.forEachPlugin(onModel, (onModel) { onModel(model, rematchStore) }) }注意rematchStore此时已带完整的dispatch[modelName][reducer|effect]结构。rematch/updated插件展示了onModel的高级用法遍历rematch.dispatch[name]用isEffect标记识别 effect dispatcher把每个 effect 的 dispatch 函数包一层——原函数执行后Promise 完成时再 dispatchupdated模型的onUpdate从而自动记录 effect 的最后触发时间全程无需用户手写任何代码packages/updated/src/index.ts。onModel还支撑rematch/select的选择器装配为每个模型初始化select[model.name]把model.selectors对象或工厂函数解析为惰性 getter避免模型间选择器互相引用时的初始化顺序问题packages/select/src/index.ts。onStoreCreated最后的钩子可整体替换 store签名(rematchStore, bag) rematchStore | void。它在 Rematch store 完全就绪后运行是插件链的最后一环。文档说明返回新 store 则替换 Rematch 创建的 store通常用于给 store 添加额外属性或函数用 TypeScript 写此类插件时务必同步更新 store 的类型定义否则新增属性没有类型。执行与替换逻辑在 rematchStore.tsbag.forEachPlugin(onStoreCreated, (onStoreCreated) { rematchStore onStoreCreated(rematchStore, bag) || rematchStore }) return rematchStore|| rematchStore意味着返回 falsy 时保留原 store而多个插件都会依次拿到当前最新版本的 store 并可以再次替换。测试用例 plugins should be able to set a value in store 直接在钩子里给 store 赋值store.returned 42验证了这条路径plugins.test.ts。钩子的执行顺序与 Rematch bag综合源码一次init()中插件相关逻辑的完整时序为config 合并阶段config.ts遍历config.plugins合并plugin.config.models与plugin.config.redux然后执行validatePluginbag 构建与中间件收集rematchStore.ts创建bagpush 核心 effects 中间件再依次调用各插件的createMiddleware模型 reducer 构建reduxStore.ts每个模型的组合 reducer 完成后触发onReducer根 reducer 构建reduxStore.ts触发onRootReducer随后createReduxStore组装 enhancers/createStore得到 Redux storeexposed 注入rematchStore.tsaddExposed先于模型装配执行保证onModel/onStoreCreated中可用模型装配rematchStore.ts两轮遍历——先prepareModel注入 reducer dispatchers再enhanceModel生成 effect dispatchers 并触发onModel分两轮是为了让循环引用的模型在 effects 中可通过解构互相访问源码注释说明了这一点收尾依次触发onStoreCreated返回最终 store。贯穿其中的bag是 RematchBag 类型由 bag.ts 创建包含models所有 NamedModel 数组模型名内嵌reduxConfig完整 Redux 配置forEachPlugin(method, fn)遍历所有插件并对实现了指定钩子的插件回调——所有钩子都通过它派发保证插件按plugins数组声明顺序执行effectsaction type 到 effect 函数的全局映射effects 中间件依赖它。文档中bag的称呼即来源于此它是Rematch 内部可用值的集合故意对最终用户隐藏只开放给插件的createMiddleware、onReducer、onRootReducer、onStoreCreated。一个完整插件示例下面把文档中的插件骨架补全为可运行的形态并附上与源码行为对应的注释import { init } from rematch/core const plugin { // 1. 注入模型 覆盖 redux 配置在 createConfig 阶段合并用户配置优先 config: { redux: { combineReducers: customCombineReducers, }, models: { extra: extraModel, }, }, // 2. 暴露属性函数会被包装store 作为第一参对象会以原型方式挂到 store 上 exposed: { select: {} }, // 3. 能拿到 bag 的自定义中间件进入 Redux 中间件链 createMiddleware: (rematchBag) (store) (next) (action) { // 可访问 rematchBag.effects / rematchBag.models / rematchBag.reduxConfig // 注意必须调用 next(action)否则 action 链会中断 return next(action) }, // 4. 模型 reducer 就绪后触发返回新函数则替换返回 void 则保留 onReducer(reducer, modelName, bag) { // do something }, // 5. 根 reducer 就绪后触发返回新函数则整体替换 onRootReducer(reducer, bag) { // do something }, // 6. 每个模型装配完成后触发addModel 动态加模型时也会触发 onModel(namedModel, rematchStore) { // do something }, // 7. 最后的钩子返回新 store 则替换 onStoreCreated(rematchStore, bag) { // do something }, } const store init({ models: { counter: { state: 0, reducers: { inc: s s 1 } } }, plugins: [plugin], })该示例与文档 plugins 页 给出的骨架一致注释补充了各属性的实际执行阶段。验证方面核心测试 packages/core/test/plugins.test.ts 覆盖了onModel订阅、插件中间件改写 action、config.models注入单个/多个模型、config.redux.initialState合并、以及onStoreCreated向 store 写入属性共五组场景。另外提醒一点运行时行为开发环境NODE_ENV ! production下validatePlugin 会校验五个钩子/工厂若已定义必须是函数报错信息形如Plugin onStoreCreated must be a function且所有错误合并后一次性抛出——这要求插件作者不要把这些 key 写成非函数值。TypeScript 写插件的要点Plugin类型types.ts有三个泛型参数TModels业务模型、TExtraModels插件注入的额外模型默认Recordstring, never、TExposedModels。官方插件的写法是范式rematch/updated定义ExtraModelsFromUpdatedupdated/src/index.ts声明自己注入的updated模型使initTModels, ExtraModelsFromUpdatedTModels后store.getState()自动包含updated字段store.dispatch.updated.onUpdate也有完整类型rematch/select返回PluginTModels, TExtraModels其exposed中的select在运行时被逐模型填充store 上的store.select通过onStoreCreated赋值获得。文档给出的 TypeScript 注意事项依然有效若插件通过onStoreCreated给 store 挂了新属性如store.select、store.returned用户侧应扩展 store 类型声明以获得类型提示——plugins.test.ts中测试注释也点明了这一点plugins.test.ts。官方插件速览每类钩子的真实用例官方插件总览见 docs/plugins/index.md仓库内各插件源码分别位于packages/下。从插件机制视角可以归纳插件主要使用的扩展点用途rematch/persistonReduceronRootReduceronStoreCreated包装 redux-persistonReducer处理嵌套持久化onRootReducer包装根 reduceronStoreCreated中创建 persistorrematch/selectexposedonModelonStoreCreated基于 reselect 为模型生成惰性、可互相引用的store.select[model]rematch/updatedconfig.modelsonModel注入updated模型并包装所有 effect dispatcher自动记录最后触发时间这也印证了文档的推荐路径如果你找不到需要的插件——可以自己写一个插件 API 页面即本文依据的 docs/api-reference/plugins.md就是编写起点参考 Rematch 团队各插件的源码即可上手。参考路径文档插件 API、init/store 配置、Models、Redux 配置、插件总览核心实现init 入口、config 合并、store 装配与钩子派发、reducer 构建、bag 创建、插件校验、类型定义测试插件行为测试赞分享前端【免费下载链接】rematchThe Redux Framework项目地址https://gitcode.com/gh_mirrors/re/rematch点击查看免费下载相关推荐conda 插件开发指南使用 conda_pre_commands 钩子扩展 conda 命令前置行为conda 插件开发指南使用 conda_pre_commands 钩子扩展 conda 命令前置行为 本篇技术指南围绕 conda 插件体系中的 conda包管理器CLIPostGraphile 原始插件 API 实战用 Graphile Engine 钩子深度扩展你的 GraphQL SchemaPostGraphile 原始插件 API 实战用 Graphile Engine 钩子深度扩展你的 GraphQL Schema 本篇技术指南以 PostG后端API网关micro插件钩子系统深度定制编辑器行为micro插件钩子系统深度定制编辑器行为 你是否曾因编辑器功能固化而无法实现特定工作流作为一款现代终端文本编辑器micro通过强大的插件钩子系统Plug开发工具CLI上一篇Dufs错误处理与日志分析终极指南快速排查服务异常的5个实用技巧下一篇Neovim-Qt性能优化指南如何调校GUI响应速度和内存使用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?