<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 多頁簽緩存處理

          共 23816字,需瀏覽 48分鐘

           ·

          2021-07-22 01:27


          7c7851be5d99fd168a291ce067338799.webp

          暑不盡的盛夏

          67315cc672b34d0384318bb957911a28.webp


          關(guān)注并將「趣談前端」設(shè)為星標(biāo)

          每早08:30按時推送技術(shù)干貨/優(yōu)秀開源/技術(shù)思維

          1. 背景

          ant design pro v2的項目需要多頁簽顯示頁面而且還需要頁面能夠緩存下來。

          • 多頁面展示
          • 不同數(shù)據(jù)會對應(yīng)不同的詳情頁面
          • 代碼里面的路由跳轉(zhuǎn)可以正常進(jìn)行頁面切換
          • 頁簽可以關(guān)閉

          0a66af7b985b4b51e0f9a826f3fe0290.webp


          2. 主要設(shè)計:

          這里主要是考慮多頁面+緩存問題。

          這里是借用了ant tabs標(biāo)簽的緩存作用。tabs的多頁面只是顯示和隱藏,組件本身還存在。

          路由這一塊,其實路由目前的基本使用頁面只是會渲染當(dāng)前匹配成功的路由節(jié)點。

          這里鉆了一個空子。Route在沒有標(biāo)注path的情況下會作為沒有匹配路徑的url進(jìn)行渲染,作為沒有匹配任何路由的情況下渲染節(jié)點。

          因為我們通過路由path去匹配是不行,只有使用不帶path的情況下渲染節(jié)點,而且不能使用switch。

          而且不使用path的情況下,路由對應(yīng)渲染的組件則不能依賴于react-router的機(jī)制來自動匹配,頁面的渲染就需要我們進(jìn)行手動處理。因為打開多少個頁面就會有多少個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>

          ????);

          以前的版本里直接渲染對應(yīng)children。這里具體ant deisgn pro,或者umijs做了對應(yīng)的處理,我沒具體去看。現(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的方式。現(xiàn)在我們需要將組件和path進(jìn)行關(guān)聯(lián)起來。因為沒有使用路由匹配了。代碼里的listenRouterState就是我們打開頁面的key和對應(yīng)組件的mapping關(guān)系。

          這里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)控路由然后進(jìn)行路由匹配,獲取對應(yīng)的組件。先介紹一下這里面用到的一些state變量

          • listenRouterState:打開頁面數(shù)據(jù)對象,也是在layout渲染的數(shù)組,存儲了pathname和component的mapping關(guān)系
          • activeKey:當(dāng)前打開的頁面key
          • listenRouterKey:listenRouterState對象key屬性的數(shù)組集合,用于一些數(shù)據(jù)判斷。
          • customerMatchs:適配match,這里可以先不管,因為這個是服務(wù)于下面實際問題的。


          這里的主要邏輯就是,監(jiān)控路由,判斷路由是否已經(jīng)打開,如果已經(jīng)打開就不會重新打開。這里的key是全路徑,是加上查詢參數(shù)的。如下面的這個地址:

          b8540d51ead6ab0690e93d19dece07b2.webp

          3.2.2.2 pathname 和 key的區(qū)別

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

          ae19cc378c646b65bc286e86fc567f18.webp
          所以我們上面說的key:是pathname + query。這里要分清key和pathname的區(qū)別,因為pathname是用來匹配獲取組件的,key是為了進(jìn)行多個詳情頁面的區(qū)分,如果不是全路徑是沒有辦法區(qū)分不同詳情頁面的。

          當(dāng)然我們這個pathname是比較好匹配的,假如是下面的這種,下面的路由對應(yīng)的是:

          ??????????{
          ????????????path:?'/List/:title/table-match-detail',
          ????????????hideInMenu:?true,
          ????????????name:?'detail',
          ????????????code:?'list_tableDetail_page',
          ????????????component:?'./List/MatchDetail',
          ??????????},

          2b0103108afd90e4f782efeeacaee39f.webp


          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,
          ??????});

          這里做的就是對當(dāng)前的key進(jìn)行判斷,如果不存在,那就是頁面沒有打開,則添加新的數(shù)據(jù)進(jìn)行,如果已經(jīng)打開,則跳轉(zhuǎn)到新的頁面,如果匹配路徑獲取組件沒有成功,則跳轉(zhuǎn)到404。

          3.2.2.5 不同詳情頁面的title如何處理

          因為詳情頁有多個,但是tab的標(biāo)簽頁title要不同

          dd74882fe7f72576aee55eef0e90b433.webp
          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)層級可能對應(yīng)的變量名稱

          比如query中的title

          ad735999ac884bb69fef3af20923eac3.webp

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

          feb1110b99807f1d03d87dba798e8c68.webp


          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ù)做一個基礎(chǔ)處理,第二個就是添加一個首頁在tab標(biāo)簽頁面。

          3.2.3.2 tab menu的處理

          主要處理,關(guān)閉打開的頁簽,關(guān)閉當(dāng)前頁面,關(guān)閉全部頁面,關(guān)閉其他頁面

          93f63b6012359fc8dd42e04a8ebb5589.webp

          這一塊比較簡單,就不介紹了。

          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應(yīng)用


          4.1 問題

          redux在多頁簽的頁面里會存在問題,比如以下兩種情況:

          • 詳情頁面:因為詳情頁面可以打開多個,但是都是公用同一個redux。
          • 多列表頁面共用同一個model
          65a7d7545b9820077430922ac5b51c02.webp0ba989336c8c5527f1272e64711dc03f.webp


          4.2 解決思路

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


          4.3 公用同一個model的具體操作

          85a62bc281ebc861299fffaa88de15d6.webp30bb7df5cc4b1100686afd562a802ef4.webp7306b1580d8563998cc4d2ef89bfe82a.webp


          4.3.1 hoc

          為什么使用hoc,這里是為了不影響業(yè)務(wù)頁面做修改,將修改的地方放在hoc統(tǒng)一處理掉。這里主要是兩個問題:

          • dispatch需要將當(dāng)前primaryKey傳到redux,因為對應(yīng)的數(shù)據(jù)更新需要放到對應(yīng)的primarykey對應(yīng)的state里面去。
          • 業(yè)務(wù)組件使用state數(shù)據(jù)的時候,需要將當(dāng)前primaryKey對應(yīng)的數(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()?{
          ????????//?可以自定擴(kuò)展如何消除當(dāng)前primarykey對應(yīng)的數(shù)據(jù)
          ????????//?一般情況下,前端業(yè)務(wù)組件會自己清除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會走當(dāng)前的頁面,這里會統(tǒng)一將primaryKey傳入進(jìn)去

          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,將對應(yīng)當(dāng)前頁面的數(shù)據(jù)傳回,頁面組件還按照之前直接訪問,下面的detail就是當(dāng)前頁面對應(yīng)的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的一級屬性值使我們頁面對應(yīng)的primaryKey。

          我們會定義一個基礎(chǔ)結(jié)構(gòu),initDataExample。用于在組件初始化的時候使用這個初始值添加到state對應(yīng)的primaryKey。

          4.3.2.2 新增兩個服務(wù)于hoc方法effects

          ????*getExample({?callback?})?{
          ??????if?(callback)?callback({?...initDataExample?});
          ????},
          ????*initData({?payload?},?{?put?})?{
          ??????yield?put({
          ????????type:?'init',
          ????????payload:?{
          ??????????[payload.primaryKey]:?{
          ????????????...initDataExample,
          ??????????},
          ????????},
          ??????});
          ????},

          getExample:獲取初始數(shù)據(jù)結(jié)構(gòu)
          initData:初始化數(shù)據(jù)結(jié)構(gòu)

          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ù):更新的時候傳入對應(yīng)的值,然后更新對應(yīng)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è)務(wù)組件的修改

          我們使用hoc就是為了盡量少的減少業(yè)務(wù)組件的改動。

          hoc內(nèi)部是一個統(tǒng)一的函數(shù)處理,所以hoc是不知道具體當(dāng)前業(yè)務(wù)組件對應(yīng)的modelspace是什么,以及當(dāng)前路由下對應(yīng)的primaryKey的key是什么,因為有的頁面可能是id,有的頁面取得是title。所以modelNamespace和primaryKey需要傳入到hoc。

          業(yè)務(wù)組件只需要添加下面的一行代碼進(jìn)行hoc的傳遞就可以了。

          @withSubscription('detail',?'title')

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

          4a97c1d4a8dc51945a9bd08d4ee836f4.webp


          5. 實際問題

          5.1 控制是否使用多頁簽

          這里其實是想可以控制部分頁面不需要根據(jù)key進(jìn)行判斷,而是根據(jù)pathname進(jìn)行判斷就好了。

          解決的代碼提交是https://github.com/rodchen-king/ant-design-pro-v2/commit/86430c03d3c13f2aed1090c71fb96cf95f195853

          路由需要進(jìn)行只會存在一個頁面標(biāo)示:

          5761db5f3954bd3f08c2da1a1647ff52.webp


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


          e102313b26bc3f11319230f7444c1c6b.webp




          5.2 支持路由match數(shù)據(jù)

          這里其實就是為了處理參數(shù)在pathname里面的參數(shù)

          ea6194d14c3981df45e242900a00decc.webp


          這里主要的問題是采用目前的這種方式,match的數(shù)據(jù)是不會正常返回的

          所以這里對于項目中用到的props.match都需要單獨處理一下。

          所以在BasicLayout里面做了單獨的處理,就是上面說的customerMatch。其實是為了處理這個問題的。


          5.2.1 具體實現(xiàn)

          整體的思想和redux應(yīng)用里面有類似的思路。不同的是

          監(jiān)控路由匹配的時候會處理得到當(dāng)前路由的match值。

          const?match?=?matchPath(route.pathname,?{?path:?replaceRouter.key?});


          這里其實是處理當(dāng)前url和匹配的路由pathname處理得到一個match的結(jié)果。

          f696e628cfa8140ad2f21b390c429638.webp


          至于matchPath這個方法,其實是我從react-router源碼里面復(fù)制出來的:

          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進(jìn)行數(shù)據(jù)傳遞。

          <RouterContext.Provider?value={customerMatchs}>
          ??<Route?key={item.key}?component={item.content}?exact?/>
          </RouterContext.Provider>


          HOC函數(shù)進(jìn)行消費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ù)進(jìn)行primarykey處理。

          943546b3f24fd6024b765038981d03e6.webp


          5.3 ?業(yè)務(wù)頁面不是由單個的參數(shù)進(jìn)行唯一表示,而是多個業(yè)務(wù)參數(shù)進(jìn)行唯一表示

          前面說的都是單個primarykey作為唯一頁面標(biāo)示的,但是可能部分代碼存在很復(fù)雜的情況。

          舉個例子,存在一個業(yè)務(wù)組件既是詳情頁面,也是新增頁面,而且業(yè)務(wù)上還存在多個。

          類似營銷活動:為了更好的說明,我在單獨加一個字段,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

          9e55b4a5d0234a6b96a5d4a9b24d6249.webp
          因為可能參數(shù)當(dāng)中涉及到中文,所以判斷key的邏輯用了decodeURIComponent方法解析。

          第二個在getDetailPagePrimaryId,這里加上這個是為了適配新的查詢參數(shù)。

          修改wrapperWithSubscription

          e1deb2eb32e74d5b7c11e3ebd2971624.webp
          主要是兼容多個參數(shù)的情況,傳進(jìn)來的是一個數(shù)組。


          調(diào)用的方式參考新增的業(yè)務(wù)Maket組件的調(diào)用方式

          @withSubscription('detail',?['activityNo',?'templateName',?'activityType'])
          4fe0bd7e92f43f0979388838c7cc66a1.webp
          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相關(guān)的處理函數(shù)
          ?*/


          /**
          ?*?updateWrapperModel
          ?*?@param?{*}?updateStateObject ????????要更新state的健值對
          ?*?@param?{*}?primaryKey????????????????當(dāng)前頁面對應(yīng)的primaryKey
          ?*?@param?{*}?currentPrimaryKeyState????primaryKey對應(yīng)的數(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

          5583105fb496de7866e6fbc1130c2b10.webp

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

          ?? 看完三件事

          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          • 點個【在看】,或者分享轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容
          • 關(guān)注公眾號【趣談前端】,定期分享?工程化?/?可視化?/?低代碼?/?優(yōu)秀開源。



          f465274d85061ca2c9f56730574a1f96.webp

          從零設(shè)計可視化大屏搭建引擎

          Dooring可視化搭建平臺數(shù)據(jù)源設(shè)計剖析

          可視化搭建的一些思考和實踐

          基于Koa + React + TS從零開發(fā)全棧文檔編輯器(進(jìn)階實戰(zhàn))


          點個在看你最好看

          af8c11b24b7b6cb125a4e3c91a582469.webp
          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操,人人搞,人人摸 | 色哟哟av网址 | 大香蕉一区| 牛牛精品一区二区AV | 黄色片国产在线观看 |