<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-Router 出發(fā),如何設(shè)計(jì)更優(yōu)雅的路由?

          共 13024字,需瀏覽 27分鐘

           ·

          2021-08-02 16:57


          前言

          每次開(kāi)發(fā)新頁(yè)面的時(shí)候,都免不了要去設(shè)計(jì)一個(gè)新的 URL,也就是我們的路由。其實(shí)路由在設(shè)計(jì)的時(shí)候不僅僅是一個(gè)由幾個(gè)簡(jiǎn)單詞匯和斜杠分隔符組成的鏈接,偶爾也可以去考慮有沒(méi)有更“優(yōu)雅”的設(shè)計(jì)方式和技巧。而在這背后,路由和組件之間的協(xié)作關(guān)系是怎樣的呢?于是我以 React 中的 Router 使用方法為例,整理了一些知識(shí)點(diǎn)小記和大家分享~

          React-Router

          基本用法

          通常我們使用 React-Router (https://reactrouter.com/native/guides/quick-start) 來(lái)實(shí)現(xiàn) React 單頁(yè)應(yīng)用的路由控制,它通過(guò)管理 URL,實(shí)現(xiàn)組件的切換,進(jìn)而呈現(xiàn)頁(yè)面的切換效果。

          其最基本用法如下:

          import { Router, Route } from 'react-router';
          render((
            <Router>
              <Route path="/" component={App}/>
            </Router>

          ), document.getElementById('app'));

          亦或是嵌套路由:

          在 React-Router V4 版本之前可以直接嵌套,方法如下:

          <Router>
            <Route path="/" render={() => <div>外層</div>}>
                <Route path="/in" render={() => <div>內(nèi)層</div>} />
            </Route>

          </Router>

          上面代碼中,理論上,用戶訪問(wèn) /in 時(shí),會(huì)先加載 <div>外層</div>,然后在它的內(nèi)部再加載 <div>內(nèi)層</div>。

          然而實(shí)際運(yùn)行上述代碼卻發(fā)現(xiàn)它只渲染出了根目錄中的內(nèi)容。后續(xù)對(duì)比 React-Router 版本發(fā)現(xiàn),是因?yàn)樵?V4 版本中變更了其渲染邏輯,原因據(jù)說(shuō)是為了踐行 React 的組件化理念,不能讓 Route 標(biāo)簽看起來(lái)只是一個(gè)標(biāo)簽(奇怪的知識(shí)又增加了)。

          現(xiàn)在較新的版本中,可以使用 Render 方法實(shí)現(xiàn)嵌套。

          <Route
            path="/"
            render={() => (
              <div>
                <Route
                  path="/"
                  render={() =>
           <div>外層</div>}
                />
                <Route
                  path="/in"
                  render={() =>
           <div>內(nèi)層</div>}
                />
                <Route
                  path="/others"
                  render={() =>
           <div>其他</div>}
                />
              </div>

            )}
          />

          此時(shí)訪問(wèn) /in 時(shí),會(huì)將“外層”和“內(nèi)層”一起展示出來(lái),類似地,訪問(wèn) /others 時(shí),會(huì)將“外層”和“其他”一起展示出來(lái)。

          路由傳參小 Tips

          在實(shí)際開(kāi)發(fā)中,往往在頁(yè)面切換時(shí)需要傳遞一些參數(shù),有些參數(shù)適合放在 Redux 中作為全局?jǐn)?shù)據(jù),或者通過(guò)上下文傳遞,比如業(yè)務(wù)的一些共享數(shù)據(jù),但有些參數(shù)則適合放在 URL 中傳遞,比如頁(yè)面類型或詳情頁(yè)中單據(jù)的唯一標(biāo)識(shí) id。在處理 URL 時(shí),除了問(wèn)號(hào)帶參數(shù)的方式,React-Router 能幫我們做什么呢?在這其中,Route 組件的 path 屬性便可用于指定路由的匹配規(guī)則。

          場(chǎng)景 1

           

          描述:就想讓普普通通的 URL 帶個(gè)平平無(wú)奇的參數(shù)

          那么,接下來(lái)我們可以這樣干:

          Case A:路由參數(shù)

          path="/book/:id"

          我們可以用冒號(hào) + 參數(shù)名字的方式,將想要傳遞的參數(shù)添加到 URL 上,此時(shí),當(dāng)參數(shù)名字(本 Case 中是 id)對(duì)應(yīng)的值改變時(shí),將被認(rèn)為是不同 URL。

          Case B:查詢參數(shù)

          path="/book"

          如果想要在頁(yè)面跳轉(zhuǎn)的時(shí)候問(wèn)號(hào)帶參數(shù),那么 path 可以直接設(shè)計(jì)成既定的樣子,參數(shù)由跳轉(zhuǎn)方拼接。在跳轉(zhuǎn)時(shí),有兩種形式帶上參數(shù)。其一是在 Link 組件的 to 參數(shù)中通過(guò)配置字符串并用問(wèn)號(hào)帶參數(shù),其二是 to 參數(shù)可以接受一個(gè)對(duì)象,其中可以在 search 字段中配置想要傳遞的參數(shù)。

          <Link to="/book?id=111" />
          // 或者
          <Link to={{
            pathname: '/book',
            search: '?id=111',
          }}/>

          此時(shí),假設(shè)當(dāng)前頁(yè)面 URL 中的 id 由 111 修改為 222 時(shí),該路由對(duì)應(yīng)的組件(在上述例子中就是 React-Route 配置時(shí) path="/book" 對(duì)應(yīng)的頁(yè)面/組件 )會(huì)更新,即執(zhí)行 componentDidUpdate 方法,但不會(huì)被卸載,也就是說(shuō),不會(huì)執(zhí)行 componentDidMount 方法。

          Case C:查詢參數(shù)隱身式帶法

          path="/book"

          path 依舊設(shè)計(jì)成既定的樣子,而在跳轉(zhuǎn)時(shí),可以通過(guò) Link 中的 state 將參數(shù)傳遞給對(duì)應(yīng)路由的頁(yè)面。

          <Link to={{
            pathname'/book',
            state: { id111 }
          }}/>

          但一定要注意的是,盡管這種方式下查詢參數(shù)不會(huì)明文傳遞了,但此時(shí)頁(yè)面刷新會(huì)導(dǎo)致參數(shù)丟失(存儲(chǔ)在 state 中的通?。琒o,灰常不推薦~~(其實(shí)不想明文可以進(jìn)行加密處理,但一般情況下敏感信息是不建議放在 URL 中傳遞的~)

          場(chǎng)景 2

           

          描述:編輯/詳情頁(yè),想要共用一個(gè)頁(yè)面,URL 由不同的參數(shù)區(qū)分,此時(shí)我們希望,參數(shù)必須為 edit、detail、add 中的 1 個(gè),不然需要跳轉(zhuǎn)到 404 Not Found 頁(yè)面。

          path='/book/:pageType(edit|detail|add)'

          如果不加括號(hào)中的內(nèi)容 (edit|detail|add),當(dāng)傳入錯(cuò)誤的參數(shù)(比如用戶誤操作、隨便拼接 URL 的情況),則頁(yè)面不會(huì)被 404 攔截,而是繼續(xù)走下去開(kāi)始渲染頁(yè)面或調(diào)用接口,但此時(shí)很有可能導(dǎo)致接口傳參錯(cuò)誤或頁(yè)面出錯(cuò)。

          場(chǎng)景 3

           

          描述:新增頁(yè)和編輯頁(yè)辣么像,我的新增頁(yè)也想和編輯/詳情共用一個(gè)頁(yè)面。但是新增頁(yè)不需要 id,編輯/詳情頁(yè)需要 id,使用同一個(gè)頁(yè)面怎么辦?

          path='/book/:pageType(edit|detail|add)/:id?'

          別急,可以用 ? 來(lái)解決,它意味著 id 不是一個(gè)必要參數(shù),可傳可不傳。

          場(chǎng)景 4

           

          描述:我的 id 只能是數(shù)字,不想要字符串怎么辦?

          path='/book/:id(\\\d+)'

          此時(shí) id 不是數(shù)字時(shí),會(huì)跳轉(zhuǎn) 404,被認(rèn)為 URL 對(duì)應(yīng)的頁(yè)面找不到啦。

          底層依賴

          有了這么多場(chǎng)景,那 Router 是怎樣實(shí)現(xiàn)的呢?其實(shí)它底層是依賴了 path-to-regexp (https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) 方法。

          var pathToRegexp = require('path-to-regexp')
          // pathToRegexp(path, keys, options)
          // 示例
          var keys = []
          var re = pathToRegexp('/foo/:bar', keys)
          // re = /^\/foo\/([^\/]+?)\/?$/i
          // keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
           

          delimiter:重復(fù)參數(shù)的定界符,默認(rèn)是 '/',可配置

          一些其他常用的路由正則通配符:

          • ? 可選參數(shù)

          • * 匹配 0 次或多次

          • + 匹配 1 次或多次

          如果忘記寫參數(shù)名字,而只寫了路由規(guī)則,比如下述代碼中 /:foo 后面的參數(shù):

          var re = pathToRegexp('/:foo/(.*)', keys)
          // 匹配除“\n”之外的任何字符
          // keys = [{ name: 'foo', ... }, { name: 0, ...}]
          re.exec('/test/route')
          //=> ['/test/route', 'test', 'route']

          它也會(huì)被正確解析,只不過(guò)在方法處理的內(nèi)部,未命名的參數(shù)名會(huì)被替換成數(shù)組下標(biāo)。

          取路由參數(shù)

          path 帶的參數(shù),可以通過(guò) this.props.match 獲取

          例如:

          // url 為 /book/:pageType(edit|detail|add)
          const { match } = this.props;
          const { pageType } = match.params;

          由于有 #,# 之后的所有內(nèi)容都會(huì)被認(rèn)為是 hash 的一部分,window.location.search 是取不到問(wèn)號(hào)帶的參數(shù)的。

          比如:http://aaa.bbb.com/book-center/#/book/list?id=123

          那么在 React-Router 中,問(wèn)號(hào)帶的參數(shù),可以通過(guò) this.props.location (官方墻推 ??)獲取。個(gè)人理解是因?yàn)?React-Router 幫我們做了處理,通過(guò)路由和 hash 值(window.location.hash)做了解析的封裝。

          例如:

          // url 為 /book?pageType=edit
          const { location } = this.props;
          const searchParams = location.search; // ?pageType=edit

          實(shí)際打印 props 參數(shù)發(fā)現(xiàn),this.props.history.location 也可以取到問(wèn)號(hào)參數(shù),但不建議使用,因?yàn)?React 的生命周期(componentWillReceiveProps、componentDidUpdate)可能使它變得不可靠。(原因可參考:https://blog.csdn.net/zrq1210/article/details/108403772)

          在早期的 React-Router 2.0 版本是可以用 location.query.pageType 來(lái)獲取參數(shù)的,但是 V4.0 去掉了(有人認(rèn)為查詢參數(shù)不是 URL 的一部分,有人認(rèn)為現(xiàn)在有很多第三方庫(kù),交給開(kāi)發(fā)者自己去解析會(huì)更好,有個(gè)對(duì)此討論的 Issue,有興趣的可以自行獲取 ?? https://github.com/ReactTraining/react-router/issues/4410)

          針對(duì)上一節(jié)中場(chǎng)景 1 的 Case C,查詢參數(shù)隱身式帶法時(shí)(從 state 里帶過(guò)去的),在 this.props.location.state 里可以取到(不推薦不推薦不推薦,刷新會(huì)沒(méi)~)

          Switch

          <div>
            <Route
              path="/router/:type"
              render={() =>
           <div>影像</div>}
            />

            <Route
              path="/router/book"
              render={() =>
           <div>圖書(shū)</div>}
            />

          </div>

          如果 <Route /> 是平鋪的(用 div 包裹是因?yàn)?Router 下只能有一個(gè)元素),輸入 /router/book 則影像和圖書(shū)都會(huì)被渲染出來(lái),如果想要只精確渲染其中一個(gè),則需要 Switch

          <Switch>
            <Route
              path="/router/:type"
              render={() =>
           <div>影像</div>}
            />

            <Route
              path="/router/book"
              render={() =>
           <div>圖書(shū)</div>}
            />

          </Switch>

          Switch 的意思便是精準(zhǔn)的根據(jù)不同的 path 渲染不同 Route 下的組件。但是,加了 Switch 之后路由匹配規(guī)則是從上到下執(zhí)行,一旦發(fā)現(xiàn)匹配,就不再匹配其余的規(guī)則了。因此在使用的時(shí)候一定要“百般小心”。

          上面代碼中,用戶訪問(wèn) /router/book 時(shí),不會(huì)觸發(fā)第二個(gè)路由規(guī)則(不會(huì)展示“圖書(shū)”),因?yàn)樗鼤?huì)匹配 /router/:type 這個(gè)規(guī)則。因此,帶參數(shù)的路徑一般要寫在路由規(guī)則的底部。

          路由的基本原理

          路由做的事情:管控 URL 變化,改變?yōu)g覽器中的地址。

          Router 做的事情:URL 改變時(shí),觸發(fā)渲染,渲染對(duì)應(yīng)的組件。

          URL 有兩種,一種不帶 #,一種帶 #,分別對(duì)應(yīng) Browse 模式和 Hash 模式。

          一般單頁(yè)應(yīng)用中,改變 URL,但是不重新加載頁(yè)面的方式有兩類:

          Case 1(會(huì)觸發(fā)路由監(jiān)聽(tīng)事件):點(diǎn)擊 前進(jìn)、后退,或者調(diào)用的 history.back( )、history.forward( )

          Case 2(不會(huì)觸發(fā)路由監(jiān)聽(tīng)事件):組件中調(diào)用 history.push( ) 和 history.replace( )

          于是參考「源碼解析 」這一次徹底弄懂 React-Router 路由原理(https://blog.csdn.net/zl_alien/article/details/109231294) 一文,針對(duì)上述兩種 Case,以及這兩種 Case 分別對(duì)應(yīng)的兩種模式,作出如下總結(jié)。

           

          圖片來(lái)源:「源碼解析 」這一次徹底弄懂 React-Router 路由原理

          Browser 模式

          Case 1:

          URL 改變,觸發(fā)路由的監(jiān)聽(tīng)事件 popstate,then,監(jiān)聽(tīng)事件的回調(diào)函數(shù) handlePopState 在回調(diào)中觸發(fā) history 的 setState 方法,產(chǎn)生新的 location 對(duì)象。state 改變,通知 Router 組件更新 location 并通過(guò) context 上下文傳遞,匹配出符合的 Route 組件,最后由 <Route /> 組件取出對(duì)應(yīng)內(nèi)容,傳遞給渲染頁(yè)面,渲染更新。

          /* 簡(jiǎn)化版的 handlePopState (監(jiān)聽(tīng)事件的回調(diào)) */
          const handlePopState = (event)=>{
               /* 獲取當(dāng)前l(fā)ocation對(duì)象 */
              const location = getDOMLocation(event.state)
              const action = 'POP'
               /* transitionManager 處理路由轉(zhuǎn)換 */
              transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
                  if (ok) {
                    setState({ action, location })
                  } else {
                    revertPop(location)
                  }
              })
          }

          Case 2: 以 history.push 為例,首先依據(jù)你要跳轉(zhuǎn)的 path 創(chuàng)建一個(gè)新的 location 對(duì)象,然后通過(guò) window.history.pushState (H5 提供的 API )方法改變?yōu)g覽器當(dāng)前路由(即當(dāng)前的 url),最后通過(guò) setState 方法通知 Router,觸發(fā)組件更新。

          const push = (path, state) => {
             const action = 'PUSH'
             /* 創(chuàng)建location對(duì)象 */
             const location = createLocation(path, state, createKey(), history.location)
             /* 確定是否能進(jìn)行路由轉(zhuǎn)換 */
             transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
             ... // 此處省略部分代碼
             const href = createHref(location)
             const { key, state } = location
             if (canUseHistory) {
               /* 改變 url */
               globalHistory.pushState({ key, state }, null, href)
               if (forceRefresh) {
                 window.location.href = href
               } else {
                 /* 改變 react-router location對(duì)象, 創(chuàng)建更新環(huán)境 */
                 setState({ action, location })
               }
             } else {
               window.location.href = href
             }
           })
          }

          Hash 模式

          Case 1:

          增加監(jiān)聽(tīng),當(dāng) URL 的 Hash 發(fā)生變化時(shí),觸發(fā) hashChange 注冊(cè)的回調(diào),回調(diào)中去進(jìn)行相類似的操作,進(jìn)而展示不同的內(nèi)容。

          window.addEventListener('hashchange',function(e){
            /* 監(jiān)聽(tīng)改變 */
          })

          Case 2:

          history.push 底層調(diào)用 window.location.hash 來(lái)改變路由。history.replace 底層是調(diào)用 window.location.replace 改變路由。然后 setState 通知改變。

          從一些參考資料中顯示,出于兼容性的考慮(H5 的方法 IE10 以下不兼容),路由系統(tǒng)內(nèi)部將 Hash 模式作為創(chuàng)建 History 對(duì)象的默認(rèn)方法。(此處若有疑議,歡迎指正~)

          Dva/Router

          在實(shí)際項(xiàng)目中發(fā)現(xiàn),Link,Route 都是從 dva/router 中引進(jìn)來(lái)的,那么,Dva 在這之中做了什么呢?

          答案:貌似沒(méi)有做特殊處理,Dva 在 React-Router 上做了上層封裝,會(huì)默認(rèn)輸出 React-Router (https://github.com/ReactTraining/react-router) 接口。

          我們對(duì) Router 做過(guò)的一些處理

          Case 1:

          項(xiàng)目代碼的 src 目錄下,不管有多少文件夾,路由一般會(huì)放在同一個(gè) router.js 文件中維護(hù),但這樣會(huì)導(dǎo)致頁(yè)面太多時(shí),文件內(nèi)容會(huì)越來(lái)越長(zhǎng),不便于查找和修改。

          因此我們可以做一些小改造,在 src 下的每個(gè)文件夾中,創(chuàng)建自己的路由配置文件,以便管理各自的路由。但這種情況下 React-Router 是不能識(shí)別的,于是我們寫了一個(gè) Plugin 放在 Webpack 中,目的是將各個(gè)文件夾下的路由匯總,并生成 router-config.js 文件。之后,將該文件中的內(nèi)容解析成組件需要的相關(guān)內(nèi)容。插件實(shí)現(xiàn)方式可了解本團(tuán)隊(duì)另一篇文章:手把手帶你入門Webpack Plugin。

          Case 2:

          路由的 Hash 模式雖然兼容性好,但是也存在一些問(wèn)題:

          1. 對(duì)于 SEO、前端埋點(diǎn)不太友好,不容易區(qū)分路徑
          2. 原有頁(yè)面有錨點(diǎn)時(shí),使用 Hash 模式會(huì)出現(xiàn)沖突

          因此公司內(nèi)部做了一次 Hash 路由轉(zhuǎn) Browser 路由的改造。

          如原有鏈接為:http://aaa.bbb.com/book-center/#/book/list?id=123

          改造方案為:

          通過(guò)新增以下配置代碼去掉 #

          import createHistory from 'history/createBrowserHistroy';
          const app = dva({
            history: createHistory({
              basename'/book-center',
            }),
            onError,
          });

          同時(shí),為了避免用戶訪問(wèn)舊頁(yè)面出現(xiàn) 404 的情況,前端需要在 Redirect 中配置重定向以及在 Nginx 中配置舊的 Hash 頁(yè)面轉(zhuǎn)發(fā)。

          Case 3:

          在實(shí)際項(xiàng)目中,其實(shí)我們也會(huì)去考慮用戶未授權(quán)時(shí)路由跳轉(zhuǎn)、頁(yè)面 404 時(shí)路由跳轉(zhuǎn)等不同情況,以下 Case 和代碼僅供讀者參考~

          <Switch>
            {
              getRoutes(match.path, routerData).map(item =>
               (
                 // 用戶未授權(quán)處理,AuthorizedRoute 為項(xiàng)目中自己實(shí)現(xiàn)的處理組件
                 <AuthorizedRoute
                   {...item}
                   redirectPath="/exception/403"
                 />

               )
              )
            }
            // 默認(rèn)跳轉(zhuǎn)頁(yè)面
            <Redirect from="/" exact to="/list" />
            // 頁(yè)面 404 處理
            <Route render={props => <NotFound {...props} />} />
          </Switch>

          參考鏈接

          「源碼解析 」這一次徹底弄懂react-router路由原理 (https://blog.csdn.net/zl_alien/article/details/109231294)

          react-router v4 路由規(guī)則解析 (https://www.cnblogs.com/pqjwyn/p/9936153.html)

          二級(jí)動(dòng)態(tài)路由的解決方案 (https://aibokalv.oschina.io/myarticle/2017/04/01/20170401%E4%BA%8C%E7%BA%A7%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/)



          瀏覽 101
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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一区二区三区 | 无码一区二区四区 |