前端實戰(zhàn):React 多頁簽緩存處理
關注并將「趣談前端」設為星標
每天定時分享技術干貨/優(yōu)秀開源/技術思維
1. 背景
ant design pro v2的項目需要多頁簽顯示頁面而且還需要頁面能夠緩存下來。
多頁面展示 不同數(shù)據(jù)會對應不同的詳情頁面 代碼里面的路由跳轉(zhuǎn)可以正常進行頁面切換 頁簽可以關閉

2. 主要設計:
這里主要是考慮多頁面+緩存問題。
這里是借用了ant tabs標簽的緩存作用。tabs的多頁面只是顯示和隱藏,組件本身還存在。
路由這一塊,其實路由目前的基本使用頁面只是會渲染當前匹配成功的路由節(jié)點。
這里鉆了一個空子。Route在沒有標注path的情況下會作為沒有匹配路徑的url進行渲染,作為沒有匹配任何路由的情況下渲染節(jié)點。
因為我們通過路由path去匹配是不行,只有使用不帶path的情況下渲染節(jié)點,而且不能使用switch。
而且不使用path的情況下,路由對應渲染的組件則不能依賴于react-router的機制來自動匹配,頁面的渲染就需要我們進行手動處理。因為打開多少個頁面就會有多少個no path的Route的節(jié)點。
3. 講解
回到具體的實現(xiàn):BasicLayout.js
3.1 原先的版本
const layout = (
<Layout>
{isTop && !isMobile ? null : (
<SiderMenu
logo={logo}
theme={navTheme}
onCollapse={this.handleMenuCollapse}
menuData={menuData}
isMobile={isMobile}
{...this.props}
/>
)}
<Layout
style={{
...this.getLayoutStyle(),
minHeight: '100vh',
}}
>
<Header
menuData={menuData}
handleMenuCollapse={this.handleMenuCollapse}
logo={logo}
isMobile={isMobile}
{...this.props}
/>
<Content className={styles.content} style={contentStyle}>
<Authorized authority={routerConfig} noMatch={<Exception403 />}>
{children}
</Authorized>
</Content>
<Footer />
</Layout>
</Layout>
);
以前的版本里直接渲染對應children。這里具體ant deisgn pro,或者umijs做了對應的處理,我沒具體去看?,F(xiàn)在的話我們不會用這個版本去做。
3.2 現(xiàn)在的版本
3.2.1 layout
const layout = (
<Layout>
{isTop && !isMobile ? null : (
<SiderMenu
logo={logo}
theme={navTheme}
onCollapse={this.handleMenuCollapse}
menuData={menuData}
isMobile={isMobile}
{...this.props}
// onHandlePage={this.onHandlePage}
/>
)}
<Layout
style={{
...this.getLayoutStyle(),
minHeight: '100vh',
}}
>
<Header
menuData={menuData}
handleMenuCollapse={this.handleMenuCollapse}
logo={logo}
isMobile={isMobile}
{...this.props}
/>
<Content className={styles.content} style={contentStyle}>
<div className={styles.contentBox}>
<div className={styles.contentTabUrlBox}>
<div className={styles.contentTabUrl}>
<Tabs
activeKey={activeKey}
onChange={this.onChange}
tabBarExtraContent={operations}
type="editable-card"
tabBarStyle={{ background: '#fff' }}
tabPosition="top"
tabBarGutter={-1}
onEdit={this.onEdit}
hideAdd
>
{listenRouterState.map(item => (
<TabPane tab={item.tab} key={item.key} closable={item.closable}>
<RouterContext.Provider value={customerMatchs}>
<Route key={item.key} component={item.content} exact />
</RouterContext.Provider>
{/* {item.component()} */}
</TabPane>
))}
</Tabs>
<Footer />
</div>
</div>
</div>
</Content>
</Layout>
</Layout>
);
核心的代碼是
<Tabs
activeKey={activeKey}
onChange={this.onChange}
tabBarExtraContent={operations}
type="editable-card"
tabBarStyle={{ background: '#fff' }}
tabPosition="top"
tabBarGutter={-1}
onEdit={this.onEdit}
hideAdd
>
{listenRouterState.map(item => (
<TabPane tab={item.tab} key={item.key} closable={item.closable}>
<RouterContext.Provider value={customerMatchs}>
<Route key={item.key} component={item.content} exact />
</RouterContext.Provider>
</TabPane>
))}
</Tabs>
這里使用的是tab + 路由 with no path的方式?,F(xiàn)在我們需要將組件和path進行關聯(lián)起來。因為沒有使用路由匹配了。代碼里的listenRouterState就是我們打開頁面的key和對應組件的mapping關系。
這里path的處理我使用的是路由監(jiān)控,因為是對于整個系統(tǒng)的頁面多頁簽,所以我使用了路由監(jiān)控。
3.2.2 componentDidMount路由監(jiān)控
UN_LISTTEN = history.listen(route => {
const { listenRouterState, listenRouterKey, customerMatchs } = this.state;
let replaceRouter = routerArray.filter(itemRoute =>
pathToRegexp(itemRoute.key || '').test(route.pathname),
)[0];
let currentKey = '';
if (replaceRouter && replaceRouter.isOnlyOnePage) {
currentKey = route.pathname;
} else {
currentKey = route.pathname + this.parseQueryString(route.search);
}
if (!listenRouterKey.includes(currentKey)) {
if (!replaceRouter) {
replaceRouter = routerArray.filter(itemroute => itemroute.key === '/404')?.[0];
this.setState({
listenRouterState: [
...listenRouterState,
{ ...replaceRouter, key: currentKey, tab: '404' },
],
activeKey: currentKey,
listenRouterKey: [...listenRouterKey, currentKey],
});
} else {
const match = matchPath(route.pathname, { path: replaceRouter.key });
this.setState({
listenRouterState: [
...listenRouterState,
{
...replaceRouter,
key: currentKey,
tab:
this.getPageTitle(route.pathname, breadcrumbNameMap) +
this.getDetailPagePrimaryId(route, match),
},
],
activeKey: currentKey,
listenRouterKey: [...listenRouterKey, currentKey],
customerMatchs: [...customerMatchs, { key: currentKey, match }],
});
}
}
this.setState({
activeKey: currentKey,
});
});
}
3.2.2.1 主要介紹
這里主要是在做什么,監(jiān)控路由然后進行路由匹配,獲取對應的組件。先介紹一下這里面用到的一些state變量
listenRouterState:打開頁面數(shù)據(jù)對象,也是在layout渲染的數(shù)組,存儲了pathname和component的mapping關系 activeKey:當前打開的頁面key listenRouterKey:listenRouterState對象key屬性的數(shù)組集合,用于一些數(shù)據(jù)判斷。 customerMatchs:適配match,這里可以先不管,因為這個是服務于下面實際問題的。
這里的主要邏輯就是,監(jiān)控路由,判斷路由是否已經(jīng)打開,如果已經(jīng)打開就不會重新打開。這里的key是全路徑,是加上查詢參數(shù)的。如下面的這個地址:

3.2.2.2 pathname 和 key的區(qū)別
但是匹配組件內(nèi)容不能使用這個進行匹配的,還是需要使用pathname進行匹配的。還是先看一下具體路由監(jiān)控的到的route數(shù)據(jù)是什么?

所以我們上面說的key:是pathname + query。這里要分清key和pathname的區(qū)別,因為pathname是用來匹配獲取組件的,key是為了進行多個詳情頁面的區(qū)分,如果不是全路徑是沒有辦法區(qū)分不同詳情頁面的。
當然我們這個pathname是比較好匹配的,假如是下面的這種,下面的路由對應的是:
{
path: '/List/:title/table-match-detail',
hideInMenu: true,
name: 'detail',
code: 'list_tableDetail_page',
component: './List/MatchDetail',
},

3.2.2.3 如何將pathname和路由正確的匹配
pathToRegexp(itemRoute.key || '').test(route.pathname)
針對路由的匹配,因為有match參數(shù)的存在,所以這里我用的是pathToRegexp,可以很好的解決這個問題。
3.2.2.4 listenRouterState的邏輯判斷
if (!listenRouterKey.includes(currentKey)) {
if (!replaceRouter) {
replaceRouter = routerArray.filter(itemroute => itemroute.key === '/404')?.[0];
this.setState({
listenRouterState: [
...listenRouterState,
{ ...replaceRouter, key: currentKey, tab: '404' },
],
activeKey: currentKey,
listenRouterKey: [...listenRouterKey, currentKey],
});
} else {
const match = matchPath(route.pathname, { path: replaceRouter.key });
this.setState({
listenRouterState: [
...listenRouterState,
{
...replaceRouter,
key: currentKey,
tab:
this.getPageTitle(route.pathname, breadcrumbNameMap) +
this.getDetailPagePrimaryId(route, match),
},
],
activeKey: currentKey,
listenRouterKey: [...listenRouterKey, currentKey],
customerMatchs: [...customerMatchs, { key: currentKey, match }],
});
}
}
this.setState({
activeKey: currentKey,
});
這里做的就是對當前的key進行判斷,如果不存在,那就是頁面沒有打開,則添加新的數(shù)據(jù)進行,如果已經(jīng)打開,則跳轉(zhuǎn)到新的頁面,如果匹配路徑獲取組件沒有成功,則跳轉(zhuǎn)到404。
3.2.2.5 不同詳情頁面的title如何處理
因為詳情頁有多個,但是tab的標簽頁title要不同

tab: this.getPageTitle(route.pathname, breadcrumbNameMap)
+ this.getDetailPagePrimaryId(route, match),
getPageTitle主要用的還是之前的邏輯,主要說明一下getDetailPagePrimaryId
getDetailPagePrimaryId = (route, match) => {
const detailPageIdEnum = ['id', 'title', 'activityNo'];
let titleValue = '';
// 處理query類型
Object.keys(route.query).forEach(item => {
if (detailPageIdEnum.includes(item) && !titleValue) {
titleValue = route.query[item];
}
});
// 處理match
Object.keys(match.params).forEach(item => {
if (detailPageIdEnum.includes(item) && !titleValue) {
titleValue = match.params[item];
}
});
return titleValue ? ` - ${titleValue}` : '';
};
這里的邏輯主要是從query和match中間變量值,只要匹配成功,就會返回匹配的數(shù)據(jù)值。detailPageIdEnum主要是系統(tǒng)層級可能對應的變量名稱
比如query中的title

或者match中title 【當然這里的match是有問題的,在下面實際問題的時候會說明一下】

3.2.3 其他代碼
其他的就不是核心代碼,基本分為兩塊,一塊是初始化處理,另外一塊是table的menu的處理
3.2.3.1 初始化處理
constructor(props) {
super(props);
this.getPageTitle = memoizeOne(this.getPageTitle);
this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
routerArray = this.updateTree(props.route.routes);
const homeRouter = routerArray.filter(itemroute => itemroute.key === '/')[0];
this.state = {
listenRouterState: [{ ...homeRouter, key: '/', tab: '首頁', closable: false }],
listenRouterKey: ['/'],
activeKey: '/',
customerMatchs: [],
};
}
主要就是會將routers的數(shù)據(jù)做一個基礎處理,第二個就是添加一個首頁在tab標簽頁面。
3.2.3.2 tab menu的處理
主要處理,關閉打開的頁簽,關閉當前頁面,關閉全部頁面,關閉其他頁面

這一塊比較簡單,就不介紹了。
onClickHover = e => {
// message.info(`Click on item ${key}`);
const { key } = e;
const { activeKey, listenRouterState, listenRouterKey, routeKey } = this.state;
if (key === '1') {
this.setState({
activeKey: routeKey,
listenRouterState: listenRouterState.filter(
v => v.key !== activeKey || v.key === routeKey || !v.closable,
),
listenRouterKey: listenRouterKey.filter(
v => v !== activeKey || v === routeKey || !v.closable,
),
});
} else if (key === '2') {
this.setState({
activeKey,
listenRouterState: listenRouterState.filter(
v => v.key === activeKey || v.key === routeKey || !v.closable,
),
listenRouterKey: listenRouterKey.filter(
v => v === activeKey || v === routeKey || v === '/',
),
customerMatchs: listenRouterState.filter(
v => v.key === activeKey || v.key === routeKey || !v.closable,
),
});
} else if (key === '3') {
this.setState({
activeKey: '/',
listenRouterState: listenRouterState.filter(v => v.key === routeKey || !v.closable),
listenRouterKey: listenRouterKey.filter(v => v === routeKey || v === '/'),
customerMatchs: listenRouterState.filter(v => v.key === routeKey || !v.closable),
});
}
};
onEdit = (targetKey, action) => {
this[action](targetKey);
};
remove = targetKey => {
const { activeKey, listenRouterState } = this.state;
let newActiviKey = activeKey;
let lastIndex;
listenRouterState.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const tabList = [];
const tabListKey = [];
listenRouterState.forEach(pane => {
if (pane.key !== targetKey) {
tabList.push(pane);
tabListKey.push(pane.key);
}
});
if (lastIndex >= 0 && activeKey === targetKey) {
newActiviKey = tabList[lastIndex].key;
}
router.push(newActiviKey);
this.setState({
listenRouterState: tabList,
activeKey: newActiviKey,
listenRouterKey: tabListKey,
});
};4. redux應用
4.1 問題
redux在多頁簽的頁面里會存在問題,比如以下兩種情況:
詳情頁面:因為詳情頁面可以打開多個,但是都是公用同一個redux。 多列表頁面共用同一個model


4.2 解決思路
動態(tài)路由,手動注冊model實現(xiàn),但是在ant design pro內(nèi)部不是很好實現(xiàn)。提了一個官方issue問題:ant design pro多頁簽緩存問題 不使用redux,大部分頁面是不需要使用redux。只通過頁面調(diào)用接口請求,數(shù)據(jù)存儲都放在組件state中去。 使用redux,同一個model通過關鍵key進行數(shù)據(jù)區(qū)分
4.3 公用同一個model的具體操作



4.3.1 hoc
為什么使用hoc,這里是為了不影響業(yè)務頁面做修改,將修改的地方放在hoc統(tǒng)一處理掉。這里主要是兩個問題:
dispatch需要將當前primaryKey傳到redux,因為對應的數(shù)據(jù)更新需要放到對應的primarykey對應的state里面去。 業(yè)務組件使用state數(shù)據(jù)的時候,需要將當前primaryKey對應的數(shù)據(jù)傳入到props里面
/**
* 高階函數(shù)
* @param {*} namespace
* @param {*} primaryKey
*/
function wrapperWithSubscription(namespace, primaryKey) {
// eslint-disable-next-line no-use-before-define
const modelNameSpace = namespace;
const modelPrimaryKey = primaryKey;
return function withSubscription(WrappedComponent) {
// ...并返回另一個組件...
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
currentProps: Object.assign({}, props.location),
initData: {},
};
}
componentWillMount() {
const { dispatch, location } = this.props;
dispatch({
type: `${modelNameSpace}/initData`,
payload: {
primaryKey: location.query[modelPrimaryKey],
},
});
dispatch({
type: `${modelNameSpace}/getExample`,
payload: {},
callback: result => {
this.setState({
initData: result,
});
},
});
}
componentWillUnmount() {
// 可以自定擴展如何消除當前primarykey對應的數(shù)據(jù)
// 一般情況下,前端業(yè)務組件會自己清除state的數(shù)據(jù)
}
wrapperDispatch = (dispatchPrams) => {
const {
dispatch,
} = this.props;
const { currentProps: { query } } = this.state;
dispatch({
...dispatchPrams,
primaryKey: query[modelPrimaryKey],
});
};
render() {
const {
initData,
currentProps: { query },
} = this.state;
const modelNameSpaceProps = {
// eslint-disable-next-line react/destructuring-assignment
[modelNameSpace]: this.props[modelNameSpace][query[modelPrimaryKey]] || initData,
};
return (
<WrappedComponent
{...this.props}
dispatch={this.wrapperDispatch}
{...modelNameSpaceProps}
/>
);
}
};
};
}
4.3.1.1 wrapperDispatch
其實頁面組件的dispatch會走當前的頁面,這里會統(tǒng)一將primaryKey傳入進去
wrapperDispatch = (dispatchPrams) => {
const {
dispatch,
} = this.props;
const { currentProps: { query } } = this.state;
dispatch({
...dispatchPrams,
primaryKey: query[modelPrimaryKey],
});
};
4.3.1.2 render
render函數(shù)會處理redux的state,將對應當前頁面的數(shù)據(jù)傳回,頁面組件還按照之前直接訪問,下面的detail就是當前頁面對應的model的namespace名稱
const { history, location, detail } = this.props;
render() {
const {
initData,
currentProps: { query },
} = this.state;
const modelNameSpaceProps = {
// eslint-disable-next-line react/destructuring-assignment
[modelNameSpace]: this.props[modelNameSpace][query[modelPrimaryKey]] || initData,
};
return (
<WrappedComponent
{...this.props}
dispatch={this.wrapperDispatch}
{...modelNameSpaceProps}
/>
);
}
4.3.2 model文件會做哪些改變呢?
4.3.2.1 初始state的不同
const initDataExample = {
data: {
name: '',
},
};
export default {
namespace: 'detail',
state: {},
*****
}
現(xiàn)在我們的state里面初始是沒有值,因為state的一級屬性值使我們頁面對應的primaryKey。
我們會定義一個基礎結構,initDataExample。用于在組件初始化的時候使用這個初始值添加到state對應的primaryKey。
4.3.2.2 新增兩個服務于hoc方法effects
*getExample({ callback }) {
if (callback) callback({ ...initDataExample });
},
*initData({ payload }, { put }) {
yield put({
type: 'init',
payload: {
[payload.primaryKey]: {
...initDataExample,
},
},
});
},
getExample:獲取初始數(shù)據(jù)結構
initData:初始化數(shù)據(jù)結構
getExample呢,是因為hoc內(nèi)部初始化函數(shù)的時候,state是異步的,不會直接在頁面render的時候直接初始成功,所以這里的getExample是為了在state還沒有更新的情況下,使用初始函數(shù)去拿到值,傳遞給組件。
hoc
componentWillMount() {
const { dispatch, location } = this.props;
dispatch({
type: `${modelNameSpace}/initData`,
payload: {
primaryKey: location.query.title,
},
});
dispatch({
type: `${modelNameSpace}/getExample`,
payload: {},
callback: result => {
this.setState({
initData: result,
});
},
});
}
*****
render() {
const {
initData,
currentProps: { query },
} = this.state;
const modelNameSpaceProps = {
// eslint-disable-next-line react/destructuring-assignment
[modelNameSpace]: this.props[modelNameSpace][query[modelPrimaryKey]] || initData,
};
return (
<WrappedComponent
{...this.props}
dispatch={this.wrapperDispatch}
{...modelNameSpaceProps}
/>
);
}
4.3.2.3 更新state的不同
*fetch({ payload, primaryKey }, { put, select }) {
const currentPrimaryKeyState = yield select(state => state.detail[primaryKey]);
yield put({
type: 'save',
payload: updateWrapperModel('data', payload, primaryKey, currentPrimaryKeyState),
});
}
現(xiàn)在更新數(shù)據(jù),需要定位到更新到哪一個primaryKey。所以這里提供了一個函數(shù):更新的時候傳入對應的值,然后更新對應primaryKey下的具體的key / value
/**
* updateWrapperModel
* @param {*} updateKey
* @param {*} updateValue
* @param {*} primaryKey
* @param {*} currentPrimaryKeyState
*/
export function updateWrapperModel(updateKey, updateValue, primaryKey, currentPrimaryKeyState) {
return {
[primaryKey]: {
...currentPrimaryKeyState,
[updateKey]: updateValue,
},
};
}4.4 業(yè)務組件的修改
我們使用hoc就是為了盡量少的減少業(yè)務組件的改動。
hoc內(nèi)部是一個統(tǒng)一的函數(shù)處理,所以hoc是不知道具體當前業(yè)務組件對應的modelspace是什么,以及當前路由下對應的primaryKey的key是什么,因為有的頁面可能是id,有的頁面取得是title。所以modelNamespace和primaryKey需要傳入到hoc。
業(yè)務組件只需要添加下面的一行代碼進行hoc的傳遞就可以了。
@withSubscription('detail', 'title')

高階函數(shù)會接受這兩個值使用

5. 實際問題
5.1 控制是否使用多頁簽
這里其實是想可以控制部分頁面不需要根據(jù)key進行判斷,而是根據(jù)pathname進行判斷就好了。
解決的代碼提交是https://github.com/rodchen-king/ant-design-pro-v2/commit/86430c03d3c13f2aed1090c71fb96cf95f195853
路由需要進行只會存在一個頁面標示:

路由監(jiān)控的地方,判斷當前路由如果是isOnlyOnePage,則采用pathname進行key標示

5.2 支持路由match數(shù)據(jù)
這里其實就是為了處理參數(shù)在pathname里面的參數(shù)

這里主要的問題是采用目前的這種方式,match的數(shù)據(jù)是不會正常返回的
所以這里對于項目中用到的props.match都需要單獨處理一下。
所以在BasicLayout里面做了單獨的處理,就是上面說的customerMatch。其實是為了處理這個問題的。
5.2.1 具體實現(xiàn)
整體的思想和redux應用里面有類似的思路。不同的是
監(jiān)控路由匹配的時候會處理得到當前路由的match值。
const match = matchPath(route.pathname, { path: replaceRouter.key });
這里其實是處理當前url和匹配的路由pathname處理得到一個match的結果。

至于matchPath這個方法,其實是我從react-router源碼里面復制出來的:
import pathToRegexp from 'path-to-regexp';
const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;
function compilePath(path, options) {
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
if (pathCache[path]) return pathCache[path];
const keys = [];
const regexp = pathToRegexp(path, keys, options);
const result = { regexp, keys };
if (cacheCount < cacheLimit) {
pathCache[path] = result;
// eslint-disable-next-line no-plusplus
cacheCount++;
}
return result;
}
/**
* Public API for matching a URL pathname to a path.
*/
function matchPath(pathname, options = {}) {
if (typeof options === 'string' || Array.isArray(options)) {
// eslint-disable-next-line no-param-reassign
options = { path: options };
}
const { path, exact = false, strict = false, sensitive = false } = options;
const paths = [].concat(path);
// eslint-disable-next-line no-shadow
return paths.reduce((matched, path) => {
if (!path && path !== '') return null;
if (matched) return matched;
const { regexp, keys } = compilePath(path, {
end: exact,
strict,
sensitive,
});
const match = regexp.exec(pathname);
if (!match) return null;
const [url, ...values] = match;
const isExact = pathname === url;
if (exact && !isExact) return null;
return {
path, // the path used to match
url: path === '/' && url === '' ? '/' : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
// eslint-disable-next-line no-param-reassign
memo[key.name] = values[index];
return memo;
}, {}),
};
}, null);
}
export default matchPath;
然后不同頁面的match值會存儲在customerMatchs。然后通過context進行數(shù)據(jù)傳遞。
<RouterContext.Provider value={customerMatchs}>
<Route key={item.key} component={item.content} exact />
</RouterContext.Provider>
HOC函數(shù)進行消費withRouterMath
import React from 'react';
import { RouterContext } from '@/layouts/BasicLayout';
/**
* 高階函數(shù): 適配match
*/
function withRouterMath() {
// eslint-disable-next-line no-use-before-define
return function withSubscription(WrappedComponent) {
// ...并返回另一個組件...
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
currentLocation: Object.assign({}, props.location),
};
}
getMatch = value => {
const {
currentLocation: { pathname },
} = this.state;
const returnValue = value.filter(item => item.key === pathname);
if (returnValue.length) {
return returnValue[0].match;
}
return {};
};
render() {
return (
<RouterContext.Consumer>
{_value => <WrappedComponent {...this.props} match={this.getMatch(_value)} />}
</RouterContext.Consumer>
);
}
};
};
}
export default withRouterMath;
使用的時候
@withRouterMatth()
@withSubscription('detail', 'title')
class ListDetail extends React.PureComponent {
componentDidMount() {
const { match, dispatch } = this.props;
dispatch({
type: 'detail/fetch',
payload: {
name: match.params.title,
},
});
}
}
這樣頁面就可以正常使用match,和原先的location一樣獲取值,然后系統(tǒng)層級也會匹配query和match的數(shù)據(jù)進行primarykey處理。

5.3 業(yè)務頁面不是由單個的參數(shù)進行唯一表示,而是多個業(yè)務參數(shù)進行唯一表示
前面說的都是單個primarykey作為唯一頁面標示的,但是可能部分代碼存在很復雜的情況。
舉個例子,存在一個業(yè)務組件既是詳情頁面,也是新增頁面,而且業(yè)務上還存在多個。
類似營銷活動:為了更好的說明,我在單獨加一個字段,templateName
滿減活動【templateName:滿減】
activityType:1; 詳情的時候會有一個activityNo 新增的時候則沒有activityNo
滿折活動【templateName:滿折】
activityType:2: 詳情的時候會有一個activityNo, 新增的時候則沒有activityNo
5.3.1 具體實現(xiàn)
https://github.com/rodchen-king/ant-design-pro-v2/commit/d0ecfd2e795cb90837b0ed94de5f4ad13012af31
這里主要是支持多個參數(shù):
修改BasicLayout.js

因為可能參數(shù)當中涉及到中文,所以判斷key的邏輯用了decodeURIComponent方法解析。
第二個在getDetailPagePrimaryId,這里加上這個是為了適配新的查詢參數(shù)。
修改wrapperWithSubscription

主要是兼容多個參數(shù)的情況,傳進來的是一個數(shù)組。
調(diào)用的方式參考新增的業(yè)務Maket組件的調(diào)用方式
@withSubscription('detail', ['activityNo', 'templateName', 'activityType'])

6. 優(yōu)化
6.1 dispatch.then的使用
代碼:https://github.com/rodchen-king/ant-design-pro-v2/commit/5f160db67aaad31cb1ac04d4d01a66a1fc6d0582
開發(fā)過程中存在dispatch().then的方式:
所以這里支持也要優(yōu)化一下:
wrapperDispatch = dispatchPrams => {
const { dispatch } = this.props;
const { primaryKeyValue } = this.state;
dispatch({
...dispatchPrams,
primaryKey: primaryKeyValue,
});
};
6.2 redux-state更新多個參數(shù)
代碼:https://github.com/rodchen-king/ant-design-pro-v2/commit/5f160db67aaad31cb1ac04d4d01a66a1fc6d0582
/**
* model相關的處理函數(shù)
*/
/**
* updateWrapperModel
* @param {*} updateStateObject 要更新state的健值對
* @param {*} primaryKey 當前頁面對應的primaryKey
* @param {*} currentPrimaryKeyState primaryKey對應的數(shù)據(jù)源
*/
export function updateWrapperModel(updateStateObject, primaryKey, currentPrimaryKeyState) {
return {
[primaryKey]: {
...currentPrimaryKeyState,
...updateStateObject,
},
};
}
7. Ant design pro v5 如何做?
https://procomponents.ant.design/components/layout
因為最新的v5 菜單已經(jīng)采用ProLayout作為布局。所以這里一種方式是利用配置項目的childRender

另一種方式則是注釋這里的代碼,然后重新寫以前版本的BasicLayout.js

創(chuàng)作不易,加個點贊、在看 支持一下哦!
