UmiJS官方文檔:https://umijs.org/zh-CN
一、什么是Umi.js
umi,中文可發音為烏米,是一個可插拔的企業級 react 應用框架。你可以將它簡單的理解為一個專注性能的類 next.js 前端框架,並通過約定、自動生成和解析代碼等方式來輔助開發,減少我們開發者的代碼量。
其特點:
1、插件化 :umi
的整個生命周期都是插件化的,甚至其內部實現就是由大量插件組成,比如:pwa、按需加載、一鍵切換 preact
、一鍵兼容 ie9 等等,都是由插件實現。
2、開箱即用 :你只需一個umi
依賴就可啟動開發,無需安裝react
、preact
、webpack
、react-router
、babel
、jest
等等。
3、約定式路由 :類似next.js
的約定式路由,無需再維護一份冗余的路由配置,支持權限、動態路由、嵌套路由等等。
二、為什么使用Umi.js?
我們做react開發的時候會不會遇到以下問題?:
1、項目做大的時候,開發調試的啟動和熱更新時間會變得很長。
2、大應用下,網站打開很慢,有沒有辦法基於路由做到按需加載。
3、dva的model每次都要手寫載入,能否一開始就同項目初始化好?
使用烏米,即可解決以上問題,並且還能提供如下優勢:
- 開箱即用,內置 react、react-router 等
- 類 next.js 且功能完備的路由約定,同時支持配置的路由方式
- 完善的插件體系,覆蓋從源碼到構建產物的每個生命周期
- 一鍵兼容到 IE9
- 完善的 TypeScript 支持
- 與 dva 數據流的深入融合
三、UmiJS快速上手
1、node環境安裝:nodeJS版本需>=8.10
2、需要全局安裝Umi:
npm install -g umi
# 使用yarn安裝umi yarn global add umi
(1)如果你要使用 yarn 安裝,那就需要先安裝 yarn:
可以把yarn
看做了優化了的npm
,其中tyarn
使用的是npm.taobao.org的源,速度要快一些。平常使用的話使用tyarn
即可。
npm i yarn tyarn -g --registry=https://registry.npm.taobao.org
然后使用tyarn安裝umi
tyarn global add umi
(2)查看是否安裝成功:umi -v
如果出現 'umi' 不是內部或外部命令,也不是可運行的程序 或批處理文件
或者提示 umi: command not found
解決方案:
3、快速上手:先新建個空目錄myapp
# 新建應用 $ mkdir myapp && cd myapp # 新建頁面 $ umi generate page index # 本地開發 $ umi dev # 構建上線 $ umi build
4、如果是拿到別人的項目是使用 Umi 開發的,那么需要怎么做?
(1)首先,全局安裝 UmiJS:npm install -g umi
(2)其次,安裝項目所需依賴:npm install
(3)其次,啟動項目:根據 package.json 里的啟動命令 : umi dev
四、項目工程目錄簡介
一個簡單的demo工程,列下工程下的文件功能描述
public // 公共文件 可以放一些第三方字體 樣式庫等
mock // mock文件
src |-- components // 公共組件目錄 當業務需要拆分組件的時候,可以在對應的業務文件夾下單獨創建一個components文件夾
|-- layouts // 項目結構文件
|-- locales // 規划文件
|-- models // 公共model存放位置
|-- public.js // 公共model文件 可以多個
|-- services // 公共api存放
|-- pages // 容器組件
|-- demo-umi // 業務容器 相對路由/demo ***不可以有任何大寫字母
|-- index.js // 業務入口 入口文件只識別index.js 后綴必須是js
|-- index.less // 業務樣式
|-- modules // 業務model目錄
|-- demo-m.js // 業務model文件 可以有多個 自動加載
|-- service // 業務api目錄
|-- demo-s.js // 業務api文件 可以有多個
|-- utils // 工具
|-- theme |-- cofnig.js // 覆蓋antd樣式文件
|-- vars.less // 全局變量
|-- global.less // 公共樣式 覆蓋樣式
.eslintignore // eslint過濾文件清單
.eslintrc.js // eslint配置
.gitignore package.json README.md
1、約定式路由
啟動 umi dev 后,大家會發現 pages 下多了個 .umi 的目錄。
五、使用 dva
在 umi 項目中,你可以使用 dva 來處理數據流,以響應一些復雜的交互操作。
在 umi@^2 中要使用 dva 的功能很簡單,只要使用 umi-plugin-react 插件並配置 dva:true 即可。
// ref: https://umijs.org/config/
export default { plugins: [ // ref: https://umijs.org/plugin/umi-plugin-react.html
['umi-plugin-react', { antd: true, dva: true, // 在此處啟用 dva
dynamicImport: false, title: 'hero', dll: false, routes: { exclude: [], }, hardSource: false, }], ], }
在dva中,處理數據流的文件統一放在 models 文件夾下,每一個文件默認導出一個對象,里面包含數據和處理數據的方法,通常我們稱之為 model 。
如以下count.js,model結構一般是如此:
// ./src/models/count.js
export default { namespace: 'count', // 默認與文件名相同
state: 'count', subscriptions: { setup({ dispatch, history }) { }, }, reducers: { update(state) { return `${state}_count`; }, }, effects: { *fetch({ type, payload }, { put, call, select }) { }, }, }
關於reducers,effects,subscriptions的詳細介紹,可參考dva.js官方文檔:Dva.js。
1、在項目頁面中使用model
我們需要導入connect將頁面和model綁定在一起。
import { connect } from 'dva'; function CountPage(props) { //從props屬性中打印namespace為count的model的state數據
console.log(props.count); return ( <div className={styles.normal}>
<h1>數量大小</h1>
<h2>This is {props.count}</h2>
</div> ); } export default connect(({ count }) => ({ count }))(CountPage);
如果使用es7的裝飾器,我們可以改成這樣的寫法:
import { connect } from 'dva'; // 裝飾器
@connect(({ count }) => ({ count })) function CountPage(props) { //從props屬性中打印namespace為count的model的state數據
console.log(props.count); return ( <div className={styles.normal}>
<h1>數量大小</h1>
<h2>This is {props.count}</h2>
</div> ); } export default CountPage;
六、分層開發
1、過程圖示
上圖中,左邊是用戶,中間為前端,右邊為后端。我們對前端進行分層,可以分為Page
、Model
、Service
3層。
(1)Page
負責與用戶直接打交道:渲染頁面、接受用戶的操作輸入,側重於展示型交互性邏輯。
(2)Model
負責處理業務邏輯,為 Page 做數據、狀態的讀寫、變換、暫存等。
(3)Service
負責與 HTTP 接口對接,進行純粹的數據讀寫。
其中
Page
層通過UmiJS
的umi-plugin-react
插件的dva
功能,可以調用Model
層定義的數據和方法;
Model
層通過import
定義的異步請求函數request.js
來調用Service
層;
而Service
層就是去后端請求數據。
2、開始開發
// 1、添加umi的依賴
tyarn add umi --dev // 2、添加umi-plugin-react插件
tyarn add umi-plugin-react --dev // 3、添加.gitignore文件
node_modules dist .umi // 4、在config/config.js配置中引入umi-plugin-react插件
export default { plugins: [ ['umi-plugin-react', { dva: true, antd: true }] ] };
3、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、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(); }
5、Mock模擬數據
在 mock目錄下創建UserList.js文件,用來模擬數據。因為有了umi默認集成了mock功能,所以只要編寫mock數據即可。
export default { 'get /ds/list' : function (req, res) { res.json({ data: ['zhangsan','lisi','wangwu'] }) } }
6、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
。
7、Page層引入Model層的數據和方法
dva是基於 redux、redux-saga 和 react-router 的輕量級前端框架,官網:https://dvajs.com/
(1)@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中的數據。
(2)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;
8、umi-plugin-react插件升級
在運行 umi dev
或 umi build
運行或部署應用是,有時候會出現 “Path must be a string”錯誤。
解決方法:按照官網升級umi-plugin-react的版本。
// 1、 package.json文件
{ "devDependencies": { - "umi-plugin-react": "^1"
+ "@umijs/preset-react": "^1" } } // 2、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
參考文章:https://www.cnblogs.com/zhaoxxnbsp/p/12672652.html