[ 一起學React系列 -- 11 ] React-Router4 (1)

2019年不知不覺已經過去19天了,有沒有給自己做個總結?有沒有給明年做個計劃?
當然筆者已經做好了明年的工作、學習計劃;同時也包括今年剩下的博文計劃,在這裏透露下:年前(對,沒有寫錯,是年前)完成該系列博客,目前還剩4篇:分別是兩篇React-Router4和兩篇immutability-helper。本篇是React-Router4的第一篇,在正式開始之前大家可以先看下實際效果,這是筆者用React-Router4寫的例子。

React-Router4

實際上,筆者接觸的第一個React中的路由模塊就是React-Router,只不過當時還是v3版本。因爲沒有太多深入得學習研究,所以在這裏不會對v4之前的版本作介紹或者評價(其實並不代表筆者對v4版本有深入的研究學習,汗...)。下面列舉些React-Router4中需要知道的一些概念或者emmmm...小知識點。

React-Router4中的包

React-Router4中的包主要有三個react-routerreact-router-domreact-router-native。據考證(手動斜眼笑),React-Router4已經將前身切分了出來分別整合成了單獨的module。其實筆者一開始看到也挺懵逼的,這三個包到底是什麼玩意?

react-router:“The core of React Router”。簡單說就是邏輯代碼。
react-router-dom:“DOM bindings for React Router”。這個模塊不僅僅包含了react-router的模塊,還包含了頁面渲染功能,也就是可以在頁面上顯示。
react-router-native:“React Native bindings for React Router”。這個也很好理解,就是可以在React-Native中使用。

路由根節點

React-Router4的理念是一切皆組件React-router-dom則提供了兩個路由根節點:HashRouter和BrowserRouter。

HashRouter: 通過hash值來對路由進行控制,而且你會發現一個現象就是url中會有個#,例如localhost:3000/#。對於筆者這種有強迫症的人來說怎麼能忍?所以筆者就沒再碰這個組件。
BrowserRouter: BrowserRouter就相對舒服點。它的原理是使用HTML5 history API (pushState, replaceState, popState)來使你的內容隨着url動態改變的,而不會出現莫名其妙的#

React-Router4的理念

上面提到:React-Router4的理念是一切皆組件(以下統一稱“組件”)。我們v4之前的版本都需要在一個js文件中配置整個項目的路由信息然後在App.js(以create-react-app腳手架創建的React項目爲例)引入並使用。而在React-Router4中則將所有的功能以React組件的形式提供出來,意思就是說我們可以像使用普通組件一樣使用路由組件並最終在項目中形成一棵路由樹。在這裏筆者要注重說下,一個項目中儘量只有一棵路由樹,除非有特殊需求,否則的話會造成一些奇怪的問題。通俗點說就是不要在一棵樹上栽另一棵樹。下面用一張圖來簡單展示下React-Router4的使用:

clipboard.png

如圖是React-Router4使用規則和最簡單的使用實踐。
當然這種寫法(我稱它叫組件型路由,下同)和之前的配置型路由哪個更好用是青菜蘿蔔的問題,不需要太多糾結,自己喜歡就好。接下來的介紹都是筆者通過學習官方文檔並做了一些實踐後的心得。如有不當或者錯誤,歡迎指正。對於第一次學習該模塊的童鞋,筆者建議將代碼下載下來一遍看代碼一遍看本文,也可以打開筆者的demo,這樣會更深刻。

常用路由組件介紹

下面這段代碼是該demo根路由配置的代碼(其實不存在什麼根路由概念,只是筆者喜歡這麼叫它)。從這裏就能明顯看出來react-router3和4兩個版本的差異。react-router3有自己獨立的一個配置中心文件,而react-router4則把跳轉的規則嵌入到實際代碼中,不需要額外維護一個路由配置文件。從這點來說的確方便了不少,也迎合React一切皆組件的理念。

<Router>
    <div className={AppStyle["Container-body"]}>
        <nav className={AppStyle["App-nav-list"]}>
            <ul>
                <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to="/basic">基礎路由</NavLink></li>
                <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to="/param">帶參路由</NavLink></li>
                <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to="/nesting">嵌套路由</NavLink></li>
                <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to="/ambiguous">曖昧匹配</NavLink></li>
                <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to="/auth">權限路由</NavLink></li>
                <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to="/404">404</NavLink></li>
            </ul>
        </nav>
        <div className={AppStyle.content}>
            <Switch>
                <Redirect exact from="/" to="/basic" />
                <Route path="/basic" component={renderBasicRouter} />

                <Route path="/param" component={RouterWithParameters} />
                <Route path="/ambiguous" component={renderAmbiguousRouter} />
                <Route path="/nesting" component={renderNestingRouter} />
                <Route path="/auth" component={authenticationRouter} />
                <Route component={NoMatch} />
            </Switch>
        </div>
    </div>
</Router>

從這段代碼中需要了解的概念包括:RouterNavLinkRouteRedirectexactSwitch

Router

上面已經介紹過了,這裏筆者用的是BrowserRouter

NavLink

NavLink可以觸發路由的跳轉,當然類似的組件還有Link。它們都可以通過指定屬性to的值來告訴Router我們要跳轉到那個Route,實際上NavLink(Link)Route本就已經通過topath兩個屬性建立起關係了。而NavLink與Link的區別在於各自API的數量,因爲NavLink可用的API相對較多,所以筆者更青睞NavLink。具體API有興趣的可以自行Google,常用的API筆者已經在項目中使用。

Route

Route組件是React Router4中主要的組成單位,可以認爲是NavLink或Link的路由入口。在這裏筆者要重點Tip一下,上一條說NavLink或Link可以觸發路由的跳轉,實際上它們的實現流程是這樣的:

NavLink(Link)改變地址欄的pathname,Router會根據pathname去匹配它子組件中的Route中的path屬性,一旦匹配上就會渲染該Route對應的組件`。所以NavLink(Link)與Route並沒有並存的關係,因爲NavLink(Link)只是用來改變pathname,不會直接去調用任何API。所以當我們在地址欄直接手動輸入路由,也會發生路由渲染。

具體匹配規則請參考path-to-regexp,或者通過這個網站進行測試。

Redirect

Redirect相當於轉發器。Router內部去匹配路由入口的時候也會去匹配Redirect的from屬性值。一旦匹配到了Redirect,Redirect就會將這個跳轉請求轉向自己的to屬性值對應的路由。所以這個過程可以這樣理解:當我們訪問頁面路徑的時候,比如:http://ip:3001/,就會捕獲到'/'這個路由跳轉請求,Router開始在Route中匹配隨後匹配到了Redirect,Redirect自行發起路由跳轉請求'/basic',Router開始像往常一樣在Route中匹配直到匹配到了path爲"/basic"的Route,隨後對Route對應的component進行渲染。

exact

exact將該Route標示爲嚴格匹配路由。什麼叫嚴格匹配路由?就是pathname必須與Route的path屬性值完全一致纔算匹配上。例如:

pathname path 匹配結果
/home /home 可以匹配
/home/child /home 無法匹配
這裏Tip下:
如果某個Route對應的組件中也有Route,那麼千萬不要 在這個Route中加 exact,不然你會發現完全匹配不到子路由。切記,因爲筆者最近剛踩過這個坑。所以這裏筆者建議大家只在葉子Route中使用exact,也就是最後一級Route中使用exact,當然 exact '/' 除外

Switch

Switch可以將多個Route包裹成一組Route,當進行路由匹配時候,該組中的路由一旦發生匹配那麼就不會匹配改組中剩下的路由。也就是說該組中的路由最多隻會被匹配到一個

Route的component屬性

追加一條。
Route的component屬性對應的屬性值通常是一個組件或者一個方法。而如果是方法的話,比如例子中:

const renderBasicRouter = ({ match }) => <Basic url={match.url} path={match.path} />;

那麼傳入的參數爲:
clipboard.png

所以如果在跳轉前有什麼特殊邏輯需要處理,比如我們想讓不同的頁面有不同的前綴,比如例子中的/basic/Home/param/home等,那麼就可以如例子一樣處理。
但如果不需要特殊處理的話,直接把組件放到component屬性下即可。當然router相關的參數也會通過props傳給該組件。

這裏Tip下:
1、Route中還有一個render屬性,也可以用來渲染組件,但是當我們渲染被Redux處理過的組件時候可能會有問題,因爲Redux會在原組件基礎上多包裹一層。
2、如果

當然常用路由組件還不僅僅這些,後續例子會有補充。

基礎路由

基礎路由的使用比較簡單,前面的介紹其實已經把它基礎使用方法已經說了。不過筆者這裏有個小tip:

match.url 常用於NavLink或者Link中拼接路由路徑,比如:
<NavLink to={`${URL}/Home`}></NavLink>
match.path常用於Route中拼接路由入口路徑,比如:
<Route path={`${PATH}/Home`} component={HomePage} />
這裏Tip下:
當URL中不帶有任何參數的時候,match.path和match.url完全一致。如果帶有參數的話可能會有點編碼上的差異。

帶參路由

帶參路由在實際開發中用的比較多,這裏只介紹location.pathname中的參數,location.search筆者沒有研究過所以就不說了,免得誤導大家。我們可以先看下demo例子的部分代碼:

<nav className={Style["Params-nav-list"]}>
    <ul>
        <li><NavLink exact activeStyle={{ fontWeight: 'bold', color: 'red' }} to={`${URL}/name`}>/name</NavLink></li>
        <li><NavLink exact activeStyle={{ fontWeight: 'bold', color: 'red' }} to={`${URL}/name/Mario`}>/name/Mario</NavLink></li>
        <li><NavLink activeStyle={{ fontWeight: 'bold', color: 'red' }} to={`${URL}/check/true`}>/check/true</NavLink></li>
    </ul>
</nav>
<div className={Style.content}>
    <Switch>
        <Route exact path={`${PATH}/name/:name`} component={Name} />
        <Route exact path={`${PATH}/name`} component={Name1} />
        <Route exact path={`${PATH}/check/:check(true|false)`} component={Check} />
        <Route component={NoMatch} />
    </Switch>
</div>

這裏可以看到Route的配置有點不同,用過express的朋友都知道,路由中通過 :xxx 來標示這是一個參數。其實這裏也一樣。如例子所示我們在 path={${PATH}/name/:name}通過 :name 來標示這是一個參數。然後對應的導航也有 to={${URL}/name/Mario}。所以這個流程就相當於:導航到/name路由並且傳 name爲Mario的參數。這個參數可以在對應組件的props中拿到(this.props.match.prams.name)。我們還看到 path={${PATH}/check/:check(true|false)},在參數後有個括號並且裏面還有管道符,其實這是限定check的參數值必須爲true或者false這兩個。細心的朋友可能注意到,我在每個Route中加了exact,這樣做的好處是可以不用考慮Route的放置次序。舉個例子解釋下:如果我們想查看某個人的信息,那麼跳轉路由應該是 /user/4,但如果Route中有:

<Switch>
    <Route exact path={/user} component={User} />
    <Route exact path={/user/:id} component={User} />
</Switch>

那麼/user/4就會在匹配到/user後停下渲染User組件並且忽略了參數。有人說把Switch去掉不就行了嗎?並不是,那麼/user/4會同時匹配上這兩個路由並且什麼都不會渲染,因爲它懵逼了,不知道渲染哪個。所以這種情況下需要調整它們的位置

<Switch>
    <Route exact path={/user/:id} component={User} />
    <Route exact path={/user} component={User} />
</Switch>

這樣就不會出現上述問題了。但是如果在每一個Route中使用exact(前提是這個Route是葉子Route),就不用考慮Route的次序問題了。

404路由

有時候會由於各種問題出現匹配不到任何Route的情況,這個時候爲了更好的用戶體驗,我們會配置一個404路由,形如:

<div className={Style.content}>
    <Switch>
        <Route exact path={`${PATH}/name/:name`} component={Name} />
        <Route exact path={`${PATH}/name`} component={Name1} />
        <Route exact path={`${PATH}/check/:check(true|false)`} component={Check} />
        <Route component={NoMatch} />
    </Switch>
</div>

不過筆者發現在根路由中配置一個404後無法全局抓取路由404,不知道是本就如此還是用法有誤,所以筆者在每一級路由中都配置了 <Route component={NoMatch} />。寫法很簡單,照着寫就好了,component中傳入顯示404信息的組件即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章