React Router 4.0 (以下簡稱 RR4) 已經正式發佈,它遵循React的設計理念,即萬物皆組件。所以 RR4 只是一堆 提供了導航功能的組件(還有若干對象和方法),具有聲明式(引入即用),可組合性的特點。
爲什麼要使用router?
使用的目的是在具有單頁面結構的優勢下,也能通過不同路徑匹配不同頁面,實現多頁面的效果。也就是說一個<route>
就是一個單獨的頁面。
本文檔內使用的是 react-router
4.2
安裝
只需安裝react-router-dom
npm install react-router-dom --save-dev
組件
總的樣例代碼:
import { BrowserRouter, Route } from 'react-router-dom'
const PrimaryLayout = () => (
<div className="primary-layout">
<header>
Our React Router 4 App
</header>
<main>
<Route path="/" exact component={HomePage} />
<Route path="/users" component={UsersPage} />
</main>
</div>
)
const HomePage =() => <div>Home Page</div>
const UsersPage = () => <div>Users Page</div>
const App = () => (
<BrowserRouter>
<PrimaryLayout />
</BrowserRouter>
)
render(<App />, document.getElementById('root'))
<BrowserRouter>
由於我們的應用程序是用於瀏覽器的,所以我們需要將最外層組件(例子中的App)封裝在來自 v4
的 BrowserRouter
中。
<Route>
他的作用就是當url中的地中與組件上的path相匹配的時候,渲染該組件內的UI界面。
與之前版本不同的是,之前使用{props.children}
來嵌套組件,現在直接使用<Route>
,而且<Route>
在哪裏編寫,如果路由匹配,子組件就在哪裏渲染。
該組件自帶3個render method
屬性:
1. component
:後面跟組件名稱,即當路徑匹配的時候渲染該component
組件;
2. render
:後面跟一個函數,進行內聯渲染,即當路徑匹配時,執行該函數並渲染其中的UI;
3. children
:後面跟函數,用的較少。
path屬性
path屬性規定了什麼時候渲染該route組件中的組件。
exact屬性
規定了當路由完全匹配path中的路由時,渲染相應組件。
<Switch>
之前的路由匹配都是排他性匹配,意味着一次只能匹配到一條路由;V4的路由匹配時包容性匹配,也就是說,多個<Route>
可以同時匹配和渲染。
例子中,我們試圖根據路徑渲染 HomePage
或者 sersPage
。如果從示例中刪除了 exact
屬性,那麼在瀏覽器中訪問 /users
時,HomePage
和 UsersPage
組件將同時被渲染。
在V4中實現排他性匹配非常簡單,只要使用<Switch>
組件就可以了。使用後,<Switch>
內部的各子組件一次只能匹配到一個路由。
如果各個子組件中均沒有exact
屬性,則匹配哪個子組件與其位置是有關的。例如下面這個例子:
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/users/add" component={UserAddPage} />
<Route path="/users" component={UsersPage} />
<Redirect to="/" />
</Switch>
我們在 /users
之前策略性地放置了 /users/add
的路由,以確保正確匹配。由於路徑 /users/add
將匹配 /users
和 /users/add
,所以最好先把 /users/add
放在前面。
當然,如果我們以某種方式使用 exact
,我們可以把它們放在任何順序上。
<Redirect>
這個組件就相當於一個跳轉,將當前地址導航到一個新的地址。在例子中的<Switch>
中只有在其他路由不匹配的情況下,纔會渲染重定向組件。
<Link>
作用就相當於一個a標籤,將頁面跳轉至指定鏈接。
例如:
<Link to="/courses" />
除了直接寫字符串,to
後接一個對象還能攜帶參數跳轉到指定路徑:
<Link to={{
pathname: '/course',
search: '?sort=name',
state: { price: 18 }
}} />
<NavLink>
這是 <Link>
的特殊版,顧名思義這就是爲頁面導航準備的。因爲導航需要有 “激活狀態”。
路由的嵌套
路由嵌套實現的方式有很多種,比較簡單的就是把子路由和其父路由並排寫在一起,如下所示:
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/users" exact component={BrowseUsersPage} />
<Route path="/users/:userId" component={UserProfilePage} />
<Route path="/products" exact component={BrowseProductsPage} />
<Route path="/products/:productId" component={ProductProfilePage} />
<Redirect to="/" />
</Switch>
</main>
上面的這種寫法不是很好,首先父路由的<Route>
必須要加上exact
屬性才能進行匹配渲染相應的組件;其次,子組件的的佈局應該是一致的(比如userId
爲1和2的組件佈局是一樣的),其中相同的內容會在子組件每一次生成的時候重新請求、渲染,造成了代碼的重複,也會使網絡流量造成不必要的浪費。
還有一種比較好的寫法是將子組件單拿出來,父組件包容性匹配路徑,再到子組件中在具體匹配,代碼如下所示:
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/users" component={UserSubLayout} /> //沒有exact屬性
<Route path="/products" component={ProductSubLayout} /> //沒有exact屬性
<Redirect to="/" />
</Switch>
</main>
//嵌套組件
const UserSubLayout = () => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path="/users" exact component={BrowseUsersPage} />
<Route path="/users/:userId" component={UserProfilePage} />
</Switch>
</div>
</div>
)
在上面代碼中,父級路由寫在了一起,路徑中沒有exact
屬性,做包容性匹配,而相應的子路由被單獨拿了出來,寫在了父級路由將要渲染的組件裏,進行再次的路由匹配。
有一點需要注意的是,即使我們在佈局結構中深入嵌套,路由仍然需要識別它們的完整路徑才能匹配。爲了節省重複輸入,改用 props.match.path
來代替path
中的字符串路徑:
const UserSubLayout = props => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path={props.match.path} exact component={BrowseUsersPage} />
<Route path={`${props.match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>
)
完整代碼
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter,HashRouter, Route, NavLink, Switch, Link} from 'react-router-dom';
const PageLayout = ()=> (
<div>
<HeaderLayout />
<div>
<Route path='/' exact component={Home} />
<Route path='/schedule' component={Schedule} />
<Route path='/player' component={Player} />
</div>
</div>
)
const HeaderLayout = () => (
<div className='header'>
<NavLink to='/' exact activeClassName="active" className>首頁</NavLink>
<NavLink to='/schedule' activeClassName="active" className>賽程</NavLink>
<NavLink to='/player' activeClassName="active" className>球員表</NavLink>
</div>
)
const Home =() => (
<h1>這裏是首頁</h1>
)
const Schedule = () => (
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
</ul>
)
const playerName = ['lbj','kb','kg','dw'];
const Player = ({match}) => (
<div>
<TopHeader member = {playerName} />
<div>
<Switch>
<Route path={match.path} exact render={() => <h1>這裏是Player首頁</h1> }/>
<Route path={`${match.path}/:member`} component={PlayerProfile}/>
</Switch>
</div>
</div>
)
const PlayerProfile = ({match}) => (
<h1 userid = {match.params.member}>this is {match.params.member}'s Subpage page!</h1>
)
class TopHeader extends React.Component{
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(event){
console.log(event.target.innerText);
console.log(location);
location.href += `/${event.target.innerText}`;
}
render(){
return(
<div>
<ul>
{this.props.member.map((item,key)=>(
// <p key = {key} onClick={this.handleClick}>{item}</p>
<li key = {key}><Link to={`/player/${item}`}>{item}</Link></li>
))}
</ul>
</div>
)
}
}
const App = () => (
<HashRouter>
<PageLayout />
</HashRouter>
)
ReactDOM.render(<App />,document.getElementById('root'));
注意:本例中使用的是HashRouter
,之前使用的是BrowserRouter
,由於其存在不能直接進入子路由,而且不能在子路由下刷新頁面,才換成的HashRouter
。
換上
<BrowserRouter>
之後會出現 2 個問題:如果你不是通過服務器啓動應用,因爲chrome自身的安全機制,在本地環境下根本不能用chrome玩。這個不關鍵,我本地測試換個瀏覽器還不行麼,本地起個服務器也不麻煩。
關鍵問題,刷新就是404。原因很簡單,BrowserRouter 和 HashRouter 完全不同,前者利用H5的 history 接口,前臺路由就是後臺收到的路由,你點到其他頁面一刷新,得,根本沒這個文件,服務器也很無辜,到底讓我渲染個啥?後臺也可以簡單的解決這個問題:甭管你請求的啥地址,我先把入口文件扔給你。node處理方式如下(需要express):
app.get('*',(request,response)=>{
response.sendFile(path.resolve(__dirname,'../index.html'))
})