在 ant-design-pro 中使用的是 DvaJS 和 UmiJS,这里总结下基本使用以及碰到的一些小坑
概括
两者解决的问题不一样,就像是两板斧,一个砍向数据流,一个砍向路由模块。
DvaJS 是一个基于 redux 和 redux-saga 的数据流方案。
UmiJS 以路由为基础,以约定大于配置的原则进行功能扩展。
关于 Dva
dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions。
定义好 model 后,需要使用 app.model 载入它
State、Action、dispatch、Reducer 概念和 redux 是一样的,就不废话了,新增了两个概念
- Effect:异步操作
- Subscription:从
源
获取数据的方法 - namespace:当前 Model 的名称
Effect 用于定义一系列 generator 函数用于处理异步操作,第一个参数是 action,第二个参数是一系列操作函数
- call 函数用于执行异步
- put 发出一个 Action,类似与 dispatch
- select 用于获取其他 model 数据
- all 类似于 Promise.all
- race 类似于 Promise.race
- take takeLatest ……
总的来说:dva = React-Router + Redux + Redux-saga
需要知道的点
- 组件内 dispatch 方法:被 connect 的 Component 会自动在 props 中拥有 dispatch 方法
- 很多时候我们需要根据路由做组件的动态加载,对于 model 也可以做懒加载
- 动态扩展 model:注意到 dva 中的每个 model,实际上都是普通的 JavaScript 对象,可以借助
dva-model-extend
库来做这件事 - 跨 model 的通信:在 dva 中,可以用 namespace 去指定接受 action 的 model
关于 umi
一句话介绍:以路由为基础,提供各种进阶的路由功能,并以此进行功能扩展,比如路由级按需加载,配以完善的插件体系,覆盖从源码到构建产物的每个生命周期。
可以简单理解为 roadhog + 路由。roadhog 是基于 webpack 的封装工具,目的是简化 webpack 的配置。
脚手架:通过 create-umi
提供脚手架能力
- project:项目脚手架,可以选择是否启用 TS,以及一些基础设施的能力
- ant-design-pro:仅包含 ant-design-pro 布局的脚手架,具体页面可通过 umi block 添加
- block:区块脚手架
- plugin:插件脚手架
- library:依赖库脚手架
目录及约定
- 配置文件、mock 文件等都有通过
@babel/register
注册实时编译,所以可以和 src 里的文件一样,使用 ES6 的语法和 es modules - 约定 src 为源码目录,如果不存在 src 目录,则当前目录会被作为源码目录
- pages 下会有一个
.umi
目录,可以在这里做一些验证,但请不要直接在这里修改代码,umi 重启或者 pages 下的文件修改都会重新生成这个文件夹下的文件。 - src/global.(js|ts):此文件会在入口文件的最前面被自动引入,可以在这里加载补丁,做一些初始化的操作等
- src/global.(css|less|sass|scss):此文件不走 css modules,且会自动被引入,可以在这里写全局样式,以及做样式覆盖
路由
- 路由分为约定式路由和配置式路由
- 约定式路由:umi 会根据 pages 目录自动生成路由配置
- 可以配置 .umirc.(ts|js) 或者 config/config.(ts|js) 配置文件中的 routes 属性,此配置项存在时则不会对 src/pages 目录做约定式的解析
- 权限路由:umi 的权限路由是通过配置路由的 Routes 属性来实现。约定式的通过 yaml 注释添加,配置式的直接配上即可
- 路由动效:考虑使用
react-transition-group
- 面包屑:
react-router-breadcrumbs-hoc
页面跳转
- 声明式:基于
umi/link
下 Link 组件 - 命令式:基于
umi/router
,通过调用 API
Mock
- 约定 mock 文件夹下的文件或者 page(s) 文件夹下的 _mock 文件即 mock 文件
- Mock.js 是常用的辅助生成模拟数据的第三方
- 模拟延迟 roadhog-api-doc/delay
- 如果你需要动态生成 Mock 数据,你应该通过函数进行处理
按需加载
- 按需加载组件:使用
umi/dynamic
,我觉得动态 import 应该也可以 - 按需加载模块:使用动态 import
import('g2').then(() => { // do something with g2 });
运行时配置
- umi 约定 src 目录下的 app.js 为运行时的配置文件
- patchRoutes:运行时修改路由,比如请求服务端根据响应动态更新路由
- render:用于改写把整个应用 render 到 dom 树里的方法,比如用于渲染之前做权限校验
- onRouteChange:用于在初始加载和路由切换时做一些事情,比如埋点统计
- rootContainer:用于封装 root container,可以取一部分,或者外面套一层
- modifyRouteProps:修改传给路由组件的 props
部署
- 不输出 html 文件
- 部署 html 到非根目录
- 使用 hashHistory
- 按需加载:配置 dynamicImport
- 静态资源在非根目录或 cdn:配置 publicPath
- 使用 runtime 的 publicPath
- 静态化:在一些场景中,无法做服务端的 html fallback,即让每个路由都输出 index.html 的内容,那么就要做静态化 ……
编译提速
这个问题也是困扰我很久的问题,项目的 build 速度总是很慢,可尝试的特性(updated at 2020.09.01)
- 配置 externals
- 减少补丁尺寸
- 调整 splitChunks 策略,减少整体尺寸
- 调整 SourceMap 生成方式
- 替换压缩器为 esbuild - 实验性特性
实践中的坑
实践注意点
- model 中 effect 和 reducer 不能同名,否则会出现重复调用问题
- 如果在某个 model 里的 effect 内发生了未补获的错误,会导致这个 model 整个都 dispatch 不了了
- model 的文件名必须与所指定的 namespace 一致