前言
用的是umi 2.x
,寫起來挺舒服;
順帶完善了上一版本後臺的一些細節問題,功能等
umijs
類似create-react-app
, 也是一套方案的集合體,亮點很多.可以具體官網去看
- 宣告式的路由(
nuxtjs
既視感) dva(基於redux+redux-saga的封裝方案)
:寫起來有vuex
的感覺;
主要記錄我在過程中遇到的問題及解決的姿勢,技術棧 antd 3.11.x
+ umi 2.x
+ react 16.7
問題彙總及解決姿勢
moment的一些用法及antd 日期元件的細節
關於moment
為什麼說另類..就是原生日期API
結合moment
,因為我們介面需要傳遞時間戳,而是不帶毫秒級的;
而且時間必須為當天的凌晨00:00:00
開始,結束時間到操作的此刻(直接new Date().getTime()
就是此刻);
// 會直接返回你設定時間的時間戳new Date().setHours(0, 0, 0, 0)// 凌晨`00:00:00`moment(new Date().setHours(0, 0, 0, 0))// 近七天moment(new Date().setHours(0, 0, 0, 0) - 7 * 24 * 3600000)// 月初moment().startOf('month')複製程式碼
轉成unix stamp(伺服器常用的時間戳規格)
,呼叫moment().unix()
即可;
若是不控制到凌晨00:00:00
這種,
日期可以直接用moment
的add
方法往後推導,subtract
往前推導,支援日/周/月/年
antd
的日期元件
置空用null
是允許的,其他的話需要轉成moment
物件,控制元件獲取的值預設就是moment
物件
props.children
的改造,新增樣式亦或者事件!
在封裝一些元件的過程,我用了React.Fragment(<
來保證元件同級並列
>
<
/>
: 簡寫)
有些必須需要props.children
帶上一些屬性或者樣式來保證我想要的效果.
一開始無解, 因為Fragement簡寫的姿勢
沒法props
,那也就是說沒做寫成高階;
找了下官方文件,發現有這麼兩個API
:
- React.Children : 提供了幾個遍歷子元素(
React Element
)的方法,與常規陣列用法類似,只是引數不一樣 - React.cloneElement: 如名字所示,克隆子元素
這是上篇文章用到的部分內容,需要改造傳遞進來的按鈕,給新增樣式
// 構建// 克隆子元件並且新增自己要新增的特性const PropsBtn = React.Children.map(this.props.children, child =>
React.cloneElement(child, {
style: {
marginLeft: 8,
},
}));
// 渲染{PropsBtn ? <
>
{PropsBtn
}<
/>
: null
}複製程式碼
用memoize-one
來改善效能
可以快取同樣引數的結果集,非常適用於遞迴這類的函式處理,大大減少計算的壓力;
也能用於React
這類,是否有必要重新setState
, 第二個引數支援比較,官方推薦用lodash
去深度比較
函式式元件內返回一個HOC
的元件
最簡單粗暴的方法就是用變數快取,然後直接返回元件,比如我這邊文章就用了;
React 折騰記 – (9) 基於Antd+react-router-breadcrumbs-hoc封裝一個小巧的麵包屑元件
umi 約定式基礎鑑權
在layouts
裡面分別寫對應的佈局,然後由一個鑑權元件去判定是否允許進入,比如
/src/layout/index.js
import React from 'react';
import withRouter from 'umi/withRouter';
// 鑑權元件, 我寫了webpack aliasimport Authorized from 'components/Authorized';
// 佈局元件import EnranceLayout from './EntranceLayout';
import AdminLayout from './AdminLayout';
// 中文地區時間轉換引入import moment from 'moment';
import 'moment/locale/zh-cn';
// 路由動效import {
TransitionGroup, CSSTransition
} from 'react-transition-group';
// 頁面標題import {
Helmet
} from 'react-helmet';
import {
getDocumentTitle
} from 'components/Sidebar/RouterTree';
moment.locale('zh-cn');
export default withRouter(props =>
{
const {
location: {
pathname
}, location,
} = props;
// 根據路由定址,再結合鑑權來判定是否允許進入,根據您自身的業務進行調整 if (pathname.indexOf('/entrance') === -1) {
if (pathname.indexOf('/editor') !== -1) {
return ( <
Authorized>
<
Helmet>
<
title>
{getDocumentTitle(pathname)
}<
/title>
<
/Helmet>
<
TransitionGroup>
<
CSSTransition key={location.key
} classNames="spread" timeout={1000
}>
{props.children
} <
/CSSTransition>
<
/TransitionGroup>
<
/Authorized>
);
} return ( <
AdminLayout>
<
Helmet>
<
title>
{getDocumentTitle(pathname)
}<
/title>
<
/Helmet>
<
TransitionGroup>
<
CSSTransition key={location.key
} classNames="spread" timeout={1000
}>
{props.children
} <
/CSSTransition>
<
/TransitionGroup>
<
/AdminLayout>
);
} return ( <
EnranceLayout>
<
TransitionGroup>
<
CSSTransition key={location.key
} classNames="spread" timeout={1000
}>
{props.children
} <
/CSSTransition>
<
/TransitionGroup>
<
/EnranceLayout>
);
});
複製程式碼
model的規劃
全域性的放在src/models
目錄,其他的page
級別推薦直接model.js
,官方說會自下往上尋找;
是根據namespace
來區分的..不允許存在同名的namespace
;
若是要開啟umi
的model
動態引入, page
級別不允許呼叫其他page
的model
,不然會報錯,初始化找不到的!!!
所以全域性性放在全域性更為合適,當然你不需要動態引入的話,頁面間跨調是允許的..我目前是這麼做;
pages
目錄下的檔案或者目錄不自動生成對應可訪問的page
預設在page
目錄下,除了部分特殊的檔案(比如官方自己過濾的models
),都會自動產生可訪問的頁面,
也就是說檔案會被當做路由元件;
遮蔽的話, 開啟專案的配置檔案.umirc.js
const path = require('path');
// ref: https://umijs.org/config/export default {
plugins: [ // ref: https://umijs.org/plugin/umi-plugin-react.html [ 'umi-plugin-react', {
antd: true, // 預設引入antd dva: {
// 啟用引入dva immer: true, dynamicImport: false, // models 動態引入關閉 hmr: true,
}, dynamicImport: false, // 元件切割動態引入 title: '聲兮後臺管理系統', dll: true, routes: {
// 此處用正則忽略不想產生路徑的檔案或者目錄!!! exclude: [/model\.js/, /models\//, /services(\/|\.js)?/, /components\//],
}, hardSource: true, locale: {
},
}, ], ],
};
複製程式碼
umi配置開發的反向代理及目錄的alias
const path = require('path');
// ref: https://umijs.org/config/export default {
plugins: [ alias: {
'@': path.resolve(__dirname, './src'), models: path.resolve(__dirname, './src/models'), components: path.resolve(__dirname, './src/components'), utils: path.resolve(__dirname, './src/utils'), services: path.resolve(__dirname, './src/services'), assets: path.resolve(__dirname, './src/assets'),
}, proxy: {
'/api/web': {
target: 'http://stagapi.xxxx.com', changeOrigin: true, secure: false, // pathRewrite: {
'^/api': '/'
},
},
},
};
複製程式碼
如果在dva
的dispatch
後setState
// dispatch返回的是一個promise,直接then即可,// 傳入callback 姿勢無效(對於setState)this.props.dispatch({
type: 'appuser/batchItem', payload: {
batchType: 2, userIdList: userIdList,
},
}) .then(res =>
{
this.setState({
selectedRowKeys: [],
});
message.success(`批量封號,操作成功!`);
});
}
}複製程式碼
如何在umi這種加入preloading
就是react程式碼沒載入之前,顯示的區域塊,
目前的做法就是自定義模板檔案,放在react渲染塊內部,在解析程式碼渲染完畢會被替換掉
效果如下
src/pages/document.ejs
<
!doctype html>
<
html>
<
head>
<
meta charset="utf-8" />
<
meta name="viewport" content="width=device-width, initial-scale=1.0">
<
title>
xx管理後臺<
/title>
<
style>
.preloadLoading{
position:fixed;
left:0;
top:0;
width:100%;
height:100%;
display:flex;
justify-content:center;
align-items:center;
} @-webkit-keyframes square-animation {
0% {
left: 0;
top: 0;
} 10.5% {
left: 0;
top: 0;
} 12.5% {
left: 32px;
top: 0;
} 23% {
left: 32px;
top: 0;
} 25% {
left: 64px;
top: 0;
} 35.5% {
left: 64px;
top: 0;
} 37.5% {
left: 64px;
top: 32px;
} 48% {
left: 64px;
top: 32px;
} 50% {
left: 32px;
top: 32px;
} 60.5% {
left: 32px;
top: 32px;
} 62.5% {
left: 32px;
top: 64px;
} 73% {
left: 32px;
top: 64px;
} 75% {
left: 0;
top: 64px;
} 85.5% {
left: 0;
top: 64px;
} 87.5% {
left: 0;
top: 32px;
} 98% {
left: 0;
top: 32px;
} 100% {
left: 0;
top: 0;
}
}@keyframes square-animation {
0% {
left: 0;
top: 0;
} 10.5% {
left: 0;
top: 0;
} 12.5% {
left: 32px;
top: 0;
} 23% {
left: 32px;
top: 0;
} 25% {
left: 64px;
top: 0;
} 35.5% {
left: 64px;
top: 0;
} 37.5% {
left: 64px;
top: 32px;
} 48% {
left: 64px;
top: 32px;
} 50% {
left: 32px;
top: 32px;
} 60.5% {
left: 32px;
top: 32px;
} 62.5% {
left: 32px;
top: 64px;
} 73% {
left: 32px;
top: 64px;
} 75% {
left: 0;
top: 64px;
} 85.5% {
left: 0;
top: 64px;
} 87.5% {
left: 0;
top: 32px;
} 98% {
left: 0;
top: 32px;
} 100% {
left: 0;
top: 0;
}
}@-webkit-keyframes hue-rotate {
0% {
-webkit-filter: hue-rotate(0deg);
filter: hue-rotate(0deg);
} 100% {
-webkit-filter: hue-rotate(360deg);
filter: hue-rotate(360deg);
}
}@keyframes hue-rotate {
0% {
-webkit-filter: hue-rotate(0deg);
filter: hue-rotate(0deg);
} 100% {
-webkit-filter: hue-rotate(360deg);
filter: hue-rotate(360deg);
}
}.loading {
position: relative;
width: 96px;
height: 96px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: hue-rotate 10s linear infinite both;
animation: hue-rotate 10s linear infinite both;
}.loading__square {
position: absolute;
top: 0;
left: 0;
width: 28px;
height: 28px;
margin: 2px;
border-radius: 2px;
background: #07a;
background-image: -webkit-linear-gradient(45deg, #fa0 40%, #0c9 60%);
background-image: linear-gradient(45deg, #fa0 40%, #0c9 60%);
background-image: -moz-linear-gradient(#fa0, #fa0);
background-size: cover;
background-position: center;
background-attachment: fixed;
-webkit-animation: square-animation 10s ease-in-out infinite both;
animation: square-animation 10s ease-in-out infinite both;
}.loading__square:nth-of-type(0) {
-webkit-animation-delay: 0s;
animation-delay: 0s;
}.loading__square:nth-of-type(1) {
-webkit-animation-delay: -1.42857s;
animation-delay: -1.42857s;
}.loading__square:nth-of-type(2) {
-webkit-animation-delay: -2.85714s;
animation-delay: -2.85714s;
}.loading__square:nth-of-type(3) {
-webkit-animation-delay: -4.28571s;
animation-delay: -4.28571s;
}.loading__square:nth-of-type(4) {
-webkit-animation-delay: -5.71429s;
animation-delay: -5.71429s;
}.loading__square:nth-of-type(5) {
-webkit-animation-delay: -7.14286s;
animation-delay: -7.14286s;
}.loading__square:nth-of-type(6) {
-webkit-animation-delay: -8.57143s;
animation-delay: -8.57143s;
}.loading__square:nth-of-type(7) {
-webkit-animation-delay: -10s;
animation-delay: -10s;
} <
/style>
<
/head>
<
body>
<
div id="root">
<
div class="preloadLoading">
<
div class='loading'>
<
div class='loading__square'>
<
/div>
<
div class='loading__square'>
<
/div>
<
div class='loading__square'>
<
/div>
<
div class='loading__square'>
<
/div>
<
div class='loading__square'>
<
/div>
<
div class='loading__square'>
<
/div>
<
div class='loading__square'>
<
/div>
<
/div>
<
/div>
<
/div>
<
/body>
<
/html>
複製程式碼
標題如何自動隨著路由表資訊改變
首先得自己維護一份靜態路由表,類似vue
或者react-router@3
那種,
結合@withRouter
拿到pathname
傳入到靜態路由表遍歷
(這裡就可以用到上面說的memoize-one來提高效能),
效果如下
姿勢如下
用react-helmet
來實現title
的替換,這貨不僅僅可以替換title
還能替換meta
這些
參考上面的問題 ==>
umi 約定式基礎鑑權 ,這裡就有用到
antd 選單欄隨著寬度自適應及風格變化
就是縮小的時候隱藏部分子選單,這個問題在我做側邊欄變水平的時候遇到.我縮小到ipad
的尺寸
會溢位,用了常規的法子,就正常了,就是style
那裡設定一個最大寬度或者寬度
至於風格變化是因為antd
內建了兩套風格
<
Menu style={{
maxWidth: '100%', flex: 1
}
} subMenuOpenDelay={0.3
} theme={theme ? 'dark' : 'light'
} mode={mode ? 'horizontal' : 'inline'
} openKeys={openKeys
} selectedKeys={selectedKeys
} onOpenChange={this.onOpenChange
} >
複製程式碼
當然Logo
元件這些肯定是你自己拿了狀態去變化的,還有包裹的父級區域的樣式
目前不做配置儲存,想做儲存的,寫在localStorage
不失為一個好法子,沒必要寫到資料庫,都是自己人用
效果如下
專案沒有用到antd pro
這個模板(太臃腫),自己寫比較實在
總結
有新的且覺得有點意義的問題我會陸續更新…繼續寫小程式的需求去..
有不對之處請留言,會及時修正..謝謝閱讀