Better

Ethan的博客,欢迎访问交流

DvaJS 与 UmiJS

在 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 一致


留言