1)、簡介
UmiJS讀音:(烏米)
UmiJS是一個可插拔的企業級React應用框架。官網地址是:https://umijs.org/zh/
特點:
-
插件化
umi的整個生命周期都是插件化的,甚至其內部實現就是由大量插件組成,比如:pwa、按需加載、一鍵切換preact、一鍵兼容 ie9 等等,都是由插件實現。 -
開箱即用
你只需一個umi依賴就可啟動開發,無需安裝react、preact、webpack、react-router、babel、jest等等。 -
約定式路由
類似next.js的約定式路由,無需再維護一份冗余的路由配置,支持權限、動態路由、嵌套路由等等。
2)、安裝
2.1)、安裝 nodejs
要使用UmijS首先要安裝nodejs環境,在Mac下安裝nodejs:
brew install nodejs
windows下安裝,需要到官網下載安裝程序,然后下一步下一步完成即可,也非常簡單。這里不介紹了,也可自行百度,網上有很多。
2.2)、安裝 yarn
可以把yarn看做了優化了的npm,其中tyarn使用的是npm.taobao.org的源,速度要快一些。平常使用的話使用tyarn即可。
npm i yarn tyarn -g --registry=https://registry.npm.taobao.org
2.3)、安裝 umi
tyarn global add umi
查看是否安裝成功:
umi -v
如果出現 'umi' 不是內部或外部命令,也不是可運行的程序 或批處理文件或者提示 umi: command not found

3)、使用 umi
3.1)、創建項目
mkdir ~/Documents/umi-demo
3.2)、初始化
進入到項目文件夾下
cd ~/Documents/umi-demo
再創建umi其他默認的目錄,這些默認的目錄名字不能寫錯。
mkdir -p {config,mock,src/pages,src/models,src/layouts}
config/: 該目錄下的config.js默認是全局的配置
mock/: 模擬后端請求數據的js目錄
src/: 代碼目錄
pages/: 為前端頁面, 頁面的后綴名可以是.js或者.jsx
models/: 為數據層, 處理數據的 js 文件
layouts/: 默認是整個項目的基礎布局文件
通過初始化命令將生成package.json文件,它是NodeJS約定的用來存放項目的信息和配置等信息的文件。
tyarn init -y
為項目設置一個默認的展開頁面,因為umiJS在后台啟動時會默認加載src/pages/index.js文件。使用下面命令可以通過umi命令創建index.js文件。
umi g page index
可以看到在pages下創建好了index.js和index.css文件。
3.3)、開發工具打開

目錄結構

3.4)、運行項目
umi dev

4)、分層開發
4.1)、過程圖示

說明:
上圖中,左邊是用戶,中間為前端,右邊為后端。我們對前端進行分層,可以分為Page、Model、Service3層。
Page負責與用戶直接打交道:渲染頁面、接受用戶的操作輸入,側重於展示型交互 性邏輯。Model負責處理業務邏輯,為 Page 做數據、狀態的讀寫、變換、暫存等。Service負責與 HTTP 接口對接,進行純粹的數據讀寫。
其中 Page層通過UmiJS的umi-plugin-react插件的dva功能,可以調用Model層定義的數據和方法;Model層通過import定義的異步請求函數request.js來調用Service層;而Service層就是去后端請求數據。
4.2)、開始開發
4.2.1)、添加依賴
添加umi的依賴
tyarn add umi --dev
添加umi-plugin-react插件
tyarn add umi-plugin-react --dev
添加.gitignore文件
node_modules
dist
.umi
在config/config.js配置中引入umi-plugin-react插件
export default {
plugins: [
['umi-plugin-react', {
dva: true,
antd: true
}]
]
};
4.2.2)、antd基本布局
添加基本布局和樣式:
在layouts文件目錄下創建index.js文件,在index.js中我們寫入:
import { Component } from 'react';
import { Layout } from 'antd';
// Header, Footer, Sider, Content組件在Layout組件模塊下
const { Header, Footer, Sider, Content } = Layout;
class BasicLayout extends Component {
render() {
return (
<Layout>
<Sider width={256} style={{ minHeight: '100vh', color: 'white' }}>
Sider
</Sider>
<Layout >
<Header style={{ background: '#fff', textAlign: 'center', padding: 0 }}>Header</Header>
<Content style={{ margin: '24px 16px 0' }}>
<div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
{this.props.children}
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
</Layout>
</Layout>
)
}
}
export default BasicLayout;
上面代碼中,我們創建了一個三部分的基本布局:Header 、Content 、Footer。然后我們將 Content 替換成 { this.props.children },這樣之后我們設置的路由會通過替換 children 變量實現內容的切換。
上面需要引入的組件都安裝好了之后,我們就可以來編寫我們的前端代碼了。我們可以根據上面的圖,按照從下到上的順序進行編寫,也就是Service→Model→Page來進行。
4.2.3)、Service異步請求數據
在 src 目錄下創建 utils 目錄, 創建 request.js 文件
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
return await response.json();
}
4.2.4)、Mock模擬數據
在 mock目錄下創建UserList.js文件,用來模擬數據。因為有了umi默認集成了mock功能,所以只要編寫mock數據即可。
export default {
'get /ds/list' : function (req, res) {
res.json({
data: ['zhangsan','lisi','wangwu']
})
}
}
4.2.5)、Model層中引入該 js 文件用於異步請求
import request from '../../utils/request'
export default {
namespace: 'user',
//該模型中的一些屬性
state: {
data: []
},
//一些正常的同步方法
reducers: {
//state是原先的數據,result是effets中異步調用返回的數據
save(state, result){
//如果 result.data中存在數據,表示該函數是被異步調用初始化。直接返回
if (result.data){
return result.data;
}
let list = [...state.data, 'freeman'];
//返回更新后的state對象
return {
data: list
}
}
},
effects: {
// 這里定義異步方法
*initData(params, sagaEffects) { //定義異步方法
const {call, put} = sagaEffects; //獲取到call、put方法
const url = "/ds/list"; // 定義請求的url
let result = yield call(request, url); //執行請求
yield put({ // 調用reducers中的方法
type : "save", //指定方法名
data : result //傳遞ajax回來的數據, 注意 put 會指定調用的同步方法[reducers 中定義的方法],
//該調用的方法會在定義的方法的入參添加一個參數(result), 使用該參數才能獲取到put方法,取到的值
});
}
}
}
DVA 的 model 對象有幾個基本的屬性,需要大家了解。
namespace:model的命名空間,只能用字符串。一個大型應用可能包含多個model,通過namespace區分。state:當前model狀態的初始值,表示當前狀態。reducers:用於處理同步操作,可以修改state,由action觸發。reducer是一個純函數,它接受當前的state及一個數據體(payload)作為入參,返回一個新的state。effects:用於處理異步操作(例如:與服務端交互)和業務邏輯,也是由action觸發。但是,它不可以修改 state,要通過觸發action調用reducer實現對state的間接操作。action:是reducers及effects的觸發器,一般是一個對象,形如{ type: 'add', payload: todo },通過type屬性可以匹配到具體某個reducer或者effect,payload屬性則是數據體,用於傳送給reducer或effect。
4.2.6)、Page層引入Model層的數據和方法
dva是基於 redux、redux-saga 和 react-router 的輕量級前端框架。官 網:https://dvajs.com/
@connect(mapStateToProps, mapDispatchToProps) 需要2個參數:
-
mapStateToProps:是一個方法,該方法的返回值是一個屬性對象{},它的作用是將這個包含state屬性的對象注入到this.props中。組件通過this.props.xx的方式即可獲取到model中的數據。- ① 、umi框架啟動,會自動讀取models目錄下所有model文件,(如:user/List.js中的數據 )
- ②、這些model數據 會進入到
mapStateToProps方法中 - ③、在全局的數據中,會有很多,所以需要通過namespace進行區分,所以通過
state[namespace]進行獲取數據 - ④、拿到model數據中的data,也就是
['zhangsan','lisi','wangwu']數據,進行包裹{}后返回 - ⑤、返回的數據,將被封裝到
this.props中,所以通過this.props.listData即可獲取到 model中的數據。
-
mapDispatchToProps: 是一個方法,它的返回值是一個函數對象{}, 它的作用是將這些函數注入到this.props中。- ①、所以可以把
Model中暴露的方法綁定到當前的組件中,定義一個方法接收,然后可以綁定到onClick事件上,就可以實現點擊操作;也可以在頁面加載完的時候拿到數據對頁面進行渲染的操作(只需要綁定到生命周期函數上即可)。 - ②、dispatch 函數就是和 dva model 打交道的唯一途徑。 dispatch 函數接受一個 對象 作為入參,在概念上我們稱它為 action,唯一強制要包含的是 type 字段,string 類型,用來告訴 dva 我們想要干什么。我們可以選擇給 action 附着其他字段,這里約定用 payload字段表示額外信息。
- ①、所以可以把
在 pages目錄下新建user/List.js(或List.jsx)頁面,使用快捷鍵rcc或rccp可以快速生成react組件。
import React, {Component} from 'react';
import { connect } from 'dva';
const namespace = 'user';
const mapStateToProps = (state)=>{
let listData = state[namespace].data;
return {listData}
};
const mapDispatchToProps = (dispatch) => {
// 定義方法,dispatch是內置函數
return { //返回的這個對象將綁定到this.props對象中
addUser : () =>{
// 定義方法
dispatch({ // 通過調用dispatch()方法,調用model中reducers的方法
type: `${namespace}/save` // 指定方法,格式: namespace/方法名
});
},
userList : () => { //新增初始化方法的定義
dispatch({
type: `${namespace}/initData`
});
}
}
}
@connect(mapStateToProps, mapDispatchToProps)
class List extends Component {
componentDidMount() {
this.props.userList();
}
render() {
return (
<div>
<ul>
{
this.props.listData.map(
(v, i) => {return <li key={i}>{v}</li>}
)
}
</ul>
<button onClick={() => {this.props.addUser()}}>
添加
</button>
</div>
);
}
}
export default List;
4.3.)、umi-plugin-react插件升級
在運行 umi dev 或 umi build 運行或部署應用是,有時候會出現 “Path must be a string”錯誤。解決方法:
按照官網升級umi-plugin-react的版本。
- ①、 package.json文件
{
"devDependencies": {
- "umi-plugin-react": "^1"
+ "@umijs/preset-react": "^1"
}
}
- ②、 config/config.js文件
export default {
- plugins: [
- ['umi-plugin-react', {
- dva: {},
- antd: {},
- ...
- }]
- ],
+ dva: {},
+ antd: {},
+ ...
}
可以參考:https://umijs.org/docs/upgrade-to-umi-3#升級-umi-plugin-react-為-umijspreset-react
