YAOTU INSIGHTS

Rematch v1 到 v2 迁移实战:Breaking Changes 逐条解析与源码印证

Rematch v1 到 v2 迁移实战:Breaking Changes 逐条解析与源码印证
前端【免费下载链接】rematchThe Redux Framework项目地址https://gitcode.com/gh_mirrors/re/rematch点击查看免费下载本篇聚焦 Rematch 从 1.x 升级到 2.0 的全部破坏性变更Breaking Changes覆盖核心库默认 store 命名、TypeScript 类型系统重构、Effects dispatch 参数限制与插件体系onInit钩子移除、插件配置收敛、Persist 插件对齐 redux-persist。文中每一条变更都结合rematch/core与rematch/persist的源码实现进行印证读完你既能完成升级、定位测试失败点也能理解 v2 类型与插件钩子体系的设计动机。升级前速览v2 到底改了什么官方迁移文档 docs/migrating/from-v1-to-v2.md 列出的破坏性变更共两组Core核心库默认 store 名从数字改为Rematch Store ${number}测试套件中若有断言依赖旧命名可能会挂类型定义typings被重做以规避未来的类型问题v1.x 的类型本就无法正常工作因此改动预计平滑Effects 中dispatch参数在 TypeScript 下不能以函数参数解构的方式书写TypeScript 设计层面的限制。Plugins插件体系移除了onInit钩子插件配置不再允许在其中再嵌套包含其他插件插件类型定义被重做Persist 插件已按 redux-persist 的接口重新实现升级后你可能会遇到一些错误。下面逐条展开并用仓库源码说明这些变更落在哪些实现上。核心变更一默认 store 名从数字变为Rematch Store N在 v1 中如果你不传nameRematch 会给 store 一个数字名称v2 中这一行为改为可读字符串。对应实现位于 createConfiglet count 0 export default function createConfigTModels, TExtraModels( initConfig: InitConfigTModels, TExtraModels ): ConfigTModels, TExtraModels { const storeName initConfig.name ?? Rematch Store ${count} count 1 // ... redux: { // ... devtoolOptions: { name: storeName, ...(initConfig.redux?.devtoolOptions ?? {}), }, }, }三个值得注意的细节计数器count是模块级变量从 0 开始自增。也就是说同一进程中连续调用init()两次而不传name得到的名字依次是Rematch Store 0、Rematch Store 1。这个 store 名不仅用于store.name还会作为devtoolOptions.name的默认值注入 Redux DevTools 增强器配置——在多个 store 并存例如测试中频繁init()时DevTools 里的实例名因此从难以区分的数字变成了带前缀的可读名称。文档特别提示这可能是测试套件中的破坏点。如果你的断言写了expect(store.name).toBe(0)之类依赖旧数字命名的检查升级后需要改为Rematch Store 0。最稳妥的做法是显式传入name彻底消除对默认值的依赖。核心变更二类型系统重做推荐 createModel Models 接口文档原文只有一句“Changed typings to avoid future issues, on v1.x types doesnt work so the change should be easy”背后实际是一次类型架构的完整重构。对比 v1依赖全局类型推断可以看到 v2 的关键设计集中在 types.tsModelsTModels自引用接口types.ts#L82-L84模型集合以interface RootModel extends ModelsRootModel的形式显式声明root state 由ExtractRematchStateFromModels从各模型的state字段推导不再依赖魔法推断createModel工厂index.ts#L18createModelRootModel()({...})让每个模型的state、reducers、effects、baseReducer都获得独立推断dispatcher 精确类型RematchDispatcher、ExtractRematchDispatchersFromReducers、ExtractRematchDispatchersFromEffects会根据 reducer/effect 参数是否可选、是否带meta生成对应的函数签名并额外挂载isEffect属性区分两类 dispatcher。v2 下推荐的完整写法如下与 store.test.ts 的测试一致import { createModel, init, Models } from rematch/core const count createModelRootModel()({ state: 0, reducers: { addOne(state: number): number { return state 1 }, }, }) interface RootModel extends ModelsRootModel { count: typeof count } const store initRootModel({ models: { count } }) store.dispatch.count.addOne()这种写法的类型收益有专门测试佐证dispatcher-typings.test.ts 覆盖了必填 payload、联合类型、可选 payload、any、payload/meta 组合、无参 reducer 等场景错误调用均通过ts-expect-error强制在编译期报错。例如 effect 侧dispatch.count.incrementEffect(test)在 payload 为number时会被编译器拒绝见 dispatcher-typings.test.ts#L241-L269。迁移建议v1 中依赖隐式全局类型的代码升级到 v2 后统一改为interface extends Models...createModel的模式。由于 v1 的类型本身就经常不准这一步通常是“把坏类型换成好类型”而非修复既有正确代码——这正是文档说“the change should be easy”的原因。核心变更三Effects 的 dispatch 参数不能解构文档原文Effects dispatch param cant be destructured in function TypeScript (typescript design limitation).即 v1 中这种函数式 effects 的解构写法// v1 风格v2 的 TS 类型下不再受支持 effects: ({ count, settings }) ({ load: () count.fetch(), })在 v2 中必须改为通过闭包参数接收dispatcheffects: (dispatch) ({ load: () dispatch.count.fetch(), })源码层面当effects是函数时它接收的就是 Rematch 的 dispatch 对象本身见 createEffectDispatcher// effects might be actually a function creating effects if (model.effects) { effects typeof model.effects function ? (model.effects as ModelEffectsCreatorTModels)(rematch.dispatch) : model.effects }而效果函数内部调用其他模型 action 时走的是this绑定——每个 effect 被bind到该模型的 dispatcher 上dispatcher.ts#L105-L106bag.effects[${model.name}/${effectName}] effects[effectName].bind(modelDispatcher)类型定义 ModelEffect 与 ModelEffectsCreator 也印证了这一点effect 的第一个参数是payload跨模型调用通过this: ModelEffectThisTyped即this.count.fetch()或闭包中的dispatch完成而非解构函数参数。TypeScript 无法为“运行时才存在的动态模型集合”在解构位置生成可靠类型这是文档所称的 typescript design limitation 的根源。另外注意一个顺序细节effects 的生成依赖prepareModel先把dispatch[modelName]挂到 store 上rematchStore.ts#L62-L63因此源码刻意分两步遍历模型——先 prepare 再 enhance——以保证循环引用的模型在 effects 内部也能正确访问。这与 2.2.0 修复的“circular reference destructuring works with all models”见 packages/core/CHANGELOG.md相呼应回归测试位于 v1_regressions/circurlarmodels.test.ts。插件变更一onInit 钩子被移除v2 的插件钩子集合被收敛为五件套定义见 PluginHooksexport interface PluginHooksTModels, TExtraModels { onStoreCreated?: StoreCreatedHookTModels, TExtraModels onModel?: ModelHookTModels, TExtraModels onReducer?: ReducerHookTModels, TExtraModels onRootReducer?: RootReducerHookTModels, TExtraModels createMiddleware?: MiddlewareCreatorTModels, TExtraModels }onInit不在其中validatePlugin 也只校验这五个钩子是否为函数。v1 中依赖onInit做初始化逻辑的插件可以推断应改写为其他钩子store 层面的初始化放onStoreCreated针对单个模型的处理放onModel需要拦截 reducer 的用onReducer/onRootReducer需要插入 Redux 中间链的用createMiddleware。钩子的实际调用点在 rematchStore.ts插件中间件在 store 创建前收集onStoreCreated在 store 创建后执行rematchStore.ts#L65-L67onModel在 enhanceModel 中针对每个模型触发。插件变更二插件配置不能再嵌套其他插件v2 中插件的配置类型被限定为PluginConfigtypes.ts#L132-L139export interface PluginConfigTModels, TExtraModels, TExposedModels PartialTExtraModels { models?: TExposedModels redux?: InitConfigRedux }即插件只能通过config.models注入自己的模型、通过config.redux贡献 initialState / reducers / enhancers / middlewares / combineReducers / createStore不能再在插件配置里塞plugins数组。在 createConfig 中可以看到合并逻辑只处理models与redux两类字段config.plugins.forEach((plugin) { if (plugin.config) { // Collect new models config.models merge(config.models, plugin.config.models) // Collect redux configuration changes if (plugin.config.redux) { config.redux.initialState merge( config.redux.initialState, plugin.config.redux.initialState ) config.redux.reducers merge( config.redux.reducers, plugin.config.redux.reducers ) // enhancers / middlewares 数组拼接、combineReducers / createStore 优先取用户配置 // ... } } validatePlugin(plugin) })值得留意 merge 的语义它是浅合并且原对象优先{ ...extra, ...original }。也就是说应用自身在init()中声明的 models/reducers/initialState 会覆盖同名插件注入项插件处于“补充”地位。迁移时若 v1 插件曾靠嵌套插件扩展功能需要把该能力拆出来改为在init({ plugins: [...] })中显式并列声明各插件。此外v2 插件类型引入了TExtraModels/TExposedModels泛型Plugin 定义插件可以向 root state 注入额外模型如 loading、updated 这类插件的计时模型并通过exposed把方法挂到 store 上挂载逻辑见 addExposed。这就是文档所说“Changed typings to avoid future issues”在插件侧的具体含义——插件的类型从“与主模型混在一起”变为“通过泛型显式声明扩展模型”。插件变更三Persist 插件对齐 redux-persist文档提示“Persist plugin is updated to match redux-persist, so probably youll find some errors”。v2 的 persist 插件源码 直接复用 redux-persist 的persistReducer/persistStore插件签名变为四参数工厂persistPlugin( persistConfig, // PersistConfig传给 redux-persist persistReducer 的配置 nestedPersistConfig {}, // { [modelName]: PersistConfig }逐模型的 Nested Persist persistStoreConfig?, // PersistorOptions传给 persistStore callback? // () voidrehydration 完成后回调 )内部实现packages/persist/src/index.ts#L39-L54恰好是前面所述新钩子体系的标准示范onReducer对配置了nestedPersistConfig[modelName]的模型用persistReducer包裹该模型 reduceronRootReducer对根 reducer 应用主persistConfigonStoreCreatedstore 就绪后执行persistStore创建模块级persistor供getPersistor()取用配合 redux-persist 的PersistGate。标准用法来自 docs/plugins/persist.mdimport persistPlugin from rematch/persist; import { init } from rematch/core; import storage from redux-persist/lib/storage; const persistConfig { key: root, storage, }; init({ plugins: [persistPlugin(persistConfig)], });import { getPersistor } from rematch/persist; import { PersistGate } from redux-persist/lib/integration/react; const persistor getPersistor(); const Root () ( PersistGate persistor{persistor} divapp/div /PersistGate );版本对应关系务必遵守见 docs/plugins/persist.md 的 Compatibility 表rematch/corerematch/persist1.x.x1.x.x2.x.x2.x.x也就是说从 v1 升级时rematch/core与各rematch/*插件包必须一起跨入 2.x不能只升核心而留插件在 1.x——插件类型与钩子契约Plugin、PluginHooks已在 types.ts 中统一重定义旧版插件的onInit等钩子在 v2 下不会被调用。完整插件列表见 docs/plugins/index.mdimmer、select、persist、loading、updated插件 API 参考见 docs/api-reference/plugins.md。迁移检查清单按此清单过一遍即可覆盖 v2 全部破坏性变更全局搜索 store 名称断言测试中凡断言store.name或 DevTools 实例名是纯数字的地方改为Rematch Store N或在init()中显式传name消除不确定性实现依据packages/core/src/config.ts#L17。重构 TypeScript 类型为模型集合建立interface RootModel extends ModelsRootModel模型统一用createModelRootModel()({...})创建initRootModel({ models })显式标注泛型删除 v1 中不生效的全局类型声明。改写 effects 的 dispatch 解构effects: ({ count }) ({...})一律改为effects: (dispatch) ({...})并在内部使用dispatch.xxx.yyy()或this.xxx.yyy()。升级自研/社区插件移除onInit逻辑并迁移到onStoreCreated/onModel/createMiddleware等现有钩子把插件配置中嵌套的plugins拆到init()顶层声明。Persist 插件核对确认rematch/persist升到 2.x检查persistConfig是否符合 redux-persist 的PersistConfigkey、storage等getPersistor()用法不变。运行验证仓库内置了 v1 回归测试packages/core/test/v1_regressions与完整的 dispatcher 类型测试packages/core/test/ts_typings可作为自检用例参考验证你的模型与类型迁移是否完整。小结Rematch v1 到 v2 的破坏性变更集中在三处更可读的默认 store 命名、一次彻底的 TypeScript 类型重构Models接口 createModel 精确 dispatcher 类型、以及插件钩子体系收敛移除onInit、插件配置只允许贡献models与redux字段、persist 插件对齐 redux-persist 四参数工厂。这些变更的共同目标是用显式类型与清晰钩子契约换取长期的可维护性类型从“不工作”变为“严格推断”插件从“自由嵌套”变为“受控贡献”。对照本文检查清单逐项处理后升级本身是机械且低风险的。赞分享前端【免费下载链接】rematchThe Redux Framework项目地址https://gitcode.com/gh_mirrors/re/rematch点击查看免费下载相关推荐Recompose核心功能详解支持哪些XML布局与Compose组件转换Recompose核心功能详解支持哪些XML布局与Compose组件转换 Recompose是一款强大的Android开发工具专门用于将传统的XML布局文开发工具代码生成移动开发Actix Web 4.0 升级迁移完全指南从 v3 到 v4 的 Breaking Changes 逐项解析与实战迁移Actix Web 4.0 升级迁移完全指南从 v3 到 v4 的 Breaking Changes 逐项解析与实战迁移 导读 本文以 actix web/M后端Web框架Hamburgers版本迁移指南从v0.x到v1.x breaking changesHamburgers版本迁移指南从v0.x到v1.x breaking changes 你还在为Hamburgers从v0.x升级到v1.x版本时遇到的兼容性前端UI组件上一篇TUnit测试清理机制智能资源释放的实现方式下一篇ricq API完全指南高效调用私聊/群聊功能的10个技巧创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考