Umi和Dva框架指南


使用蚂蚁金服的Antd框架有一年多了,现在感觉越用越顺手。但Antd只是个UI框架,这些强大功能的背后是UmiDva的功劳,路由和Model经过简单的配置就可以使用,在一个组件中还可以实现多个Model的注入。这种丝滑程度,让我不禁想起了SpringBoot。Umi+Dva的确实是一套成熟的前端解决方案,它融合了很多技术和理念,对于刚使用的人来说,学习成本还是挺高的,不像Vue那样容易上手。

简单来说

umi :可以理解为 roadhog + 路由,并辅以一套插件机制,目的是通过框架的方式简化 React 开发

dva :可以理解为 redux + redux-saga,数据流解决方案,和 umi 没有依赖关系,可以独立使用

roadhog 是基于 webpack 的封装工具,目的是简化 webpack 的配置,是一个包含 devbuildtest 的命令行工具

Umi介绍

Umi是蚂蚁金服的底层前端框架,特别是从3.0版本开始,它们的slogan就改成了插件化的企业级前端应用框架,插件这个概念让整个umi框架的扩展性大大提高,插件让框架支持各种功能扩展和业务需求。可以说插件就是umi的灵魂,官网文档描述Umi的6大特性:

  • 🎉 可扩展,Umi 实现了完整的生命周期,并使其插件化,Umi 内部功能也全由插件完成。此外还支持插件和插件集,以满足功能和垂直域的分层需求。
  • 📦 开箱即用,Umi 内置了路由、构建、部署、测试等,仅需一个依赖即可上手开发。并且还提供针对 React 的集成插件集,内涵丰富的功能,可满足日常 80% 的开发需求。
  • 🐠 企业级,经蚂蚁内部 3000+ 项目以及阿里、优酷、网易、飞猪、口碑等公司项目的验证,值得信赖。
  • 🚀 大量自研,包含微前端、组件打包、文档工具、请求库、hooks 库、数据流等,满足日常项目的周边需求。
  • 🌴 完备路由,同时支持配置式路由和约定式路由,同时保持功能的完备性,比如动态路由、嵌套路由、权限路由等等。
  • 🚄 面向未来,在满足需求的同时,我们也不会停止对新技术的探索。比如 dll 提速、modern mode、webpack@5、自动化 external、bundler less 等等

约定大于配置

约定大于配置的思想,简化了很多搭建项目时的工作,在使用umi框架的时候,它很多东西都是约定式的。所谓约定式就是指,按照约定好的方式开发,就能达到某种效果,中间的过程由框架帮我们完成,一般的我们通过umi创建的工程,目录结构如下

.
├── dist/                          // 默认的 build 输出目录
├── mock/                          // mock 文件所在目录,基于 express
├── config/
    ├── config.js                  // umi 配置,同 .umirc.js,二选一
└── src/                           // 源码目录,可选
    ├── layouts/index.js           // 全局布局
    ├── locales                    // 国际化
    ├── models                     // model层
    ├── services                   // service层
    ├── pages/                     // 页面目录,里面的文件即路由
        ├── .umi/                  // dev 临时目录,需添加到 .gitignore
        ├── .umi-production/       // build 临时目录,会自动删除
        ├── document.ejs           // HTML 模板
        ├── 404.js                 // 404 页面
        ├── page1.js               // 页面 1,任意命名,导出 react 组件
        ├── page1.test.js          // 用例文件,umi test 会匹配所有 .test.js 和 .e2e.js 结尾的文件
        └── page2.js               // 页面 2,任意命名
    ├── global.css                 // 约定的全局样式文件,自动引入,也可以用 global.less
    ├── global.js                  // 可以在这里加入 polyfill
    ├── app.js                     // 运行时配置文件
├── .umirc.js                      // umi 配置,同 config/config.js,二选一
├── .env                           // 环境变量
└── package.json

在Umi的约定下

  • 建一个 locales 目录,就拥有了国际化
  • 建一个 models 目录,就拥有了数据流
  • 建一个 mock 目录,就拥有了数据 mock

这看起来是非常黑盒非常酷的,用这种方式其实对于团队代码风格的统一是非常有好处的,直接在框架层面就约束了大家的目录组织模式,便于团队维护。

但是缺点也是挺明显的,灵活性不如配置式的高,因为只能按特定的模式来开发,如果原本约定的方式不满足业务需求,就需要额外开发umi插件来魔改原本的功能。而且约定式的开发是相对其他框架来说很特别的一点,对新上手的同学来说需要时间去通读官网文档了解约定式的规则。总的来说有舍有得,我还是倾向于约定的方式,整个项目结构简单清晰

umi 缺点就是无法暴露原始的webpack.config.json 文件,只能按照官方文档上特定的方法进行修改,而文档却并不完善,项目进行顺利时大家笑嘻嘻,但如果碰到棘手的问题,就不得不去深入了解umi这个黑盒的原理。启动 umi dev 后,大家会发现 pages 下多了个 .umi 的目录。这是啥?这是 umi 的临时目录,可以在这里做一些验证,但请不要直接在这里修改代码,umi 重启或者 pages 下的文件修改都会重新生成这个文件夹下的文件

插件体系

umi 区别于其他前端开发框架和工具的核心就是它的插件机制,基于 umi 的插件机制,你可以获得扩展项目的编译时和运行时的能力。通过插件支持的功能也会变得更强大,我们针对功能的需要可以去使用修改代码打包配置,修改启动代码,约定目录结构,修改 HTML 等更丰富接口。

可以说插件体系是 Umi 最重要的基建,因为包括 Umi内部实现也是全部由插件构成,Umi通过插件体系实现了技术收敛,把大家常用的技术栈进行整理,让大家只用 Umi 就可以完成 80% 的日常工作。3.x版本重构了整个插件体系。从图看出 presets插件集 和 plugins插件 垂直分层,插件组合成不同的插件集来处理不同的业务场景。而umi本身已经内置了一个针对react应用的插件集(@umijs/preset-react),正是这个插件集中的插件引入了react项目中常用的一些功能如antd,数据流,国际化等。

umi的配置

根目录下的 .umirc.js 是整个应用的配置文件,上面提到的插件也可以在这里配置。配置项可以写在 .umirc.js 文件中,也可以写在 config/config.js 文件中,二者只有一个生效 。我个人倾向于用config.js,关于更详细配置,看官网

Dva介绍

一个轻量级的前端数据流应用框架。通过 reducers, effects 和 subscriptions 组织 model,简化 redux 和 redux-saga 引入的概念

React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。如果开发大应用,还需要解决一个问题。

  • 通信:组件之间如何通信?
  • 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?

目前流行的数据流方案有:

  • Flux,单向数据流方案,以 Redux 为代表
  • Reactive,响应式数据流方案,以 Mobx 为代表
  • 其他,比如 rxjs 等

到底哪一种架构最合适 React ?目前最流行的数据流方案,截止 2017.1,最流行的社区 React 应用架构方案如下。

缺点:要引入多个库,项目结构复杂。dva 是体验技术部开发的 React 应用框架,dva将上面三个 React 工具库包装在一起,简化了 API,让开发 React 应用更加方便和快捷。dva = React-Router + Redux + Redux-saga。

Action

Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch 函数;需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的。

方式一(推荐): 使用dispatch调用models里面effects/reducer声明的方法

this.props.dispatch({
  type: 'system/testFunc',                                      //type,命名空间/effects方法名
  payload: params,                                              //payload,参数
});

方式二:dispatch带callback回调函数作为第三个参数

//组件中
this.props.dispatch({
  type: 'system/testFunc',
  payload: params,
  callback: (res) => {
    if (!!res) {
      //do sth
    }
  }
});

//model中
*testFunc({ payload, callback }, { call, put }){
  const { data } = yield call(services.rightSave, payload);
  if (data && data.code == 0) {
    !!callback && && callback(data);
  }
}

connect

方式一:通过connet函数

import { connect } from 'dva'; 
function mapStateToProps(state) { //state是项目所有的models
  const { selectList } = state.system; //获取namespace命名空间为system的models数据state
  const { organizationList } = state.global; //全局获取namespace命名空间为global的models数据state
  return {
    selectList,
    organizationList
  };
}
export default connect(mapStateToProps)(System);   //connect组件
//或者直接解构
export default connect(({ system, global: { organizationList } }) => ({ ...system, organizationList }))(System); 

方式二:es6注解方式引入,只能用于class继承组件

@connect(({ system, global: {organizationList} }) => ({ 
  ...system,
  organizationList
}))
class System extends React.Component{render(){return {
}}

在@connect注解中可以注入想使用的任何model,注入后这些model中的数据可以在组件的props参数中取到


Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
Webpack打包原理 Webpack打包原理
近年来 Web 应用变得更加复杂与庞大,Web 前端技术的应用范围也更加广泛。 从复杂庞大的管理后台到对性能要求苛刻的移动网页,再到类似 ReactNative 的原生应用开发方案,Web 前端工程师在面临更多机遇的同时也会面临更大的挑战。
2021-06-30
Next 
Synchronized之轻量级锁 Synchronized之轻量级锁
在偏向锁出现竞争后,加锁失败的线程会把Mark Word中的锁状态改为轻量级锁,这样其他线程再来时就会走向轻量级锁的加锁流程。下面开始轻量级锁获取流程分析,代码在bytecodeInterpreter.cpp#1816。 CASE(_mon
2021-05-09
  TOC