蚂蚁金服开源的企业级React框架,并不是UI框架
- 特性
- 开箱即用,内置
react、react-router ... - 类似
next.js且功能完备的路由约定,同时也支持手动配置路由的方式; - 完善的插件体系,高性能,通过插件支持PWA、以路由为单元的code splitting等等;
- 支持静态页面导出,适配各种环境,如中台业务、无线业务、egg、支付宝钱包
- 开发启动快,支持一键开启dll
- 一键兼容IE9、基于
umi-plugin-polyfills - 支持TypeScript
- 与
dva数据流的深入融合,支持duck directory、model的自动加载、code splitting等等
- 开箱即用,内置
-
dva是React应用框架,封装了Redux、Redux-saga、React-router三个React工具库,目前React最流行的数据流解决放案;

数据流向.jpg
1. State:一个对象,保存整个应用状态;
2. View:React组件构成的视图层;
3. Action:一个对象,描述事件
4. connect():绑定```State```到```View```
5. dispatch():发送```Action```到```State```
-
dva与umi的约定-
src源码:pages(页面)、components(组件)、layout(布局)、model(数据模型) -
config配置 -
mock数据模拟 -
test测试
-
- 全局安装脚手架:
npm i umi -g
初体验
- 创建一个项目目录:umidemo
-
cd umidemo -> npm init:生成package.json"scripts": { "start": "umi dev", "build": "umi build" } - 创建src目录,生成
pages目录,默认使用约定式路由;cd src umi g page index // index.js和index.css umi g page about // bout.js和about.css - 运行项目:
npm start,自动编译生成页面配置/src/pages/.umi目录,且项目是热部署;http://localhost:8000/ --> index.js http://localhost:8000/about --> about.js
约定式路由嵌套
- 当出现
_layout.js页面时默认为父组件页面,通过{props.children}显示子组件内容; - 嵌套路由:
/users,创建pages/users目录umi g page users/_layout // pages/users/_layout.js、_layout.css-
pages/users/_layout.jsimport styles from './_layout.css' export default function(props) { return ( <div className={styles.normal}> <h1>Page users _layout</h1> <div>{props.children}</div> //引入子组件,如果没有创建子组件,则会报错 </div> ) } - 为
_layout.js创建子组件,users的首页index.jsumi g page users/index // pages/users/index.js、index.css - 访问嵌套路由:
http://localhost:8000/users
-
- 动态路由文件的命名以
$为开头,users/$name.js对应路由为/users/:name- 在
users目录中,再创建$name.js、$name.css - 访问
$name.js:http://localhost:8000/users/xxx
- 在
- 路由跳转
users/index.js
import Link from 'umi/Link' export default function() { const userList = [ { id:1, name:'Tim' }, { id:2, name:'Jerry' } ] return (<div> <ul> { userList.map(item=>( <li key={item.id}> <Link to={`/users/${item.name}`}>{item.name}</Link> </li> )) } </ul> </div>) }users/$name.js
export default function(props) { return ( <div> <h2>{props.match.params.name}</h2> <button onClick={()=>props.history.goBack()}>返回</button> </div> ) } -
src/layout目录中的index.js将成为项目的定级布局页面,使用{props.children}显示src/pages目录中的组件。import styles from './index.css' export default function(props) { return ( <div className={styles.normal}> <p>我是项目的Layout布局</p> <div>{props.children}</div> </div> ) }
配置式路由
- 配置式路由一旦创建,约定式路由自动失效,umi不会再自动创建路由;
- 在项目根目录下创建
config目录,并创建config.js文件;export default { //路由配置:路径相对于src/pages routes: [ { path: "/", component: "./index" }, { path: "/about", component: "./about" }, { path: "/users", component: "./users/_layout", routes: [ { path: "/users", component: "./users/index" }, { path: "/users/:name", component: "./users/$name" }, ] }, { component: "./notfound" } // 404页面,上面的所有路由都没有匹配时,则匹配404页面 ] } - 相应地,创建404组件:
umi g page notfound - 路由守卫
- 路由守卫组件的路径相对于项目根目录,且后缀名不能省略;
- 在项目根目录下创建
routes目录,用于存放路由守卫组件; routes/PriviteRoute.js
import Redirect from 'umi/redirect' export default function(props) { if(Math.random()>0.5) { return <Redirect to="/login" /> //没有登录时,重定向到登录页 } //登录成功时,显示子路由的页面组件 return <div>{props.children}</div> }- 创建登录页
umi g page login - 在
config/config.js中配置/login,并守卫/about
routes: [ { path: "/login", component: "./login" }, { path: "/about", component: "./about", Routes: ["./routes/PriviteRoute.js"] //路由守卫 }, ...... ] - 引入ant design UI库
npm i antd -S npm i umi-plugin-react -Dconfig/config.js
export default { //路由配置 routes: [...], //插件配置 plugins: [ [ "umi-plugin-react", { antd: true } ] ] }- 使用时需要导入组件,因为是按需加载
import {Button} from 'antd' <Button type="primary">Primary</Button>
引入dva
-
dva主要是软件分层的概念-
Page负责与用户直接交互:渲染页面、接收用户的操作输入,侧重于展示型和交互逻辑; -
Model负责处理业务逻辑,可以理解成一个维护页面数据状态的对象,为Page做数据、状态的读写等操作;
export default { namespace: 'goods', // model的命名空间,区分多个model state: [], //初始状态 effects: { //异步操作 }, reducers: {} }-
Service主要负责与HTTP做接口对接,跟后端做数据交互,读写数据;
-
-
dva已经融合进了umi,在config/config.js中打开dva的开关plugins: [ [ "umi-plugin-react", { antd: true, dva: true } ] ]
基本用法
umi g page goods
npm i axios -S
- 路由配置:
config/config.jsroutes: [ { path: "/goods", component: "./goods" }, ] - 在项目根目录下创建
mock/goods.js,模拟接收请求,响应数据let data = [ { title: '单页面' }, { title: '管理项目' }, ], export default { //method url "get /api/goods": function(req, res) { setTimeout(()=>{ res.json({result: data}) }, 1000); } } -
Model:src/models/goods.jsimport axios from 'axios' //调接口的逻辑应该放在 Service 层 function getGoods() { return axios.get('/api/goods') } export default { namespace: 'goods', // 命名空间,如果省略,则以文件名作为命名空间 state: [], effects: { *getList(action, {call, put}) { //异步操作 //发起请求 const res = yield call(getGoods) //派发异步action: initGoods yield put({type:'initGoods', payload:res.data.result}) } }, reducers: { initGoods(state, action) { return action.payload }, addGood(state, action) { // 添加函数,返回一个新的state return [...state, {title: action.plyload.title}] } } } -
pages/goods.jsimport {connect} from 'dva' import React, {Component} from 'react' @connect( state=>({ goodList: state.goods, // 从指定命名空间内获取state loading: state.loading, // 通过loading命名空间获取加载的状态 }), { getList: ()=>{ {type: 'goods/getList'} // action的type需要以命名空间为前缀,后跟reducer }, addGood: title=>({ type:'goods/addGood', payload: {title} }) } ) export default class extends Component { componentDidMount() { this.props.getList() //触发事件,发起请求,获取数据 } render() { if(this.props.loading.models.goods) { //命名空间goods 的请求在加载中 return <div>loading</div> } return ( <div> <ul> { this.props.goodList.map((good, index)=>{ return <li key={index}>{good.index}</li> }) } </ul> <button onClick={()=>this.props.addGood("商品3")}>添加</button> </div> ) } }
升级路由守卫
- 在项目根目录下创建
mock/login.jsexport default { "post /api/login": function(req, res) { const {username, password} = req.body if(username==="xxx"&&password==="xxx") { return res.json({code: 200, data: {token:"xxxx", name:"xxx"}}) } return res.json({code: 404, info:"登录失败"}) } } -
Model:src/models/login.jsimport axios from 'axios' import router from 'umi/router' //初始的state const initUserInfo = { token: "", name: "" } //调接口的逻辑应该放在 Service 层 function login(data) { return axios.post('/api/login', data) } export default { namespace: 'login', state: initUserInfo, effects: { //异步操作 *login(action, {call, put}) { //发起请求 const res = yield call(login, action.payload) if(res.data.code===200) { //登录成功 //派发异步action yield put({type:'init', payload:res.data.data}) //登录成功,跳转去首页 router.push('/') } else { console.log("登陆失败!") } } }, reducers: { init(state, action) { return action.payload } } } -
pages/login.jsimport {connect} from 'dva' import React, {Component} from 'react' @connect() export default class extends Component { onSubmit() { //派发action,发起请求 this.props.dispatch({type:"login/login", payload:{username:"xxx",password:"xxx"}}) } render() { return ( <div> <button onClick={onSubmit}>登录</button> </div> ) } } -
routes/PriviteRoute.jsimport React,{Component} from 'react' import Redirect from 'umi/redirect' import {connect} from 'dva' @connect( state => ({token:state.login.token}) //从指定命名空间内获取state ) export default class extends Component { render() { if(this.props.token) { //登录成功时,显示子路由的页面组件 return <div>{this.props.children}</div> } //没有登录时,重定向到登录页 return <Redirect to="/login" /> } }