umi+dva項目快速上手指南
react+umi+dva+antd umi項目的上手教程。轉載而來,僅用於給人學習。
構建項目
node版本 >= 8.0.0
npm install -g umi
建議使用yarn安裝
// 全局安裝yarn
npm install -g yarn
// 使用yarn安裝umi
yarn global add 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
+ manager
+ components/
+ models/
+ services/
- index.js
- _layout.js
業務目錄中,標配局部公用組件components,局部共用數據models,局部共用接口services,_layout.js入口文件需要則引入。
1.1 umi約定,在pages目錄下的.js/.jsx會自動生成路由,除開在配置文件plugins/routes中被exclude的目錄或文件,文件的路徑即路由。
1.2 src/layout/爲全局layout,默認全局入口文件,配置式路由下無效。pages/下任何文件下的_layout.js即當前文件夾路由下的入口文件,必須先經過_layout.js才能進入當前文件夾路由
在配置文件 .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'] }
}
],
},
],
};
路由通過Routes屬性來實現權限路由,如上文:/manager/system路由。創建一個./routes/PrivateRoute.js 權限文件,props裏會傳入/manager/system的路由信息。
export default (props) => {
return (
<div>
<div>PrivateRoute (routes/PrivateRoute.js)</div>
{ props.children }
</div>
);
}
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/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。
請求後臺接口的方法,返回一個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)
});
}
與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的作用:未經路由跳轉的組件,如子組件想拿到路由的相關信息location、history、match等時可用,經路由跳轉的頁面則默認已有路由信息。
FAQ:
- url 變化了,但頁面組件不刷新,是什麼原因?
layouts/index.js 裏如果用了 connect 傳數據,需要用 umi/withRouter 高階一下
import withRouter from 'umi/withRouter';
export default withRouter(connect(mapStateToProps)(LayoutComponent));
- 全局 layout 使用 connect 後路由切換後沒有刷新?
需用 withRouter 包一下導出的 react 組件,注意順序。
import withRouter from 'umi/withRouter';
export default withRouter(connect()(Layout));
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方法進行重新渲染 –> 頁面更新
附上數據流向圖
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