- 前端
【免费下载链接】rematch
The Redux Framework
本文基于 Rematch 仓库的介绍文档(docs/introduction.md)展开:Rematch 定位为“不带样板代码的 Redux 最佳实践”,即无需再手写 action types、action creators、switch 语句和 thunks。读完本文,你将理解 Rematch 的完整能力清单、从零初始化 store 的四步流程,并能对照 packages/core 的源码确认每一项特性背后的真实实现,从而在 React、React Native 等场景中以极小的依赖体积接入 Redux 状态管理。
一、Rematch 是什么
Redux 是一个强大的状态管理工具,拥有健康的中间件生态和出色的 devtools。Rematch 建立在 Redux 之上,通过减少样板代码并强制推行最佳实践来解决 Redux 的三大痛点:
- 不再需要定义 action types 常量字符串;
- 不再需要编写 action creators 函数;
- 不再需要 reducer 中的
switch分支,也不再需要为异步逻辑引入 thunks。
README 中的对比表概括了两者的关系(摘自 README.md):
| 能力 | Redux | Rematch |
|---|---|---|
| 简单搭建 | 支持 | |
| 更少的样板代码 | 支持 | |
| 可读性 | 支持 | |
| 可配置 | 支持 | 支持 |
| redux devtools | 支持 | 支持 |
| 自动生成的 action creators | 支持 | |
| 异步处理 | thunks | async/await |
需要说明的是,Rematch 并没有替代 Redux:从 packages/core/package.json 可见,@rematch/core声明了peerDependencies: { "redux": ">=4" },当前版本为 2.2.0,它构建的是一个真实 Redux store,只是把配置和调用方式包装得更为简洁。
二、官方特性清单与源码印证
介绍文档列出了 Rematch 的完整特性,下面逐项结合仓库源码给出实现层面的印证。
2.1 体积小于 2kb,且无需配置
介绍文档宣称核心小于 2kb(README 进一步标注为 less than 1.4 kilobytes)。源码规模也确实很小:packages/core/src/index.ts 整个入口只导出init、createModel与全部类型定义。
“无配置”体现为 packages/core/src/config.ts 中的createConfig:用户不传任何参数也能构建完整配置——models默认为{}、plugins默认为[],Redux 侧的reducers、rootReducers、enhancers、middlewares全部有默认值,devtoolOptions.name默认取 store 名称。
2.2 减少 Redux 样板代码
Model 把 state、reducers、effects 聚合在一处。从 packages/core/src/reduxStore.ts 的createModelReducer可以看到样板代码是如何被消解的:
- 每个 model 的 reducer 键自动组合为
modelName/reducerKey形式的 action name(如count/increment),完全取代手写 action type 常量; - 生成一个
combinedReducer,按action.type分发到对应 reducer 并传入state、action.payload、action.meta,取代switch语句; - 若 reducer 键本身包含
/(如监听其他 model 的 action),则通过isAlreadyActionName判定后直接作为 action name 使用。
2.3 内置副作用(effects)支持
effects 让异步逻辑用原生 async/await 表达。实现分两半:
- dispatcher 侧:packages/core/src/dispatcher.ts 的
createEffectDispatcher会把effects: (dispatch) => ({...})形式的 effects 展开,将每个 effect 以modelDispatcher为this绑定后注册进bag.effects['modelName/effectName'],并在dispatch[modelName]上挂上带isEffect: true标记的 action 生成器; - middleware 侧:packages/core/src/rematchStore.ts 中的
createEffectsMiddleware拦截 action——当action.type in bag.effects时,先调用next(action)执行 reducer(如果存在同名的 reducer),再调用对应 effect 函数并传入action.payload、store.getState()、action.meta,返回其结果。
这正是“thunks 被 async/await 取代”的底层机制:effect 的返回值可以是一个 Promise,调用方可以await。
2.4 自动生成的 dispatch 与 React Devtools 支持
dispatch[model]action的便捷语法由createActionDispatcher生成:构造{ type: 'modelName/actionName', payload?, meta? }后转发给 Redux 的dispatch。
Devtools 支持在 packages/core/src/reduxStore.ts 的composeEnhancersWithDevtools中实现:只要未通过devtoolOptions.disabled关闭且window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__存在,就用扩展提供的 compose 包装增强器,devtoolOptions.name默认为 store 名称,因此多个 store 在 Devtools 中可区分。
2.5 TypeScript 支持
packages/core/src/types.ts 提供了完整类型体系:
Model/NamedModel/Models描述模型结构;RematchRootState<TModels>从所有 model 的state推导根 state 类型;RematchDispatch<TModels>在原生 ReduxDispatch 之上叠加dispatch[modelName]actionName的签名,并根据 reducer/effect 的参数推导payload、meta是否必填,effect dispatcher 还会携带isEffect标记与返回类型;createModel<RootModel>()({ ... })辅助函数在 packages/core/src/index.ts 中定义,用于在models/index.ts中导出RootModel接口后获得完整的类型推断。
2.6 动态添加 reducers
RematchStore接口在ReduxStore基础上额外暴露addModel方法。其实现位于 packages/core/src/rematchStore.ts 的rematchStore对象字面量中:先validateModel校验、createModelReducer注册 reducer、prepareModel与enhanceModel生成 dispatchers,最后调用reduxStore.replaceReducer(createRootReducer(bag))并派发@@redux/REPLACE触发 Devtools 重算,实现运行时动态挂载新 model。
2.7 支持热重载与多 store
- 热重载:model 以普通模块导出、store 通过
init集中创建,配合shouldHotReload等 devtool 选项与replaceReducer机制(见 2.6),HMR 场景下 reducer 可被整体替换而不丢失状态; - 多 store:packages/core/src/config.ts 维护了一个模块级计数器,未指定
name时自动命名为Rematch Store 0、Rematch Store 1……每次调用init都基于独立配置创建独立 Redux store,store.name会同时用于 Devtools 实例名,便于多 store 并存。
2.8 支持 React Native
@rematch/core对 Redux 的依赖是纯 JS 实现,Devtools 接入被typeof window === 'object'守卫保护(见 packages/core/src/reduxStore.ts),非浏览器环境自动回退到Redux.compose,因此可直接用于 React Native。
2.9 插件可扩展性与官方插件库
Rematch 及其内部全部构建在插件管道之上(见bag.forEachPlugin的调用点:createMiddleware、onModel、onStoreCreated、onReducer、onRootReducer)。官方插件一览(见 docs/plugins/index.md):
- Immer 插件:用 immer 包装 reducers,允许以可变写法产生不可变状态;
- Select 插件:为 models 提供 reselect 风格的 selectors;
- Persist 插件:redux-persist 封装,持久化数据;
- Loading 插件:为 effects 自动添加 loading 指示器;
- Updated 插件:记录 model、effect、reducer 最近触发时间;
- 另有 typed-state 插件 提供
useTypedState等类型化状态访问。
官方插件源码均在仓库内可直接阅读,例如 packages/loading/src/index.ts 展示了完整插件形态:通过config.models注入一个loadingmodel,通过onModel钩子包裹每个 effect 的 dispatcher,在 Promise 的 then/catch 中派发show/hide更新 loading 状态,并支持whitelist/blacklist过滤与boolean/number/full三种状态形态。
三、从零开始的四步上手流程
以下流程继承自 docs/installation.md,是最小可用的完整路径。
Step 0:安装
npm install @rematch/core注意redux是 peer 依赖(>=4),需要随项目一起安装;engines要求 Node>=10。
Step 1:定义 models
Model 回答三个问题:初始状态是什么(state)、如何同步改变状态(reducers)、如何处理异步(effects)。
export const count = { state: 0, // 初始状态 reducers: { // 纯函数处理状态变更 increment(state, payload) { return state + payload; }, }, effects: (dispatch) => ({ // 非纯函数处理状态变更,异步用 async/await async incrementAsync(payload, rootState) { await new Promise((resolve) => setTimeout(resolve, 1000)); dispatch.count.increment(payload); }, }), };TypeScript 版本使用createModel辅助函数,并在models/index.ts中声明RootModel以获得推断:
// ./models/count.ts import { createModel } from "@rematch/core"; import { RootModel } from "."; export const count = createModel<RootModel>()({ state: 0, reducers: { increment(state, payload: number) { return state + payload; }, }, effects: (dispatch) => ({ async incrementAsync(payload: number, state) { console.log("This is current root state", state); await new Promise((resolve) => setTimeout(resolve, 1000)); dispatch.count.increment(payload); }, }), });// ./models/index.ts import { Models } from "@rematch/core"; import { count } from "./count"; export interface RootModel extends Models<RootModel> { count: typeof count; } export const models: RootModel = { count };复杂 state 可用as断言给出完整类型,仓库中的完整可运行示例见 examples/count-react-ts/src/models/questions.ts 与 examples/all-plugins-react-ts/src/models,这些示例全部纳入仓库测试套件(examples/all-plugins-react-ts/src/index.test.tsx)。
Step 2:初始化 store
init是唯一必须调用的方法。最低限度只需提供models:
// store.js import { init } from "@rematch/core"; import * as models from "./models"; const store = init({ models }); export default store;// store.ts import { init, RematchDispatch, RematchRootState } from "@rematch/core"; import { models, RootModel } from "./models"; export const store = init({ models }); export type Store = typeof store; export type Dispatch = RematchDispatch<RootModel>; export type RootState = RematchRootState<RootModel>;Step 3:派发 action
dispatch既支持原生 Redux 的dispatch({ type, payload }),也支持dispatch[model]action简写,两者等价:
const { dispatch } = store; // state = { count: 0 } dispatch({ type: "count/increment", payload: 1 }); // state = { count: 1 } dispatch.count.increment(1); // state = { count: 2 } dispatch({ type: "count/incrementAsync", payload: 1 }); // 延迟后 state = { count: 3 } dispatch.count.incrementAsync(1); // 延迟后 state = { count: 4 }Step 4:接入视图层
Rematch 可与 react-redux 等原生 Redux 集成方式无缝配合:
// App.js import React from "react"; import ReactDOM from "react-dom"; import { Provider, connect } from "react-redux"; import store from "./store"; const Count = (props) => ( <div> The count is {props.count} <button onClick={props.increment}>increment</button> <button onClick={props.incrementAsync}>incrementAsync</button> </div> ); const mapState = (state) => ({ count: state.count }); const mapDispatch = (dispatch) => ({ increment: () => dispatch.count.increment(1), incrementAsync: () => dispatch.count.incrementAsync(1), }); const CountContainer = connect(mapState, mapDispatch)(Count); ReactDOM.render( <Provider store={store}> <CountContainer /> </Provider>, document.getElementById("root") );examples/count-react 是这套最小用法的完整可运行工程。
四、store 创建的内部调用链
理解init之后的内部流程,有助于在排查问题时定位环节。从 packages/core/src/index.ts 到 packages/core/src/rematchStore.ts,init的调用链为:
createConfig(config.ts):补齐默认值、校验配置,并遍历config.plugins,将每个插件config.models、config.redux中的改动合并进主配置(模型合并、initialState/reducers 浅合并、enhancers/middlewares 追加、combineReducers/createStore可被插件覆盖);createRematchBag(bag.ts):把models映射转成带name与默认空reducers的命名模型数组,并对每个 model 执行validateModel;- 组装 store(rematchStore.ts):
- 先向
bag.reduxConfig.middlewares压入 effects 中间件,再依次收集各插件的createMiddleware产物; createReduxStore(reduxStore.ts)为每个 model 生成 combined reducer、合并 root reducer(支持rootReducers前置处理)、组合 middlewares 与 devtools 增强器后创建真正的 Redux store;prepareModel先为每个 model 注入dispatch[modelName]并生成 reducer dispatchers,enhanceModel再生成 effect dispatchers 并触发插件的onModel钩子——两步分离是为了让循环引用模型(如示例 packages/core/test/v1_regressions/circurlarmodels.test.ts)在 effects 中解构时都能拿到彼此;- 最后执行插件的
onStoreCreated钩子,允许插件替换或扩展最终 store。
- 先向
相关行为均有测试覆盖,例如 packages/core/test/multiple.test.ts(多 store)、packages/core/test/plugins.test.ts(插件管道)、packages/core/test/effects.test.ts(effects 语义)。
五、插件 API:扩展点的完整清单
介绍文档强调 Rematch “Extendable with plugins”,其 API 详见 docs/api-reference/plugins.md。一个插件对象可包含:
config: { models, redux }:注入额外 model 或覆盖 Redux 配置(形状与init接受的配置一致);exposed:向 store 挂载额外属性,供插件间通信;在onModel与onStoreCreated之前执行;createMiddleware(bag):创建可访问 Rematch 内部 “bag” 的自定义中间件;onReducer(reducer, modelName, bag):model 的 base reducer 创建时执行,可返回新 reducer 覆盖;onRootReducer(reducer, bag):root reducer 创建时执行,可返回新 root reducer 覆盖;onModel(namedModel, rematchStore):每个 model 的 reducers 与 dispatchers 就绪后执行,动态addModel时也会再次触发;onStoreCreated(rematchStore, bag):store 就绪后的最后一个钩子,可返回新 store 覆盖。
完整形态示例(摘自官方插件 API 文档):
const plugin = { config: { redux: { combineReducers: customCombineReducers, }, models: { extra: extraModel, }, }, exposed: { select: {} }, createMiddleware: (rematchBag) => (store) => (next) => (action) => { // do something here return next(action); }, onReducer(reducer, modelName, bag) { // do something }, onRootReducer(reducer, bag) { // do something }, onModel(namedModel, rematchStore) { // do something }, onStoreCreated(rematchStore, bag) { // do something }, };这些钩子在核心中的触发点均可在 packages/core/src/rematchStore.ts、packages/core/src/reduxStore.ts 与 packages/core/src/bag.ts 中逐一对应找到(forEachPlugin('onReducer' | 'onRootReducer' | 'onModel' | 'onStoreCreated' | 'createMiddleware'))。
六、进阶路径与资源索引
介绍文档为两类读者给出了分岔口:
- 来自现有 Redux 代码库:迁移往往只涉及状态管理层的小改动,视图逻辑基本不动,详见 docs/migrating/from-redux.md;
- 从零开始:先读安装指南 docs/installation.md,TypeScript 用户可直接跳转 docs/typescript.md 了解 utility types;v1 老用户可参考 docs/migrating/from-v1-to-v2.md。
仓库内值得深入的路径:
| 路径 | 内容 |
|---|---|
| packages/core/src | 核心库全部源码(bag、config、dispatcher、reduxStore、rematchStore、types、validate) |
| packages/core/test | 核心行为测试(effects、plugins、multiple、init、listener 等) |
| examples | 10 个可运行示例:count-react、count-react-ts、all-plugins-react-ts、multi-react(多 store)、nextjs-blog、gatsby-example 等 |
| docs/api-reference | init配置参数、models、plugins、redux 配置、store 的完整 API 参考 |
| docs/recipes | Redux DevTools、Redux 插件、测试等实战配方 |
七、许可与支持
- 项目采用 MIT 许可(LICENSE);
- 问题反馈、功能请求与疑问可提交 issue(对应 CONTRIBUTING.md 中的社区规范);
- 版本适用前提:本文所述 API 对应仓库中
@rematch/core2.2.0(packages/core/package.json),要求redux >= 4、Node>= 10,文档与示例均以当前仓库内容为准。
- 前端
【免费下载链接】rematch
The Redux Framework
相关推荐
Rematch:用模型驱动的 Redux 框架消除样板代码,从 Model 到 Store 的完整剖析
Rematch:用模型驱动的 Redux 框架消除样板代码,从 Model 到 Store 的完整剖析 本文以 Rematch 仓库根目录 README htt
前端Rematch 安装与快速上手:用 @rematch/core 四步搭建 Redux Store
Rematch 安装与快速上手:用 @rematch/core 四步搭建 Redux Store 本篇基于 Rematch 官方文档的 Installation
前端Rematch Store API 详解:在 Redux Store 之上构建对象化 dispatch 与动态 addModel
Rematch Store API 详解:在 Redux Store 之上构建对象化 dispatch 与动态 addModel Rematch 通过 init
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考