React Router 主要使用场景是单页面富应用(SPA)。实现有两种,一种是利用url的hash,就是常说的锚点(#),JavaScript通过hashChange事件来监听url的改变;另一种是HTML5的History模式,它使url看起来像普通网站那样,以“ / ”分割,没有#,不过这种模式需要服务端支持,当url所指向的位置在服务端中目录结构错误时,将报404。
React Router 引入有两种方式,react-router 和 react-router-dom,大多数时候,我们使用的是react-router-dom包,因为它包括了一些便于路由实现的DOM组件,如NavLink。react-router-dom初始化语句如下:
$ npm install react-router-dom -S
要学会使用react-router-dom,必须理解好 HashRouter、BrowserRouter、NavLink、Link、Route、Switch、Redirect、 Prompt这些概念。
Router Demo:https://github.com/smallH/react-router-demo.git
那么现在一步步来吧,下面是一个路由的实现代码:
<HashRouter basename="/">
<div>
<div>
<ul>
<li><NavLink exact to="/" activeClassName="selected">First</NavLink></li>
<li><NavLink to='/second/002' activeClassName="selected">Second</NavLink></li>
<li><NavLink to={{
pathname: '/third',
search: '?search=userinfo',
state: { id: '003' }
}} activeClassName="selected">Third</NavLink></li>
</ul>
</div>
<div>
<Switch>
<Route exact path="/" component={First}></Route>
<Route path="/second/:id" component={Second}></Route>
<Route path="/third" component={Third}></Route>
</Switch>
</div>
</div>
</HashRouter>
该路由实现了单页面的导航栏效果,内容包括了First、Second和Third三个页面,当点击不同按钮时会切换不同的页面。
如果现在看不懂也没关系,React Router说难也难,说简单也简单,关键还是要学会几个常用的API ,所以接下来通过重点介绍React Router的API来入门学习。
API官网:https://reacttraining.com/react-router/
<BrowserRouter>
<BrowserRouter> 使用 HTML5 提供的 history API (pushState, replaceState 和 popstate 事件) 来保持 UI 和 URL 的同步。
import { BrowserRouter } from 'react-router-dom'
<BrowserRouter
basename={optionalString}
forceRefresh={optionalBool}
getUserConfirmation={optionalFunc}
keyLength={optionalNumber}
>
<App/>
</BrowserRouter>
-
basename: string
所有位置的基准 URL。如果你的应用程序部署在服务器的子目录,则需要将其设置为子目录。basename 的正确格式是前面有一个前导斜杠,但不能有尾部斜杠。
<BrowserRouter basename="/calendar">
<Link to="/today" /> // 最终渲染为 <a href="/calendar/today" />
</BrowserRouter>
-
forceRefresh: bool
如果为true,在导航的过程中整个页面将会刷新。一般情况下,只有在不支持 HTML5 history API 的浏览器中使用此功能。
const supportsHistory = 'pushState' in window.history;
<BrowserRouter forceRefresh={!supportsHistory} />
-
getUserConfirmation: func
用于确认导航的函数,默认使用window.confirm。例如,当从 /a 导航至 /b 时,会使用默认的 confirm 函数弹出一个提示,用户点击确定后才进行导航,否则不做任何处理。
const getConfirmation = (message, callback) => {
const allowTransition = window.confirm(message);
callback(allowTransition); // 回调询问结果
}
<BrowserRouter getUserConfirmation={getConfirmation('是否允许使用路由?', myCallBack)} />
-
keyLength: number
location.key 的长度,默认为 6,每个页面都有一个location.key。
<BrowserRouter keyLength={12} />
-
children: node
呈现的单个子元素或DOM组件。
<HashRouter>
<HashRouter> 使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步。
import { HashRouter } from 'react-router-dom';
<HashRouter>
<App />
</HashRouter>
-
basename: string
同上 <BrowserRouter>
-
getUserConfirmation: func
同上 <BrowserRouter>
-
hashType: string
window.location.hash使用的hash类型,有如下几种:
- slash - 后面跟一个斜杠,例如 #/ 和 #/sunshine/lollipops
- noslash - 后面没有斜杠,例如 # 和 #sunshine/lollipops
- hashbang - Google 风格的 ajax crawlable,例如 #!/ 和 #!/sunshine/lollipops
默认为 slash。
-
children: node
呈现的单个子元素或DOM组件。
(贴士)关于选择HashRouter还是BrowserRouter?
虽然官网中推荐使用BrowserRouter,但出于个人实际项目开发经验及兼容性的考虑,还是建议使用HashRouter。首先BrowserRouter是基于HTML5的,对旧式的浏览器不一定支持,其次,BrowserRouter 在build后,打包出来的文件路由是对服务器的目录是有严格要求的,比如把打包出来的文件放到了服务器的root/project/目录下,若在BrowserRouter 中没有配置基准 URL,将会访问不到页面资源。有人说为什么在本地开发是没有问题的?那是因为在本地开发时,webpack.config.js中使用 webpack-dev-server 已经做了配置。
<Link>
为你的应用提供声明式的、可访问的导航链接。
import { Link } from 'react-router-dom';
<Link to="/about">About</Link>
-
to: string
一个字符串形式的链接地址,通过 pathname
、search
和 hash
属性创建,其中search
和 hash可以为空
。
<Link to='/courses?sort=name#the-hash' />
-
to: object
一个对象形式的链接地址,可以具有以下任何属性:
- pathname - 要链接到的路径
- search - 查询参数
- hash - URL 中的 hash,例如 #the-hash
- state - 存储到 location 中的额外状态数据
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: {
fromDashboard: true
}
}} />
-
replace: bool
当设置为 true
时,点击链接后将替换历史堆栈中的当前条目,而不是添加新条目。默认为 false
。
<Link to="/courses" replace />
比如,从页面 '/a' 跳到 '/b',再跳到带replace属性的'/c',此时从'/c'点击浏览器的返回上一页,将直接返回到'/a' ,'/b' 会被忽略。
-
innerRef: func
允许访问组件的底层引用。
const refCallback = node => {
// node 指向最终挂载的 DOM 元素,在卸载时为 null
}
<Link to="/" innerRef={refCallback} />
-
others
还可以传递一些其它属性,例如 title
、id
或 className
等。
<Link to="/" className="nav" title="a title">About</Link>
<NavLink>
一个特殊版本的 <Link>
,它会在与当前 URL 匹配时为其呈现元素添加样式属性。
import { NavLink } from 'react-router-dom';
<NavLink to="/about">About</NavLink>
-
activeClassName: string
当元素处于激活状态时应用的类,默认为 active
。它将与 className
属性一起使用。
<NavLink to="/faq" activeClassName="selected">FAQs</NavLink>
-
activeStyle: object
当元素处于激活状态时应用的样式。
const activeStyle = {
fontWeight: 'bold',
color: 'red'
};
<NavLink to="/faq" activeStyle={activeStyle}>FAQs</NavLink>
-
exact: bool
如果为 true
,则只有在位置完全匹配时才应用激活类/样式。
<NavLink exact to="/profile">Profile</NavLink>
-
isActive: func
添加额外逻辑以确定链接是否处于激活状态的函数。如果你要做的不仅仅是验证链接的路径名与当前 URL 的路径名相匹配,那么应该使用它。
// 只有当事件 id 为奇数时才考虑激活
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>
-
strict: bool
如果为 true
,则在确定位置是否与当前 URL 匹配时,将考虑位置的路径名后面的斜杠。
<NavLink strict to="/one/">Events</NavLink>
<Route>
<Route>是指与<Link>的to设置的路径匹配时,显示其指定的组件。
注意,<Route path='/'>表示起始页,它与所有的<Link>都会匹配到,如:
<HashRouter basename="/">
<div>
<div>
<ul>
<li><NavLink to="/" activeClassName="selected">First</NavLink></li>
<li><NavLink to='/second' activeClassName="selected">Second</NavLink></li>
</ul>
</div>
<div>
<Route path="/" component={First}></Route>
<Route path="/second" component={Second}></Route>
</div>
</div>
</HashRouter>
当点击Second时,组件First、Second都会显示出来
这明显不是我们想要的结果,这时就需要用到exact和<switch>实现严格模式匹配和只渲染一次。
Route有三种组件的渲染方式:
- <Route component>
- <Route render>
- <Route children>
三种渲染方式都将提供相同的Route Props:
- match
- location
- history
如组件里可以通过this.props.match获取匹配信息;通过this.props.history.push(path)实现路由页面跳转,若需传递参数。path可以换成Object,参数与<Link>中to传递对象参数相同。
-
component
指定只有当位置匹配时才会渲染的 React 组件,该组件会接收 route props 作为属性。
const User = ({ match }) => {
return <h1>Hello {match.params.username}!</h1>
}
<Route path="/user/:username" component={User} />
当你使用 component 时,Router将根据指定的组件,使用 React.createElement 创建一个新的 React 元素。
-
render: func
使用 render 可以方便地进行内联渲染和包装,而无需进行上文解释的不必要的组件重装。这种写法有一个好处就是可以很方便地在切换页面时实现一些动画效果。
// AnimateComp 为切换动画效果组件
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<AnimateComp>
<Component {...props} />
</AnimateComp>
)} />
)
<FadingRoute path="/second" component={Second} />
-
children: func
与render效果一样,只是写法不同。
<Route path="/second" children={({ match, ...rest }) => (
<AnimateComp>
{match && <Second {...rest}/>}
</AnimateComp>
)}/>
-
path: string
有效的 URL 路径,没有定义 path 的 <Route> 总是会被匹配。
<Route path="/users/:id" component={User} />
-
exact: bool
严格匹配,如果为 true
,则只有在 path
完全匹配 location.pathname
时才匹配,即Route中的path和Link中的to的路径必须一致时,该Route才会被渲染。
-
strict: bool
同上<NavLink> srict效果。
-
sensitive: bool
如果为 true
,进行匹配时将区分大小写。
<Switch>
用于渲染与路径匹配的第一个子 <Route>
或 <Redirect>
。<Switch> 只会渲染一个路由。相反,仅仅定义一系列 <Route> 时,每一个与路径匹配的 <Route> 都将包含在渲染范围内。考虑如下代码:
<Route path="/about" component={About} />
<Route path="/:user" component={User} />
<Route component={NoMatch} />
如果 URL 是 /about,那么 <About>、<User> 和 <NoMatch> 将全部渲染,因为它们都与路径匹配,其中第二个 '/:user' 等同于 '/*' ,第三个没有设置path,可以与任何路径匹配。这种方式通过设计的,允许我们以很多方式将 <Route> 组合成我们的应用程序,例如侧边栏和面包屑、引导标签等。
但是,有时候我们只想选择一个 <Route> 来呈现。比如我们在 URL 为 /about 时不想匹配 /:user(或者显示我们的 404 页面),这该怎么实现呢?以下就是如何使用 <Switch> 做到这一点:
import { Switch, Route } from 'react-router';
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/:user" component={User} />
<Route component={NoMatch} />
</Switch>
现在,当我们在 /about 路径时,<Switch> 将开始寻找匹配的 <Route>。我们知道,<Route path="/about" /> 将会被正确匹配,这时 <Switch> 会停止查找匹配项并立即呈现 <About>。同样,如果我们在 /michael 路径时,那么 <User> 会呈现。
<Redirect>
使用 <Redirect>
会导航到一个新的位置。新的位置将覆盖历史堆栈中的当前条目,例如服务器端重定向(HTTP 3xx)。
import { Route, Redirect } from 'react-router-dom';
<Route exact path="/" render={() => (
loggedIn ? (
<Redirect to="/dashboard" />
) : (
<PublicHomePage />
)
)} />
-
to: string
要重定向到的 URL。
<Redirect to="/somewhere/else" />
-
to: object
要重定向到的对象。
<Redirect to={{
pathname: '/login',
search: '?utm=your+face',
state: {
referrer: currentLocation
}
}} />
上例中的 state 对象可以在重定向到的组件中通过 this.props.location.state 进行访问。
-
push: bool
如果为 true
,重定向会将新的位置推入历史记录,而不是替换当前条目。
<Redirect push to="/somewhere/else" />
-
from: string
只能在 <Switch>
组件内使用 <Redirect from>
,以匹配一个位置。
<Switch>
<Redirect from='/old-path' to='/new-path' />
<Route path='/new-path' component={Place} />
</Switch>
-
exact: bool
当为true时,表示严格模式匹配。
-
strict: bool
同上<NavLink>。
<Prompt>
用于在位置跳转之前给予用户一些确认信息。当你的应用程序进入一个应该阻止用户导航的状态时(比如表单只填写了一半),弹出一个提示。
import { Prompt } from 'react-router-dom';
<Prompt
when={formIsHalfFilledOut}
message="你确定要离开当前页面吗?"
/>
-
message: string
当用户试图离开某个位置时弹出的提示信息。
<Prompt message="你确定要离开当前页面吗?" />
-
message: func
将在用户试图导航到下一个位置时调用。需要返回一个字符串以向用户显示提示,或者返回 true
以允许直接跳转。
<Prompt message={location => {
const isApp = location.pathname.startsWith('/app');
return isApp ? `你确定要跳转到${location.pathname}吗?` : true;
}} />
-
when: bool
在应用程序中,你可以始终渲染 <Prompt>
组件,并通过设置 when={true}
或 when={false}
以阻止或允许相应的导航,而不是根据某些条件来决定是否渲染 <Prompt>
组件。
<Prompt when={true} message="你确定要离开当前页面吗?" />