ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

umi+dva项目快速上手指南

2020-03-07 21:39:40  阅读:11008  来源: 互联网

标签:手指 models dva system js 组件 umi 路由


在react项目中,使用react+umi+dva+antd这一阿里系列技术栈的人越来越多,本文就来分享一下umi项目的上手教程。

构建项目

node环境

node版本 >= 8.0.0

全局安装umi

npm install -g umi

建议使用yarn安装

// 全局安装yarn
npm install -g yarn

// 使用yarn安装umi
yarn global add umi

构建umi项目

mkdir myapp && cd myapp
yarn create umi

详细构建请参考 umi官方项目构建

目录结构

├── config/
    ├── config.js                  // umi 配置,同 .umirc.js,二选一
├── dist/                          // 默认的 build 输出目录
├── mock/                          // mock 文件所在目录,基于 express
├── public/                        // 全局相对路径文件
└── src/                           // 源码目录,可选
    ├── assets/                    // 静态文件
    ├── components/                // 全局共用组件
    ├── layouts/index.js           // 全局入口文件
    ├── models/                    // 全局models文件,存放全局共用数据store
    ├── pages/                     // 页面目录,业务组件
        ├── .umi/                  // dev 临时目录,需添加到 .gitignore
        ├── .umi-production/       // build 临时目录,会自动删除
        ├── index/                 // 首页模块
        ├── manager/               // 管理端模块
            ├── components/        // 管理端-局部公共组件
            ├── models/            // 管理端-局部models,存放manager的store
            ├── services/          // 管理端-局部services,存放manager的接口
            ├── index.js           // 业务组件index
            ├── page.js            // 业务组件page
            ├── _layout.js         // 局部入口文件
        ├── 404.js                 // 404 页面
    ├── services/                  // 全局services文件,存放全局公共接口
    ├── utils/                     // 全局工具类
    ├── global.css                 // 约定的全局样式文件,自动引入,也可以用 global.less
    ├── global.js                  // 约定的全局Js文件,自动引入,可以在这里加入 polyfill
    ├── app.js                     // 运行时配置文件
├── .umirc.js                      // umi 配置,同 config/config.js,二选一
├── .env                           // 环境变量
└── package.json

配置文件

umi 允许在 .umirc.js 或 config/config.js (二选一,.umirc.js 优先)中进行配置,支持 ES6 语法。本文使用config/config.js

export default {
  base: '/web/',  //部署到非根目录时才需配置
  targets: { //配置浏览器最低版本,比如兼容ie11
   ie: 11
  },
  hash: true,  //开启打包文件的hash值后缀
  treeShaking: true, //去除那些引用的但却没有使用的代码
  plugins: [
    [
      'umi-plugin-react',
      {
        antd: true, //启用后自动配置 babel-plugin-import,实现antd按需加载
        dynamicImport: { //实现路由级的动态加载
          webpackChunkName: true //实现有意义的异步文件名
        },
        dva: {
          dynamicImport: true, //是否启用按需加载
          hmr: true //是否启用 dva 的 热更新
        },
        //通过 webpack 的 dll 插件预打包一份 dll 文件来达到二次启动提速的目的
        dll: {
          exclude: [],
          include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch', 'antd/es']
        },
        //约定式路由时才需引用,用于忽略指定文件夹中自动生成的路由
        routes: {
          exclude: [
            /components\//,
            /model\.(j|t)sx?$/,
            /components\.(j|t)sx?$/,
            /service\.(j|t)sx?$/,
            /models\//,
            /services\//
          ],
        },
      }
    ]
  ],
  //配置式路由时,路由文件由此引用(往下会讲到)
  routes: routes,
  //代理请求
  proxy: {
    "/api": {
      "target": "http://jsonplaceholder.typicode.com/",
      "changeOrigin": true,
      "pathRewrite": { "^/api" : "" }
    }
  },
  alias: {'@': resolve(__dirname, '../src'),} //别名,umirc.js为'src'
};

pages

+ pages
  + manager
    + components/
    + models/
    + services/
    - index.js
    - _layout.js  

业务目录中,标配局部公用组件components,局部共用数据models,局部共用接口services,_layout.js入口文件需要则引入。

路由

1.约定式路由

1.1 umi约定,在pages目录下的.js/.jsx会自动生成路由,除开在配置文件plugins/routes中被exclude的目录或文件,文件的路径即路由。
1.2 src/layout/为全局layout,默认全局入口文件,配置式路由下无效。pages/下任何文件下的_layout.js即当前文件夹路由下的入口文件,必须先经过_layout.js才能进入当前文件夹路由

2.配置式路由

在配置文件 .umirc.(ts|js) 或者 config/config.(ts|js)中引入:

export default {
  routes: [
    {
      path: '/',
      component: '../layouts/index',
      routes: [
        { path: '/user', redirect: '/user/login' },//redirect,避免只渲染_layout.js
        { path: '/user/login', component: './user/login' },
        {
          path: '/manager', component: '../pages/management/_layout.js',
          routes: [
            { path: '/manager/system', component: '../pages/management/manager/system', Routes: ['./routes/PrivateRoute.js'] }
        }
      ],
    },
  ],
};
3.权限路由

路由通过Routes属性来实现权限路由,如上文:/manager/system路由。创建一个./routes/PrivateRoute.js 权限文件,props里会传入/manager/system的路由信息。

export default (props) => {
  return (
    <div>
      <div>PrivateRoute (routes/PrivateRoute.js)</div>
      { props.children }
    </div>
  );
}
4.跳转路由

link方式

  import Link from 'umi/link';
  <Link to="/list">Go to list page</Link> 

router方式

  import router from 'umi/router';
  router.push({
    pathname: '/list',
    query: {
      a: 'b',
    },
  });

models层

models/index.js,不能空文件。由于umi/dva是基于redux,redux-saga的数据流方案,dva的connect即redux的connect,dva的model即redux-saga简化版,更易用。

import * as services from '../services';
{
  namespace: 'system',                             //models命名空间,需全局唯一
  state: {
    dataList: []
  },                                      //models存储的数据store
  reducers: {
    save(state, { payload }) {                    //更新store,用新数据合并state的旧数据
      return { ...state, ...payload };
    }
  },
  effects: {
    * testFunc({ payload: params }, { call, put, select }) {   //dispatch请求的方法
      const { dataList } = yield select(state => state.system); //获取models中的state
      const { data } = yield call(services.testFunc, params);  //call,请求services里面的接口以及传参,可继续往后面加参数,跟JavaScript的call一样
      if (data && data.code == 0) {
        const data_ = data.data.content;
        yield put({ //put,必须发出action save,此action被reducer监听,从而达到更新state数据的目的
          type: 'save',                                        
          payload: {
            dataList: data_ || []
          }
        });
        return data_;                                          //返回response,可选
      }                                                        
    },
  },
  subscriptions: {                                             //订阅,在app.start()即启动项目时被执行
    setup({ dispatch, history }) {
      return history.listen(({ pathname, query }) => {
        // 进入 '/manager/system' 路由,会发起一个名叫 'save' 的 effect
        if (pathname === '/manager/system') {
					//do sth... dispatch({ type: 'save', payload: query });
				}
      })
    }
  }
}

注:call会阻塞,在call方法调用结束之前,call方法之后的语句是无法执行的,使得以同步的方式执行异步,如需无阻塞,则要引入fork,用yield fork代替yield call。

services层

请求后台接口的方法,返回一个promise对象。

import request from '@utils/request';

export function rightSave(values) {
  return request(`/authirty/right/save`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(values)
  });
}

components层

dispatch

与effect中的put方法相似,必须触发action。

  • 在model的subscription参数中使用。
  • 在组件中,由connect过的组件中,组件的props中可以获取到dispatch。
import { connect } from 'dva'; 
export default connect()(System);   
  • 接口请求

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

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

//直接赋值更新state
this.props.dispatch({
  type: 'system/save',                                          //type,命名空间/reducer方法名
  payload: {
    rightDetail: {a:1}
  },                                              //payload,参数
}).then(res => {})

//请求全局model
this.props.dispatch({
  type: 'global/getOrganizationList',                           //全局namespace/全局effects方法名
  payload: params,                                              //payload,参数
}).then(res => {})

方式二: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);
  }
}

方式三(少用):如果组件中不需要用到model存store时,直接引入services请求接口

//组件中
//使用new promise请求接口,service返回promise函数,待接口异步请求完才执行.then
import * as service from '../services'; 
new Promise((resolve) => {
  const ret = service.rightSave(param);
  resolve(ret);
}).then((ret) => {
  if (ret && ret.code == 200) {
    //do sth
  }
});

connect

方式一:mapStateToProps,class继承组件和函数组件都能用这种

接口请求完后,组件中引入connect,获取models的数据放入当前组件props中

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 {<></>}}}

withRouter

withRouter的作用:未经路由跳转的组件,如子组件想拿到路由的相关信息location、history、match等时可用,经路由跳转的页面则默认已有路由信息。
FAQ:

  1. url 变化了,但页面组件不刷新,是什么原因?
    layouts/index.js 里如果用了 connect 传数据,需要用 umi/withRouter 高阶一下
import withRouter from 'umi/withRouter';
export default withRouter(connect(mapStateToProps)(LayoutComponent));
  1. 全局 layout 使用 connect 后路由切换后没有刷新?
    需用 withRouter 包一下导出的 react 组件,注意顺序。
import withRouter from 'umi/withRouter';
export default withRouter(connect()(Layout));

mock

export default {
  // 支持值为 Object 和 Array
  'GET /api/users': { users: [1, 2] },
  // 支持自定义函数,API 参考 express@4
  'POST /api/users/create': (req, res) => { res.end('OK'); },
}

如请求接口/api/list,数据返回{ users: [1, 2] }

数据流程

最后给大家总结一下dva的数据流向:

  • View层dispatch操作action –> 触发models层effect中相应方法 –> 触发call发起services层请求,获取接口数据 –> 触发put发起reducer处理相应的action更新数据 –> 更新model层中state –> 触发view层的render方法进行重新渲染 –> 页面更新

附上数据流向图
dva数据流向图

参考文档

umi文档:https://umijs.org/zh/guide/
dva文档:https://dvajs.com/guide/

ps:如有纰漏,请留言更正!

标签:手指,models,dva,system,js,组件,umi,路由
来源: https://blog.csdn.net/u010074572/article/details/104722800

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有