螞蟻金服開源的企業(yè)級React框架,并不是UI框架
- 特性
- 開箱即用,內(nèi)置
react、react-router ...
- 類似
next.js
且功能完備的路由約定呀枢,同時也支持手動配置路由的方式; - 完善的插件體系笼痛,高性能裙秋,通過插件支持PWA、以路由為單元的code splitting等等缨伊;
- 支持靜態(tài)頁面導(dǎo)出残吩,適配各種環(huán)境,如中臺業(yè)務(wù)倘核、無線業(yè)務(wù)、egg即彪、支付寶錢包
- 開發(fā)啟動快紧唱,支持一鍵開啟dll
- 一鍵兼容IE9、基于
umi-plugin-polyfills
- 支持TypeScript
- 與
dva
數(shù)據(jù)流的深入融合隶校,支持duck directory漏益、model的自動加載、code splitting等等
- 開箱即用,內(nèi)置
-
dva
是React應(yīng)用框架深胳,封裝了Redux绰疤、Redux-saga、React-router
三個React工具庫舞终,目前React最流行的數(shù)據(jù)流解決放案轻庆;
數(shù)據(jù)流向.jpg
1. State:一個對象癣猾,保存整個應(yīng)用狀態(tài);
2. View:React組件構(gòu)成的視圖層余爆;
3. Action:一個對象纷宇,描述事件
4. connect():綁定```State```到```View```
5. dispatch():發(fā)送```Action```到```State```
-
dva
與umi
的約定-
src
源碼:pages(頁面)
、components(組件)
蛾方、layout(布局)
像捶、model(數(shù)據(jù)模型)
-
config
配置 -
mock
數(shù)據(jù)模擬 -
test
測試
-
- 全局安裝腳手架:
npm i umi -g
初體驗
- 創(chuàng)建一個項目目錄:umidemo
-
cd umidemo -> npm init
:生成package.json
"scripts": { "start": "umi dev", "build": "umi build" }
- 創(chuàng)建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
約定式路由嵌套
- 當出現(xiàn)
_layout.js
頁面時默認為父組件頁面,通過{props.children}
顯示子組件內(nèi)容枢步; - 嵌套路由:
/users
沉删,創(chuàng)建pages/users
目錄umi g page users/_layout // pages/users/_layout.js、_layout.css
-
pages/users/_layout.js
import styles from './_layout.css' export default function(props) { return ( <div className={styles.normal}> <h1>Page users _layout</h1> <div>{props.children}</div> //引入子組件醉途,如果沒有創(chuàng)建子組件矾瑰,則會報錯 </div> ) }
- 為
_layout.js
創(chuàng)建子組件,users
的首頁index.js
umi g page users/index // pages/users/index.js隘擎、index.css
- 訪問嵌套路由:
http://localhost:8000/users
-
- 動態(tài)路由文件的命名以
$
為開頭殴穴,users/$name.js
對應(yīng)路由為/users/:name
- 在
users
目錄中,再創(chuàng)建$name.js货葬、$name.css
- 訪問
$name.js
:http://localhost:8000/users/xxx
- 在
- 路由跳轉(zhuǎn)
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> ) }
配置式路由
- 配置式路由一旦創(chuàng)建震桶,約定式路由自動失效休傍,umi不會再自動創(chuàng)建路由;
- 在項目根目錄下創(chuàng)建
config
目錄蹲姐,并創(chuàng)建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頁面 ] }
- 相應(yīng)地忙厌,創(chuàng)建404組件:
umi g page notfound
- 路由守衛(wèi)
- 路由守衛(wèi)組件的路徑相對于項目根目錄,且后綴名不能省略江咳;
- 在項目根目錄下創(chuàng)建
routes
目錄逢净,用于存放路由守衛(wèi)組件; 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> }
- 創(chuàng)建登錄頁
umi g page login
- 在
config/config.js
中配置/login
甥雕,并守衛(wèi)/about
routes: [ { path: "/login", component: "./login" }, { path: "/about", component: "./about", Routes: ["./routes/PriviteRoute.js"] //路由守衛(wèi) }, ...... ]
- 引入ant design UI庫
npm i antd -S npm i umi-plugin-react -D
config/config.js
export default { //路由配置 routes: [...], //插件配置 plugins: [ [ "umi-plugin-react", { antd: true } ] ] }
- 使用時需要導(dǎo)入組件,因為是按需加載
import {Button} from 'antd' <Button type="primary">Primary</Button>
引入dva
-
dva
主要是軟件分層的概念-
Page
負責與用戶直接交互:渲染頁面着饥、接收用戶的操作輸入犀农,側(cè)重于展示型和交互邏輯; -
Model
負責處理業(yè)務(wù)邏輯宰掉,可以理解成一個維護頁面數(shù)據(jù)狀態(tài)的對象呵哨,為Page
做數(shù)據(jù)、狀態(tài)的讀寫等操作轨奄;
export default { namespace: 'goods', // model的命名空間孟害,區(qū)分多個model state: [], //初始狀態(tài) effects: { //異步操作 }, reducers: {} }
-
Service
主要負責與HTTP做接口對接,跟后端做數(shù)據(jù)交互挪拟,讀寫數(shù)據(jù)挨务;
-
-
dva
已經(jīng)融合進了umi
,在config/config.js
中打開dva的開關(guān)plugins: [ [ "umi-plugin-react", { antd: true, dva: true } ] ]
基本用法
umi g page goods
npm i axios -S
- 路由配置:
config/config.js
routes: [ { path: "/goods", component: "./goods" }, ]
- 在項目根目錄下創(chuàng)建
mock/goods.js
玉组,模擬接收請求谎柄,響應(yīng)數(shù)據(jù)let data = [ { title: '單頁面' }, { title: '管理項目' }, ], export default { //method url "get /api/goods": function(req, res) { setTimeout(()=>{ res.json({result: data}) }, 1000); } }
-
Model
:src/models/goods.js
import axios from 'axios' //調(diào)接口的邏輯應(yīng)該放在 Service 層 function getGoods() { return axios.get('/api/goods') } export default { namespace: 'goods', // 命名空間,如果省略惯雳,則以文件名作為命名空間 state: [], effects: { *getList(action, {call, put}) { //異步操作 //發(fā)起請求 const res = yield call(getGoods) //派發(fā)異步action: initGoods yield put({type:'initGoods', payload:res.data.result}) } }, reducers: { initGoods(state, action) { return action.payload }, addGood(state, action) { // 添加函數(shù)朝巫,返回一個新的state return [...state, {title: action.plyload.title}] } } }
-
pages/goods.js
import {connect} from 'dva' import React, {Component} from 'react' @connect( state=>({ goodList: state.goods, // 從指定命名空間內(nèi)獲取state loading: state.loading, // 通過loading命名空間獲取加載的狀態(tài) }), { getList: ()=>{ {type: 'goods/getList'} // action的type需要以命名空間為前綴,后跟reducer }, addGood: title=>({ type:'goods/addGood', payload: {title} }) } ) export default class extends Component { componentDidMount() { this.props.getList() //觸發(fā)事件石景,發(fā)起請求劈猿,獲取數(shù)據(jù) } 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> ) } }
升級路由守衛(wèi)
- 在項目根目錄下創(chuàng)建
mock/login.js
export 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.js
import axios from 'axios' import router from 'umi/router' //初始的state const initUserInfo = { token: "", name: "" } //調(diào)接口的邏輯應(yīng)該放在 Service 層 function login(data) { return axios.post('/api/login', data) } export default { namespace: 'login', state: initUserInfo, effects: { //異步操作 *login(action, {call, put}) { //發(fā)起請求 const res = yield call(login, action.payload) if(res.data.code===200) { //登錄成功 //派發(fā)異步action yield put({type:'init', payload:res.data.data}) //登錄成功,跳轉(zhuǎn)去首頁 router.push('/') } else { console.log("登陸失敗!") } } }, reducers: { init(state, action) { return action.payload } } }
-
pages/login.js
import {connect} from 'dva' import React, {Component} from 'react' @connect() export default class extends Component { onSubmit() { //派發(fā)action潮孽,發(fā)起請求 this.props.dispatch({type:"login/login", payload:{username:"xxx",password:"xxx"}}) } render() { return ( <div> <button onClick={onSubmit}>登錄</button> </div> ) } }
-
routes/PriviteRoute.js
import React,{Component} from 'react' import Redirect from 'umi/redirect' import {connect} from 'dva' @connect( state => ({token:state.login.token}) //從指定命名空間內(nèi)獲取state ) export default class extends Component { render() { if(this.props.token) { //登錄成功時揪荣,顯示子路由的頁面組件 return <div>{this.props.children}</div> } //沒有登錄時,重定向到登錄頁 return <Redirect to="/login" /> } }