<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>

          前端實戰(zhàn):React 多頁簽緩存處理

          共 50953字,需瀏覽 102分鐘

           ·

          2021-09-05 16:20

          關注并將「趣談前端」設為星標

          每天定時分享技術干貨/優(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',
                      hideInMenutrue,
                      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'首頁'closablefalse }],
                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 解決思路

          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通過關鍵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 = {
                    currentPropsObject.assign({}, props.location),
                    initData: {},
                  };
                }

                componentWillMount() {
                  const { dispatch, location } = this.props;

                  dispatch({
                    type`${modelNameSpace}/initData`,
                    payload: {
                      primaryKey: location.query[modelPrimaryKey],
                    },
                  });

                  dispatch({
                    type`${modelNameSpace}/getExample`,
                    payload: {},
                    callbackresult => {
                      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: {},
                    callbackresult => {
                      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 = {
                    currentLocationObject.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.propsmatch={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


          好了,今天的分享就到這里了,如果文章對你有幫助,你也可以點贊 + 轉(zhuǎn)發(fā), 鼓勵作者持續(xù)創(chuàng)作。




          從零搭建全??梢暬笃林谱髌脚_V6.Dooring

          從零設計可視化大屏搭建引擎

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

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

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



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

          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产一级婬乱片A片 | 亚洲精品福利导航 | 操屄在线播放 | 男人天堂2014 | 亚洲无码性爱video |