React Router系列分爲三個部分,React Router基本用法,React Router從V2/V3到V4的變化,React Router實現原理。javascript
下面未說明的都指的是React Router V4,用到的包是
react-router-dom
html
路由的基本原理就是保證view和url同步,React-Router
有下面一些特色java
聲明式的路由react
跟react同樣,咱們能夠聲明式的書寫router,能夠用JSX
語法git
嵌套路由及路徑匹配github
支持多種路由切換方式web
能夠用hashchange
或者history.putState
。redux
hashChange
的兼容性較好,但在瀏覽器地址欄顯示#看上去會很醜;並且hash記錄導航歷史不支持location.key
和location.state
。hashHistory
即hashChange
的實現。segmentfault
history.putState
能夠給咱們提供優雅的url,但須要額外的服務端配置解決路徑刷新問題;browserHistory
即history.pushState
的實現。api
由於兩種方式都有優缺點,咱們能夠根據本身的業務需求進行挑選,這也是爲何咱們的路由配置中須要從react.router
引入browserHistory
並將其看成props傳給Router。
react—router
實現了路由的核心功能,在V4以前(V2,V3)可使用它,而react-router-dom
基於react-router
,加入了再瀏覽器環境下的一些功能,例如Link組件
,BroswerRouter和HashRouter組件
這類的DOM類組件,因此若是用到DOM綁定就使用react-router-dom
,實際上react-router
是react-router-dom
的子集,因此在新版本中咱們使用react-router-dom
就好了,不須要使用react-router
.react-router-native
在React Native中用到。
react-router-redux沒有集成進來
React-Router
的API主要有
BrowserRouter | HashRouter | MemoryRouter | StaticRouter |
---|---|---|---|
Link | NavLink | Redirect | Prompt |
Route | Router | Swith |
這些組件的具體用法能夠在react-router官網和segmentfault一篇文章查看,這裏對它們作個總結:
BrowserRouter
使用HTML5提供的History api(putState,replaceState和popState事件)
來保持UI和URL的同步.
HashRouter
使用URL的hash部分(即window.location.hash)
來保持UI和URL的同步;HashRouter
主要用於支持低版本的瀏覽器,所以對於一些新式瀏覽器,咱們鼓勵使用BrowserHistory
。
MemoryRouter
將歷史記錄保存在內存中,這個在測試和非瀏覽器環境中頗有用,例如react native
。
StaticRouter
是一個永遠不會改變位置的Router,這在服務端渲染場景中很是有用,由於用戶實際上沒有點擊,因此位置時間上沒有發生變化。
NavLink
與Link
的區別主要在於,前者會在與URL匹配時爲呈現要素添加樣式屬性。
Route
是這些組件中重要的組件,它的任務就是在其path屬性
與某個location
匹配時呈現一些UI。
對於Router
,通常程序只會使用其中一個高階Router
,包括BrowserRouter,HashRouter,MemoryRouter,NativeRouter和StaticRouter
;
Switch
用於渲染與路徑匹配的第一個Route
或Redirect
。
兩個頁面home
和detail
//home.js
import React from 'react
export default class Home extends React.Component {
render(){
return (
<div>
<a>調轉到detail頁面</a>
</div>
)
}
}
複製代碼
//detail.js
import React from 'react'
export default class Home extends React.Component {
render(){
return(
<div> <a>跳轉到home頁面</a> </div>
)
}
}
複製代碼
//Route.js
import React from 'react'
import {HashRouter, Route, Switch} from 'react-router-dom'
import Home from '../home'
import Detail from '../detail'
const BasicRoute = () => (
<HashRouter>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/detail' component={Detail} />
</Switch>
</HashRouter>
)
export default BasicRoute;
複製代碼
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Router from './router/router'
ReactDOM.render(
<Router/>,
document.getElementById('root')
)
複製代碼
修改home.js
和detail.js
//home.js
import React from 'react'
export default class Home extends React.Component {
render(){
return(
<div> <a href='#/detail'>跳轉到detail頁面</a> </div>
)
}
}
複製代碼
//detail.js
import React from 'react';
export default class Home extends React.Component {
render() {
return (
<div> <a href='#/'>回到home</a> </div>
)
}
}
複製代碼
首先須要修改router.js
中的代碼
...
import {HashRouter, Route, Switch, hashHistory} from 'react-router-dom';
...
<HashRouter history={hashHistory}>
...
複製代碼
而後在home.js
中
export default class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div> <a href='#/detail'>去detail</a> <button onClick={() => this.props.history.push('detail')}>經過函數跳轉</button> </div>
)
}
}
複製代碼
不少場景下,咱們還須要在頁面跳轉的同時傳遞參數,在react-router-dom
中,一樣提供了兩種方式進行傳參:
url傳參和經過push函數隱式傳參
修改route.js中的代碼
...
<Route exact path="/detail/:id" component={Detail}/>
...
複製代碼
而後修改detail.js,使用this.props.match.params來獲取url傳過來的參數
...
componentDidMount() {
console.log(this.props.match.params);
}
...
複製代碼
在地址欄輸入「http://localhost:3000/#/detail/3」,打開控制檯:
修改home.js
import React from 'react';
export default class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div> <a href='#/detail/3'>去detail</a> <button onClick={() => this.props.history.push({ pathname: '/detail', state: { id: 3 } })}>經過函數跳轉</button> </div>
)
}
}
複製代碼
在detai.js中,就可使用this.props.location.state獲取home傳過來的參數
componentDidMount() {
//console.log(this.props.match.params);
console.log(this.props.history.location.state);
}
複製代碼
跳轉後打開控制檯能夠看到參數被打印:
有些場景下,重複使用push或a標籤跳轉會產生死循環,爲了不這種狀況出現,react-router-dom提供了replace。在可能會出現死循環的地方使用replace來跳轉:
this.props.history.replace('/detail');
複製代碼
場景中須要返回上級頁面的時候使用:
this.props.history.goBack();
複製代碼
React Router V4 實現了動態路由。
對於大型應用來講,一個首當其衝的問題就是所需加載的JavaScript的大小。程序應當只加載當前渲染頁所需的JavaScript。有些開發者將這種方式稱之爲「代碼分拆」 —— 將全部的代碼分拆成多個小包,在用戶瀏覽過程當中按需加載。React-Router 裏的路徑匹配以及組件加載都是異步完成的,不只容許你延遲加載組件,而且能夠延遲加載路由配置。Route能夠定義 getChildRoutes,getIndexRoute 和 getComponents 這幾個函數。它們都是異步執行,而且只有在須要時才被調用。咱們將這種方式稱之爲 「逐漸匹配」。 React-Router 會逐漸的匹配 URL 並只加載該URL對應頁面所需的路徑配置和組件。
const CourseRoute = {
path: 'course/:courseId',
getChildRoutes(location, callback) {
require.ensure([], function (require) {
callback(null, [
require('./routes/Announcements'),
require('./routes/Assignments'),
require('./routes/Grades'),
])
})
},
getIndexRoute(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Index'))
})
},
getComponents(location, callback) {
require.ensure([], function (require) {
callback(null, require('./components/Course'))
})
}
}
複製代碼
若是咱們給/
,/category
和/products
建立了路由,但若是咱們想要/category/shoes
,/category/boots
,/category/footwear
這種形式的url呢?在React Router V4
以前的版本中,咱們的作法是利用Route組件的上下層嵌套:
<Route exact path="/" component={Home}/>
<Route path="/category" component={Category}/>
<Route path='/category/shoes' component={Shoes}/>
<Route path='/category/boots' component={Boots}/>
<Route path='/category/footwear' component={Footwear}/>
<Route path="/products" component={Products}/>
複製代碼
那麼在V4版本中該怎麼實現嵌套路由呢,咱們能夠將嵌套的路由放在父元素裏面定義。
//app.js
import React, { Component } from 'react';
import { Link, Route, Switch } from 'react-router-dom
import Category from './Category'
class App extends Component {
render(){
return(
<div>
<nav className="navbar navbar-light">
<ul className="nav navbar-nav">
<li><Link to="/">Homes</Link></li>
<li><Link to="/category">Category</Link></li>
<li><Link to="/products">Products</Link></li>
</ul>
</nav>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/category" component={Category}/>
<Route path="/products" component={Products}/>
</Switch>
</div>
)
}
}
export default App;
複製代碼
//Category.jsx
import React from 'react';
import { Link, Route } from 'react-router-dom';
const Category = ({ match }) => {
return( <div> <ul> <li><Link to={`${match.url}/shoes`}>Shoes</Link></li> <li><Link to={`${match.url}/boots`}>Boots</Link></li> <li><Link to={`${match.url}/footwear`}>Footwear</Link></li> </ul> <Route path={`${match.path}/:name`} render= {({match}) =>( <div> <h3> {match.params.name} </h3></div>)}/> //嵌套路由 </div>) } export default Category; 複製代碼
咱們須要理解上面的match
對象,當路由路徑和當前路徑成功匹配時會產生match對象,它有以下屬性:
Link
路徑Route
路徑Path-to-RegExp
包從URL解析測鍵值對注意match.url
和match.path
沒有太大區別,控制檯常常出現相同的輸出,例如訪問/user
const UserSubLayout = ({ match }) => {
console.log(match.url) // output: "/user"
console.log(match.path) // output: "/user"
return (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path={match.path} exact component={BrowseUsersPage} />
<Route path={`${match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>
)
}
//注意這裏match在組件的參數中被解構,意思就是咱們可使用match.path代替props.match.path
複製代碼
通常的,咱們在構建Link
組件的路徑時用match.url
,在構建Route
組件的路徑時用match.path
還有一個地方須要理解的是Route
組件有三個能夠用來定義要渲染內容的props:
router
會將傳遞的組件使用React.createElement
來生成一個React元素renderprop
指望一個函數返回一個元素childrenprop
跟render
很相似,也指望一個函數返回一個React元素。然而,無論路徑是否匹配,children都會渲染。一個真實的路由應該是根據數據,而後動態顯示。假設咱們獲取了從服務端API返回的product數據,以下所示
//Product.jsx
const productData = [
{
id: 1,
name: 'NIKE Liteforce Blue Sneakers',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.',
status: 'Available'
},
{
id: 2,
name: 'Stylised Flip Flops and Slippers',
description: 'Mauris finibus, massa eu tempor volutpat, magna dolor euismod dolor.',
status: 'Out of Stock'
},
{
id: 3,
name: 'ADIDAS Adispree Running Shoes',
description: 'Maecenas condimentum porttitor auctor. Maecenas viverra fringilla felis, eu pretium.',
status: 'Available'
},
{
id: 4,
name: 'ADIDAS Mid Sneakers',
description: 'Ut hendrerit venenatis lacus, vel lacinia ipsum fermentum vel. Cras.',
status: 'Out of Stock'
},
];
複製代碼
咱們須要根據下面這些路徑建立路由:
/products
. 這個路徑應該展現產品列表。/products/:productId
.若是產品有:productId
,這個頁面應該展現該產品的數據,若是沒有,就該展現一個錯誤信息。//Products.jsx
const Products = ({ match }) => {
const productsData = [
{
id: 1,
name: 'NIKE Liteforce Blue Sneakers',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.',
status: 'Available'
},
//Rest of the data has been left out for code brevity
];
/* Create an array of `<li>` items for each product*/
var linkList = productsData.map( (product) => {
return(
<li> <Link to={`${match.url}/${product.id}`}> {product.name} </Link> </li>
)
})
return(
<div>
<div>
<div>
<h3> Products</h3>
<ul> {linkList} </ul>
</div>
</div>
<Route path={`${match.url}/:productId`}
render={ (props) => <Product data= {productsData} {...props} />}/>
<Route exact path={match.url}
render={() => (
<div>Please select a product.</div>
)}
/>
</div>
)
}
複製代碼
下面是Product組件的代碼
//Product.jsx
const Product = ({match,data}) => {
var product= data.find(p => p.id == match.params.productId);
var productData;
if(product)
productData = <div> <h3> {product.name} </h3> <p>{product.description}</p> <hr/> <h4>{product.status}</h4> </div>;
else
productData = <h2> Sorry. Product doesnt exist </h2>;
return (
<div> <div> {productData} </div> </div>
)
}
複製代碼
考慮到這樣一個場景,用戶必須先驗證登陸狀態才能進入到主頁,因此須要保護式路由,這裏須要保護的路由是Admin,若是登陸沒經過則先進入Login路由組件。保護式路由會用到重定向組件Redirect,若是有人已經註銷了帳戶,想進入/admin
頁面,他們會被重定向到/login
頁面。當前路徑的信息是經過state傳遞的,若用戶信息驗證成功,用戶會被重定向回初始路徑。在子組件中,你能夠經過this.props.location.state
獲取state的信息。
`<Redirect to={{pathname: '/login', state: {from: props.location}}} />`
複製代碼
具體地,咱們須要自定義路由來實現上面的場景
class App5 extends React.Component {
render(){
return (
<div className="app5">
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/category'>Category</Link>
</li>
<li>
<Link to='/products'>Products</Link>
</li>
<li>
<Link to='/admin'>Admin</Link>
</li>
</ul>
<Route exact path='/' component={Home} />
<Route path='/category' component={Category} />
<Route path='/products' component={Products} />
<Route path='/login' component={Login} />
{/*自定義路由*/}
<PrivateRoute path='/admin' component={Admin} />
</div>
)
}
}
const Home = props => <h2>This is Home {console.log('Home-Props')}{console.log(props)}</h2>
const Admin = () => <h2>Welcome to admin!</h2>
// 自定義路由
const PrivateRoute = (({component:Component,...rest}) => {
return (
<Route
{...rest}
render={props =>
// 若是登陸驗證經過則進入Admin路由組件
fakeAuth.isAuthenticated === true
?(<Component />)
// 將from設置爲Admin路由pathname,並傳遞給子組件Login
:(<Redirect to={{pathname:'/login',state:{from:props.location.pathname}}} />)
}
/>
)
})
複製代碼
Login組件實現以下,主要就是經過this.props.location.state.from
來記住是從哪一個頁面跳轉過來的,而後若是toAdmin
爲false
的話就要進行登陸,登陸後將toAdmin
設爲true
,爲true
就是進行重定向跳轉到原來的頁面<Redirect to={from} />
class Login extends React.Component {
constructor(){
super()
this.state = {
toAdmin:false
}
}
login = () =>{
fakeAuth.authenticate(() => {
this.setState({
toAdmin:true
})
})
}
render(){
const from = this.props.location.state.from
const toAdmin = this.state.toAdmin
if(toAdmin) {
return (
<Redirect to={from} /> ) } return ( <div className="login"> {console.log(this.props)} <p>You must log in then go to the{from} </p> <button onClick={this.login}> Log in </button> </div> ) } } export default Login export const fakeAuth = { // 驗證狀態 isAuthenticated:false, authenticate(cb){ this.isAuthenticated = true setTimeout(cb,100) } } 複製代碼
參考文章: