React路由基礎和用法

前言


react-router-v4,我稱之爲“第四代react-router”,react-router針對不同的使用場景衍生了不同的路由包,RN項目用react-router-native,web項目用react-router-dom。他們有什麼區別呢?讓我們一起來看一下:

我們會在react項目中看到這兩種路由的寫法:
寫法1:

import {Swtich, Route, Router, HashHistory, Link} from 'react-router-dom';

寫法2:

import {Switch, Route, Router} from 'react-router';
import {HashHistory, Link} from 'react-router-dom';

看一下,他們之間有什麼去邊:

  • react-router:實現了路由的核心功能
  • react-router-dom:基於react-router,加入了在瀏覽器運行環境下的一些功能,例如:Link組件,會渲染一個a標籤,Link組件源碼a標籤行; BrowserRouter和HashRouter組件,前者使用pushState和popState事件構建路由,後者使用window.location.hash和hashchange事件構建路由。
  • react-router-native:基於react-router,類似react-router-dom,加入了react-native運行環境下的一些功能。


前端路由


在瞭解react路由之前,讓我們先來了解一下前端路由,Ajax誕生以後,解決了每次用戶操作都要向服務器端發起請求重刷整個頁面的問題,但隨之而來的問題是無法保存Ajax操作狀態,瀏覽器的前進後退功能也不可用,當下流行的兩種解決方法是:

  1. hash, hash原本的作用是爲一個很長的文檔頁添加錨點信息,它自帶不改變url刷新頁面的功能,所以自然而然被用在記錄Ajax操作狀態中了。
  2. history, 應該說history是主流的解決方案,瀏覽器的前進後退用的就是這個,它是window對象下的,以前的history提供的方法只能做頁面之間的前進後退,如下:
  • history.go(number|URL)可加載歷史列表中的某個具體的頁面
  • history.forward()可加載歷史列表中的下一個 URL
  • history.back()可加載歷史列表中的前一個 URL

爲了讓history不僅僅能回退到上一個頁面,還可以回到上一個操作狀態。HTML5新增了三個方法,其中兩個是在history對象裏的:

  • history.pushState(state, title, url)
    添加一條歷史記錄, state用於傳遞參數,可以爲空。title是設置歷史記錄的標題,可以爲空。url是歷史記錄的URL,不可以爲空。
  • history.replaceState(state, title, url)
    將history堆棧中當前的記錄替換成這裏的url,參數同上。

還有一個事件在window對象下:

window.onpopstate()監聽url的變化,會忽略hash的變化(hash變化有一個onhashchange事件),但是前面的兩個事件不會觸發它。

好了,到這裏你大概猜到了單頁面應用或者Ajax操作記錄狀態用的就是hash和h5增加的history API,這就是react-router-dom 擴展的路由實現,也是web應用最常用的兩種路由。

安裝


正如我前面所說,對於web應用,我們只需要安裝react-router-dom:

npm install react-router-dom

不過,在node_modules下你依然能找到react-router這個組建,這是因爲react-router-dom依賴於react-router,另外還有一個history包,我們一會再看。


<Router>


<Router>是實現路由最外層的容器,一般情況下我們不再需要直接使用它,而是使用在它基礎之上封裝的幾個適用於不同環境的組件,react-router-dom有四種Router:

Router 適用情況
BrowserRouter react-router-dom擴展,利用HTML5 新增的history API (pushState, replaceState),是web應用最常用的路由組件
HashRouter react-router-dom擴展,利用window.location.hash,適用於低版本瀏覽器或者一些特殊情境
MemoryRouter 繼承自react-router ,用戶在地址欄看不到任何路徑變化,一般用在測試或者非瀏覽器環境開發中
StaticRouter 繼承自react-router,某些頁面從渲染出來以後沒有多的交互,所以沒有狀態的變化需要存儲,就可以使用靜態路由,靜態路由適用於服務器端

備註一:<withRouter>有別於上面四個組件,這裏沒有列出來。

備註二:一般我們很少會用到和,在web應用中更多的是用react-router-dom擴展出來的%=<BrowserRouter>和<HashRouter>,這兩個就是我前面提到的前端路由的兩種解決辦法的各自實現。

<Route>


<Route>是路由配置的具體實現,它指定當路徑匹配的時候渲染哪一個UI,一個基本的路由配置如下:

<Router>
    <div>
        <Route exact path="/" component={Home}/>
        <Route strict path="/login" render={() => <h1>Login</h1>} />
        <Route path="/user" children={() => <h1>User</h1>}/>
    </div>
</Router>

path,exact,strict

  • path是用於指定路徑名的,exact和strict是匹配路徑名時指定更爲嚴格的匹配規則,其匹配原則用的是path-to-regexp
  • 如果不寫path則總是能被匹配。
  • 當exact爲true時只有path等於location.pathname時纔會匹配成功。location就是前面Router提到的location對象,我也在圖中框出來了。
  • 當strict爲true時會嚴格驗證尾隨線,path和location.pathname都有或者都沒有才會匹配成功。

讓我們看幾個例子理解一下,注意以下例子exactstrict都是寫在<Route>裏的,<NavLink>也有這兩個值,寫在這兩個地方效果是不一樣的,後面會講<NavLink>.

path location.pathname exact match?
/ /user false yes
/ /user true no
/user /user/:name false yes
/user /user/:name true no

注:第三、四行是帶參數路由的寫法,後面會講。

總結:從表中可以看出,當一個路徑包含某一個路徑,暫且稱它們爲子路徑和父路徑,如果exact爲false(默認),那麼“子路徑”會渲染出“父路徑”的UI(所有的路徑都是’/‘的子路徑)如果不想子路徑渲染出父路徑的UI,那麼就給父路徑添加exact屬性。所以表中一二行的exact是加在‘/’的<Route>裏,三四行是加在’/user’的<Route>裏。

path location.pathname strict match?
/user/ /user false yes
/user/ /user true no
/user /user/ true yes
/user/ /user/logout true or false yes

注意:表中第二三行的區別,即多餘的尾隨線加在location.pathname裏,那麼依然會匹配成功。

從第四行可以看出,path有尾隨線,location.pathname有二級路由,會被認爲也是有尾隨線的,所以會匹配成功,不過只需要再添加exact,那麼就無法匹配成功了。

component, render, children

component, render, children是渲染UI方法,它們的區別如下:

  • component (最常用)當路徑匹配時渲染UI,內部實現用的是React.createElement()方法,即每一次都會觸發卸載和創建組件,如果渲染的UI沒有多餘的內容,推薦使用render。
  • render 當路徑匹配時渲染UI,與component不同的是,它只調用render()方法去渲染組件,不會去重新創建元素,所以速度更快,只適用於行內渲染。
  • children 與render類似,唯一的區別是不管路徑是否匹配都會渲染,所以它最適合用於做轉場動畫

這三個方法在渲染組件的同時還傳遞了幾個參數過去,這些參數也不是它的,是從前面傳下來的:

const props = { match, location, history, staticContext }

除了最後一個其它三個我們已經見過了,match來自Router.js,前面我也貼過源碼,history和loaction來自history插件的createBrowserHistory(或createHashHistory)方法,最後一個我暫時還不清楚怎麼用。現在,這幾個UI組件都可以訪問到這幾個對象了:

//component
<Route path="/user" component={User} />
//User.js
class User extends Component {
    let { match, location, history } = this.props;
    render() {
        return(
            <div className="user"></div>    
        ) 
    }
}
//render, children
<Route path="/user" render={(match, location, history) => <div></div>} />

當然,有個最簡單的方式就是直接傳一個props屬性過去,這幾個對象可以直接通過props屬性訪問:

//render, children
<Route path="/user" render={(props) => <User {...props}/>} />

它們有啥用?後面就知道了。

<NavLink>和<Link>


前面的<Route>提供了路由配置,<NavLink><Link>就是可以訪問這些路由的組件,也就是:

Route path => path
//to可以是對象也可以是字符串
NavLink(Link) to => location or location.pathname

總結:整個路由棧匹配就是在圍繞path和location.pathname這兩個東西,其中,組件負責path, ()組件負責location.pathname。

一個簡單的示例:

<ul>
    <li><NavLink exact to="/">Home</NavLink></li>
    <li><NavLink strict
        to={{
            pathname: "/user/",
            state: {isLogin: true},
            search: 'name=melody',
            hash: 'tab1'
        }}>
        User</NavLink></li>
    <li><NavLink to="/login">Login</NavLink></li>
</ul>

<NavLink>和 <Link>的區別

它倆都是react-router-dom提供的組件,<NavLink>是在<Link>上面擴展了當路由匹配時添加樣式屬性,而這更常用,所以建議直接使用<NavLink>.

<Link>提供的屬性及方法:

  • to [string]:路徑名
  • to [object]:location對象,值如下:
 {
    pathname: '/', //路徑名,
    search: '', //參數,會添加到url裏面,形如"?name=melody&age=20"
    hash: '', //參數,會添加到url裏面,形如"#tab1"
    state: {},//參數,不會添加到url裏面
  }
  • replace[bool]false, //是否替換當前路由,正常情況下是往路由棧裏新增一條數據,如果將此參數設置爲true,則會替換當前路由。

<NavLink>擴展的屬性及方法:

  • activeClassName[string]:‘active’, //路由匹配時添加的class,默認是active
  • activeStyle[object]:{}, //路由匹配時的樣式
  • exact[bool]:是否開啓嚴格模式
  • strict[bool]:是否嚴格驗證尾隨線

exact和strict

暫時沒使用過,等使用過之後再補充

<Switch>

<Switch>就像一個“開關”,它會在多個路由配置都可以匹配成功的時候只選擇第一個匹配上的渲染其UI,有的時候它也需要和exact配合使用,否則會有永遠匹配不上某個路由的情況發生。比如:

<Route path="/dataList" component={List}/>
<Route path="/dataList/:id" component={ListDetails}/>
<Route path="/error" component={Error} />
<Route path="*" render={() => <Redirect to="/error"/> }/>


<li><NavLink to="/dataList/4">Go to ListDetails</NavLink></li>
<li><NavLink to="/dataList">Go to List</NavLink></li>

List<Route>配置沒有加exact參數,所以在ListDetails頁也會渲染出List頁面,添加了<Switch>以後,根據<Switch>的工作原則,它只渲染第一個匹配成功的UI,這就會導致ListDetails永遠不會被渲染,而正確做法是給List添加exact:

<Switch>
        <Route exact path="/dataList" component={List}/>
        <Route path="/dataList/:id" component={ListDetails}/>
        <Route path="/error" component={Error} />
        <Route path="*" render={() => <Redirect to="/error"/> }/>
</Switch>

<Switch>有個很大的作用就是可以實現當所有路由都匹配不上的時候,可以顯示一個404頁面,也就是代碼中的Error頁,如上所示。


<Redirect>

重定向組件,它會從路由棧裏將當前路由替換爲它的路徑名,這也是它和<NavLink>的最大區別。

 <Route path="/error" component={Error} />
 <Route path="*" render={() => <Redirect to="/error"/> }/>

to和push

to屬性和<NavLink>to一樣,可以爲string,也可以爲object,爲string時就是location.pathname,爲object時就是location對象。

push屬性對應<NavLink>replace<Redirect>默認行爲是替換路由,而<NavLink>默認行爲是新增一個路由,pushreplace就是改變它們的默認行爲的參數。

from

指定一個路由名,當匹配到該路由時重定向到另一個路由上:

<Switch>
  <Redirect from="/hello" to="/user" />
  <Route path="/login" component={Login}/>
  <Route exact path="/user" render={() => <div>hello user</div>}/>
</Switch>

注:官網說指定的路由必須在裏面纔有效,但我測試了發現它的意思不是說from或者to的值都必須是裏面的的path指定過的(比如代碼中的/hello),而是如果你要使用from屬性則必須將其包含在裏面,否則頁面會報警告。並且要注意要寫在它想要替換的路由配置前面,否則不會生效。

小結

此處沒有列舉源碼的解析,因爲小編不是專業搞前端的,如果想看源碼解析的,去下面這個地方看吧,小編也是參考的這裏的內容。

參考內容

https://www.jianshu.com/p/875225b2ec90

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