react-router
本來想給大家教學react-router2.0的版本直晨。但是考慮到4.0的版本已經出現(xiàn)了章鲤。本著學新不學舊的原則莉给。今天來帶大家踩坑react-router4.0楷怒,react-routerV2垛吗,v3织堂。V3相對V2其實沒有什么改變叠艳,V2是一種面向切面的編程思想(AOP),而V4是一種萬物皆組件的思想(just component)易阳。V4和V2也大相徑庭附较。因此學了V2的同學可能要在思想有所轉變。
1.準備
- 創(chuàng)建項目
//創(chuàng)建項目
create-react-app react4demo
- 引入react-router
//引入react-router
npm install react-router --save
項目中的目錄是4.2.0
2.開始
先讓我們看一個小demo潦俺【芸危可能開始有些地方不理解,但是之后我會慢慢講解每個用到的知識點事示。讓我們從整體開始認識react-routerV4
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
const BasicExample = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
)
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
const About = () => (
<div>
<h2>About</h2>
</div>
)
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/components`}>
Components
</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Route path={`${match.url}/:topicId`} component={Topic}/>
<Route exact path={match.url} render={() => (
<h3>Please select a topic.</h3>
)}/>
</div>
)
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
)
export default BasicExample;
2.1包的選擇
react router v4 是對v3的重寫≡缦瘢現(xiàn)在分為三個包:
-
react-router
:只提供核心的路由和函數(shù)。一般的應用不會直接使用 -
react-router-dom
:供瀏覽器/Web應用使用的API肖爵。依賴于react-router卢鹦, 同時將react-router的API重新暴露(export)出來; -
react-router-native
:供 React Native 應用使用的API劝堪。同時將react-router的API重新暴露(export)出來冀自;
因此對于一般項目來說,我們其實只需要引入react-router-dom
就好了秒啦。如果你項目中存在老版本的v2,v3熬粗,需要你先刪除
npm uninstall react-router --save
npm install --save react-router-dom
2.2<Router>
和之前的Router不一樣,這里<Router>組件下只允許存在一個子元素帝蒿,如存在多個則會報錯荐糜。
/*錯誤的實例*/
<Router>
<ul>
<li><Link to="/">首頁</Link></li>
<li><Link to="/about">關于</Link></li>
<li><Link to="/topics">主題列表</Link></li>
</ul>
<hr/>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</Router>
同上面的例子不同的是,沒有了div的庇護葛超,這里就會報錯暴氏。
2.3<Route>
Route組件主要的作用就是當一個location匹配路由的path時,渲染某些UI绣张。示例如下:
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/ttt" component={Topic}/>
</div>
</Router>
// 如果應用的地址是/,那么相應的UI會類似這個樣子:
<div>
<Home/>
</div>
// 如果應用的地址是/ttt,那么相應的UI就會成為這個樣子:
<div>
<Topic/>
</div>
Route屬性
path(string): 路由匹配路徑答渔。(沒有path屬性的Route 總是會 匹配);
exact(bool):為true時侥涵,則要求路徑與location.pathname必須完全匹配沼撕;
strict(bool):true的時候宋雏,有結尾斜線的路徑只能匹配有斜線的location.pathname;
同時务豺,新版的路由為<Route>提供了三種渲染內容的方法:
-
<Route component>
:在地址匹配的時候React的組件才會被渲染磨总,route props也會隨著一起被渲染; -
<Route render>
:這種方式對于內聯(lián)渲染和包裝組件卻不引起意料之外的重新掛載特別方便笼沥; -
<Route children>
:與render屬性的工作方式基本一樣蚪燕,除了它是不管地址匹配與否都會被調用;
上面的例子是講的<Route component>,現(xiàn)在我們來說<Route render>
//就在源代碼中渲染奔浅。
<Route path="/home" render={() => <div>Home</div>}/>
// 包裝/合成
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)
<FadingRoute path="/cool" component={Something}/>
<Route component>的優(yōu)先級要比<Route render>高馆纳,所以不要在同一個<Route>中同時使用這兩個屬性。
2.4<Link>
也就是我們的跳轉屬性啦⌒阼耄現(xiàn)在我們看看Link的屬性
- to(string / object):要跳轉的路徑或地址鲁驶;
- replace :為 true 時,點擊鏈接后將使用新地址替換掉訪問歷史記錄里面的原地址舞骆;為 false 時钥弯,點擊鏈接后將在原有訪問歷史記錄的基礎上添加一個新的紀錄。默認為 false葛作;
/*這里一些關于Link的例子*/
//當to為string類型的時候
<Link to="/about">關于</Link>
//to為obj
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: { fromDashboard: true }
}}/>
// replace
<Link to="/courses" replace />
2.5<NavLink>
<NavLink>是<Link>的一個特定版本寿羞,會在匹配上當前URL的時候會給已經渲染的元素添加樣式參數(shù)猖凛。
我們自己如果手寫一個NavLink赂蠢,應該可以這樣去完成:只是封裝這層<NavLink>花了更多的心思去完成他的樣式封裝和功能封裝。
import React from 'react'
import { Link } from 'react-router'
export default React.createClass({
render() {
return <Link {...this.props} activeClassName="active"/>
}
})
//index.css
.active {
color: green;
}
讓我們來看下<NavLink>有什么屬性吧
- activeClassName(string):設置選中樣式辨泳,默認值為 active虱岂;
- activeStyle(object):當元素被選中時, 為此元素添加樣式;
- exact(bool):為 true 時, 只有當?shù)刂吠耆ヅ?class 和 style 才會應用菠红;
- strict(bool):為 true 時第岖,在確定位置是否與當前 URL 匹配時,將考慮位置 pathname 后的斜線试溯;
isActive(func):判斷鏈接是否激活的額外邏輯的功能蔑滓;
來看幾個簡單的demo
// activeClassName選中時樣式為selected
<NavLink
to="/faq"
activeClassName="selected"
>FAQs</NavLink>
// 選中時樣式為activeStyle的樣式設置
<NavLink
to="/faq"
activeStyle={{
fontWeight: 'bold',
color: 'red'
}}
>FAQs</NavLink>
// 當event id為奇數(shù)的時候,激活鏈接
const oddEvent = (match, location) => {
if (!match) {
return false
}
const eventID = parseInt(match.params.eventID)
return !isNaN(eventID) && eventID % 2 === 1
}
<NavLink
to="/events/123"
isActive={oddEvent}
>Event 123</NavLink>
2.6<Switch>
該組件用來渲染匹配地址的第一個<Route>或者<Redirect>遇绞。那么它與使用一堆route又有什么區(qū)別呢键袱?
<Switch>的獨特之處是獨它僅僅渲染一個路由。相反地摹闽,每一個包含匹配地址(location)的<Route>都會被渲染蹄咖。思考下面的代碼:
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
如果現(xiàn)在的URL是/about,那么<About>, <User>, 還有<NoMatch>都會被渲染付鹿,因為它們都與路徑(path)匹配澜汤。這種設計蚜迅,允許我們以多種方式將多個<Route>組合到我們的應用程序中,例如側欄(sidebars)俊抵,面包屑(breadcrumbs)谁不,bootstrap tabs等等。 然而徽诲,偶爾我們只想選擇一個<Route>來渲染拍谐。如果我們現(xiàn)在處于/about,我們也不希望匹配/:user(或者顯示我們的 “404” 頁面 )馏段。以下是使用 Switch 的方法來實現(xiàn)
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
現(xiàn)在轩拨,如果我們處于/about,<Switch>將開始尋找匹配的<Route>院喜。<Route path="/about"/> 將被匹配亡蓉, <Switch>將停止尋找匹配并渲染<About>。同樣喷舀,如果我們處于/michael砍濒,<User>將被渲染
以上是react-router的基礎。
3.URL參數(shù)
同樣的硫麻,我們先來看一個demo
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
const ParamsExample = () => (
<Router>
<div>
<h2>Accounts</h2>
<ul>
<li><Link to="/netflix">Netflix</Link></li>
<li><Link to="/zillow-group">Zillow Group</Link></li>
<li><Link to="/yahoo">Yahoo</Link></li>
<li><Link to="/modus-create">Modus Create</Link></li>
</ul>
<Route path="/:id" component={Child}/>
</div>
</Router>
)
const Child = ({ match }) => (
<div>
<h3>ID: {match.params.id}</h3>
</div>
)
export default ParamsExample
4.重定向
<Redirect>
組件用于路由的跳轉爸邢,即用戶訪問一個路由,會自動跳轉到另一個路由拿愧。
這里原本我們需要訪問protected杠河,由于沒登錄被跳轉登錄到login頁面。
看效果圖:
這是一個需要你登錄才能查看隱私內容浇辜。那么如何去實現(xiàn)呢券敌?
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100) // fake async
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
const AuthButton = withRouter(({ history }) => (
fakeAuth.isAuthenticated ? (
<p>
Welcome! <button onClick={() => {
fakeAuth.signout(() => history.push('/'))
}}>Sign out</button>
</p>
) : (
<p>You are not logged in.</p>
)
))
代碼中出現(xiàn)了withRouter這個又是什么呢?
首先withRouter是一個組件柳洋,withRouter可以包裝任何自定義組件待诅,將react-router 的 history,location,match 三個對象傳入。 無需一級級傳遞react-router 的屬性熊镣,當需要用的router 屬性的時候卑雁,將組件包一層withRouter,就可以拿到需要的路由信息
ok,得到了history(統(tǒng)一的API管理歷史堆棧绪囱、導航测蹲、確認跳轉、以及sessions間的持續(xù)狀態(tài))毕箍。在v3的時候弛房,我們想跳轉路徑,一般會這樣處理而柑。
- 我們從react-router導出browserHistory文捶。
- 我們使用browserHistory.push()等等方法操作路由跳轉荷逞。
例如:
import browserHistory from 'react-router';
export function addProduct(props) {
return dispatch =>
axios.post(`xxx`, props, config)
.then(response => {
browserHistory.push('/cart'); //這里
});
}
在v4我們直接操作history來進行路由棧的管理。history.push('/'))
默認調到該路由的主頁
ok,現(xiàn)在我們來看Login里的代碼
class Login extends React.Component {
state = {
redirectToReferrer: false
}
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
)
}
}
這里肯定有人會有疑問粹排,這里的from 是什么种远?打印出來是什么?
回答這個問題顽耳,我們先來看一下location
首先location是一個Object坠敷。當前訪問地址信息組成的對象,具有如下屬性:
- pathname: string URL路徑
- search: string URL中的查詢字符串
- hash: string URL的 hash 片段
- state: string 例如執(zhí)行 push(path, state) 操作時射富,location 的 state 將被提供到堆棧信息里膝迎。
打印出來剛剛代碼里的location
這里的from.pathname是 /protected 意思是什么?其實本應該跳轉到 ‘/protected’胰耗,但是redirect 因為你沒登錄攔截下來了限次。因此我們可以通過 this.props.location.state.from 來查看是否跳轉到成功的頁面。講到這里相信大家對重定向有了自己的理解柴灯。手敲一遍代碼是最方便也是最好的理解卖漫。
5.自定義鏈接
自定義鏈接的思路,其實就是對<Route>
和<Link>
進行封裝一層赠群。
const OldSchoolMenuLink = ({ label, to, activeOnlyWhenExact }) => (
<Route path={to} exact={activeOnlyWhenExact} children={({ match }) => (
<div className={match ? 'active' : ''}>
{match ? '> ' : ''}<Link to={to}>{label}</Link>
</div>
)}/>
)
要想理解這段代碼羊始,我們先了解match,
match 對象包含了 <Route path> 如何與 URL 匹配的信息查描,具有以下屬性:
- params: object 路徑參數(shù)突委,通過解析 URL 中的動態(tài)部分獲得鍵值對
- isExact: bool 為 true 時,整個 URL 都需要匹配
- path: string 用來匹配的路徑模式叹誉,用于創(chuàng)建嵌套的 <Route>
- url: string URL 匹配的部分鸯两,用于嵌套的 <Link>
在以下情境中可以獲取 match 對象
- 在 Route component 中,以 this.props.match獲取
- 在 Route render 中长豁,以 ({match}) => () 方式獲取
- 在 Route children 中,以 ({match}) => () 方式獲取
- 在 withRouter 中忙灼,以 this.props.match的方式獲取
- matchPath 的返回值
當該鏈接被點擊的時候匠襟,match就有了值,未被點擊的就是null该园。這時候可以根據(jù)match是否存在來進行判斷酸舍。當有的時候帅刀,className為active痕支,并且,在Link之前加上 > 符號
理解了match對于這里的理解就會顯得方便了多随闺。在項目中我們可以根據(jù)自己的需求封裝不同樣式不同外觀的造型双妨。
6.防止轉換
首先我們來看一下這里的效果圖
代碼:
<Prompt
when={isBlocking}
message={history => (
`Are you sure you want to go to ${history.pathname}`
)}
/>
//這里history 同樣可以 使用location.pathname 來獲得
其實這里的關鍵就 一個組件 Prompt 淮阐,我們來看一下它的屬性:
- message :提示用戶的一種方式叮阅。(可以得到 history ,match 泣特,location)
- when:作為一種觸發(fā)的方式 bool 類型
7.404 No match
之前上面是switch講的有點干癟浩姥,現(xiàn)在配合著具體的代碼來說
const NoMatchExample = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/old-match">Old Match, to be redirected</Link></li>
<li><Link to="/will-match">Will Match</Link></li>
<li><Link to="/will-not-match">Will Not Match</Link></li>
<li><Link to="/also/will/not/match">Also Will Not Match</Link></li>
</ul>
<Switch>
<Route path="/" exact component={Home}/>
<Redirect from="/old-match" to="/will-match"/>
<Route path="/will-match" component={WillMatch}/>
<Route component={NoMatch}/>
</Switch>
</div>
</Router>
)
const Home = () => (
<p>
A <code><Switch></code> renders the
first child <code><Route></code> that
matches. A <code><Route></code> with
no <code>path</code> always matches.
</p>
)
const WillMatch = () => <h3>Matched!</h3>
const NoMatch = ({ location }) => (
<div>
<h3>No match for <code>{location.pathname}</code></h3>
</div>
)
export default NoMatchExample
我們把switch 理解為 代碼中的 switch case 有匹配的則跳轉。<Route component={NoMatch}/>
放在最后的意義是状您,如果上訴都沒有匹配的勒叠,那么就跳轉到404頁面。
8.遞歸路徑
通常來說膏孟,遞歸路徑適用于項目中需要用到分級的地方眯分,比如,一級目錄柒桑,二級目錄颗搂,三級目錄這樣子。我們先來看一下項目的效果圖幕垦。
接下來丢氢,我們來解析源碼。
const PEEPS = [
{ id: 0, name: 'Michelle', friends: [ 1, 2, 3 ] },
{ id: 1, name: 'Sean', friends: [ 0, 3 ] },
{ id: 2, name: 'Kim', friends: [ 0, 1, 3 ], },
{ id: 3, name: 'David', friends: [ 1, 2 ] }
]
const find = (id) => PEEPS.find(p => p.id == id)
const RecursiveExample = () => (
<Router>
<Person match={{ params: { id: 0 }, url: '' }}/>
</Router>
)
const Person = ({ match }) => {
//console.log(match);
const person = find(match.params.id)
console.log(person);
return (
<div>
<h3>{person.name}’s Friends</h3>
<ul>
{person.friends.map(id => (
<li key={id}>
<Link to={`${match.url}/${id}`}>
{find(id).name}
</Link>
</li>
))}
</ul>
<Route path={`${match.url}/:id`} component={Person}/>
</div>
)
}
find 是ES6中的方法先改。用于找到第一個符合條件的數(shù)組成員并返回疚察。
每次點擊的時候,我們會傳遞id過去仇奶。因此可以使用“match” 查看我們的params 中的參數(shù)屬性貌嫡。通過id 再繼而判斷 找出是 find 的數(shù)據(jù)。
這里的知識點是告訴我們如何進行遞歸路徑的排序该溯。
9.邊欄
邊欄的代碼相對較簡單岛抄,這里我就不多過述
10.動畫轉化
首先來看效果圖
這里需要我們在項目導入
npm install react-transition-group --save
我們用到了CSSTransitionGroup 這個組件。它有這么些個屬性
transitionName="fade"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
更多的大家可以上github上查看 具體的文檔狈茉。
<div style={styles.content}>
<CSSTransitionGroup
transitionName="fade"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
>
{/* no different than other usage of
CSSTransitionGroup, just make
sure to pass `location` to `Route`
so it can match the old location
as it animates out
*/}
<Route
location={location}
key={location.key}
path="/:h/:s/:l"
component={HSL}
/>
</CSSTransitionGroup>
</div>
11.不明確匹配
知識點也同switch夫椭,這里就不過多述
12.路由配置
官方文檔里寫的路由配置,可以實際配置到項目中的氯庆。它把我們所有的路由情況加載到一個數(shù)組中蹭秋,通過數(shù)組去配置整個路由。
const routes = [
{ path: '/sandwiches',
component: Sandwiches
},
{ path: '/tacos',
component: Tacos,
routes: [
{ path: '/tacos/bus',
component: Bus
},
{ path: '/tacos/cart',
component: Cart
}
]
}
]
如果有多個路由堤撵,routes繼續(xù)遞增仁讨。我們可以看到routes[1]的這個路由對象,它的路由一級目錄是'/tacos' 在‘/tacos‘下還有二級目錄实昨。分別是'/tacos/bus'和'/tacos/cart'洞豁。因此每次我們需要增加路由的時候,只需要在routes這個數(shù)組中完成配置,并不需要添加額外的組件丈挟,同時也減少了代碼的耦合刁卜,增加了可讀性,這是非常關鍵的一點礁哄。
const RouteWithSubRoutes = (route) =>
{
console.log(route);
return(
<Route path={route.path} render={props => (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes}/>
)}/>
)}
上面這個是對路由的一次封裝长酗。
const RouteConfigExample = () => (
<Router>
<div>
<ul>
<li><Link to="/tacos">Tacos</Link></li>
<li><Link to="/sandwiches">Sandwiches</Link></li>
</ul>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route}/>
))}
</div>
</Router>
)
使用的時候,我們只需要對數(shù)組進行一次map遍歷桐绒。按照上面封裝的方法依次加載所需展示的路由夺脾。
與此同時我也在思考一個問題?
我們使用標簽的方式去展示路由茉继,自然是沒有問題的咧叭,但是?假設項目很大烁竭。我們使用webpack對項目進行打包菲茬,webpack是把所有的文件都打包在一個main.***.js的文件中,那么加載首頁的時候的派撕,就需要花費大量的的時間去加載這么大的文件婉弹,豈不是很耗時間?
如何改善终吼?
按需加載镀赌。。也可以叫延遲加載际跪。 這里有一份簡書的文檔可以參考(作者:zhangpei),里面的內容值得深入琢磨商佛,在這里不做討論,有興趣的可以研究研究姆打。
demo地址-github
參考
作者:阮一峰
鏈接:http://www.ruanyifeng.com/blog/2016/05/react_router.html?utm_source=tool.lu
react-router 官網(wǎng)
鏈接 :https://reacttraining.com/react-router/web/example/url-params
作者:桂圓_noble
鏈接:http://www.reibang.com/p/6a45e2dfc9d9
來源:簡書
參考博文鏈接:http://blog.csdn.net/sinat_17775997/article/details/69218382