
Umi 数据流插件怎么定义 Model 并用 useModel 在组件间共享状态【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi这篇文章解决一个问题在 Umi 项目里如何让多个组件拿到同一份状态比如用户信息、一个计数器而不必层层传props。做法是使用 Umi 的数据流管理插件把状态写进一个默认导出函数的 Model 文件插件会把它变成全局数据组件中调用useModel(命名空间)即可读取。Model 是普通的自定义 hook没有任何黑魔法。数据流管理插件内置于umi/max如果用的是普通 Umi 项目需要先安装并开启插件见下一节。准备开启数据流插件umi/max已内置数据流model、initial-state等插件创建 Max 项目后可以直接使用配置和用法见 Umi Max 简介。普通 Umi 应用默认不附带任何插件需要手动安装并开启pnpm add -D umijs/plugins然后在项目配置文件如.umirc.ts中加载插件。按插件文档给出的写法开启 antd 插件是这样// .umirc.ts export default { plugins: [umijs/plugins/dist/antd], antd: {}, };数据流插件同理它也在umijs/plugins/dist/*中加载且通过配置开启数据流对应initial-state、model两个插件。如果项目里需要定义初始化数据getInitialState、useModel属于数据流相关功能运行时配置文档也指向了同一套方案。数据流插件本身支持两个配置项完整说明见数据流文档export default { model: { extraModels: [src/models/userModel.ts], sort: (a, b) a.namespace.localeCompare(b.namespace), }, };extraModelsType:string[]Default:[]插件会自动将这些 Model 文件添加到数据流管理中适合不在约定目录里的文件。sortType:(a: Model, b: Model) number按返回值对 Model 排序。定义 Model目录约定与命名空间插件采用约定式目录结构以下位置的文件会被识别为 Modelsrc/models/目录src/pages/xxxx/models/目录src/pages/xxxx/model.{js,jsx,ts,tsx}文件Model 文件允许.(tsx|ts|jsx|js)四种后缀。命名空间namespace生成规则如下引自官方文档路径命名空间说明src/models/count.tscountsrc/models目录下不支持目录嵌套定义 modelsrc/pages/pageA/model.tspageA.modelsrc/pages/pageB/models/product.tspageB.productsrc/pages/pageB/models/fruit/apple.tspageB.fruit.applepages/xxx/models下 model 定义支持嵌套定义关键约束Model 文件必须默认导出一个函数这个函数就是一个 hook。不符合该规范的文件会被过滤掉无法通过命名空间调用。最简单的 Model只返回一份静态数据// src/models/userModel.ts export default () { const user { username: umi, }; return { user }; };插件所做的工作就是把这个函数里的状态或数据变成全局数据——不同组件使用同一个 Model 时拿到的是同一份状态。Model 中允许使用其它 hooks官方给出的计数器示例// src/models/counterModel.ts import { useState, useCallback } from react; export default function Page() { const [counter, setCounter] useState(0); const increment useCallback(() setCounter((c) c 1), []); const decrement useCallback(() setCounter((c) c - 1), []); return { counter, increment, decrement }; };实际项目中通常需要请求后端接口获取数据官方在userModel上的扩展写法// src/models/userModel.ts import { useState, useEffect } from react; import { getUser } from /services/user; export default function Page() { const [user, setUser] useState({}); const [loading, setLoading] useState(true); useEffect(() { getUser().then((res) { setUser(res); setLoading(false); }); }, []); return { user, loading, }; };如果项目里已经在用 ahooks文档给出的等价组织方式是换成useRequest// src/models/userModel.ts import { useRequest } from ahooks; import { getUser } from /services/user; export default function Page() { const { data: user, loading: loading } useRequest(async () { const res await getUser(); if (res) { return res; } return {}; }); return { user, loading, }; };用 useModel 在组件中读取共享状态useModel从umi包导入第一个参数是 Model 的命名空间// src/components/Username/index.tsx import { useModel } from umi; export default function Page() { const { user, loading } useModel(userModel); return ( {loading ? /: div{user.username}/div} ); }useModel()接受两个参数引自官方 API 说明参数类型介绍namespaceStringModel 文件的命名空间updater(model: any) any可选。传入一个函数函数的返回值为当前组件中需要使用到的 Model 状态或数据并作为useModel()方法的返回值仓库中的示例工程 examples/with-use-model 演示了完整的共享链路可以对照阅读examples/with-use-model/models/todo.ts默认导出一个基于useState的 hook返回{ todos, setTodos }examples/with-use-model/models/count.tsModel 内部也可以再调用useModel(todo)和useModel(initialState)返回{ total: 123 }examples/with-use-model/pages/index.tsx页面组件分别useModel(todo)和useModel(count)把todos列表和total渲染出来。该工程package.json中声明了umijs/plugins与umi两个依赖提供umi dev/umi build脚本。在该目录下运行umi dev启动后页面会展示todos列表foo、bar和count的total值123——这就是多个组件读取同一份 Model 数据的运行结果以上取值来自示例工程源码是文档示例。判断共享是否生效的直接依据同一个命名空间下不同组件甚至 Model 之间useModel拿到的是同一份状态如果某个命名空间调不到先检查该文件是否在约定目录中、是否默认导出了函数——不符合规范的文件会被过滤。用第二个参数过滤减少不必要的重渲染当组件只需要 Model 中的部分字段、对其它字段的变化不感兴趣时给useModel()传入第二个参数updater函数返回值就是组件实际拿到的部分。以计数器为例按钮组件只关心increment/decrement不关心频繁变化的counter// src/components/CounterActions/index.tsx import { useModel } from umi; export default function Page() { const { add, minus } useModel(counterModel, (model) ({ add: model.increment, minus: model.decrement, })); return ( div button onClick{add}add by 1/button button onClick{minus}minus by 1/button /div ); };这样过滤掉了counter这一频繁变化的值避免了组件重复渲染带来的性能损失。文档把这个用法放在性能优化一节并强调updater对优化组件性能具有重要意义。可选共享全局初始状态initialStateumi/max还内置了全局初始状态管理插件全局初始状态是一种特殊的 Model在整个项目最开始创建在src/app.ts中导出getInitialState()其返回值即为全局初始状态// src/app.ts import { fetchInitialData } from /services/initial; export async function getInitialState() { const initialData await fetchInitialData(); return initialData; }之后任何组件都可以通过useModel(initialState)获取import { useModel } from umi; export default function Page() { const { initialState, loading, error, refresh, setInitialState } useModel(initialState); return {initialState}/; };返回对象的字段说明对象属性类型介绍initialStateany导出的getInitialState()方法的返回值loadingbooleangetInitialState()或refresh()方法是否正在进行中。在首次获取到初始状态前页面其他部分的渲染都会被阻止errorError如果导出的getInitialState()方法运行时报错报错的错误信息refresh() void重新执行getInitialState方法并获取新的全局初始状态setInitialState(state: any) void手动设置initialState的值手动设置完毕会将loading置为false仓库示例工程中的 examples/with-use-model/app.ts 就是一个带 500ms 延时的getInitialState配合上文的models/count.ts一起演示了initialState的读取。限制与边界src/models目录下不支持目录嵌套定义 model需要嵌套时放到src/pages/xxx/models/下或用extraModels显式声明文件路径。Model 文件不默认导出函数时会被插件过滤无法通过命名空间调用。若组件只订阅部分字段记得用updater过滤否则 Model 中任一字段变化都会触发该组件重新计算。umi/max项目开箱即用普通 Umi 项目必须安装umijs/plugins并在配置中开启model插件useModel才可用。完整的配置项、API 与更多示例以数据流官方文档为准。【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考