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'))
})