react router
参考网站:http://react-guide.github.io/react-router-cn/docs/API.htmlapihttp://react-guide.github.io/react-router-cn/docs/API.html路由配置路由配置是一组指令,用来告诉 router 如何匹配 URL以及匹配后如何执行代码。添加首页想象一下当 URL 为/...
参考网站:API 文档 | React Router 中文文档
api
路由配置
路由配置是一组指令,用来告诉 router 如何匹配 URL以及匹配后如何执行代码。
添加首页
想象一下当 URL 为 /
时,我们想渲染一个在 App
中的组件。不过在此时,App
的 render
中的 this.props.children
还是 undefined
。这种情况我们可以使用 IndexRoute 来设置一个默认页面。
import { IndexRoute } from 'react-router'
const Dashboard = React.createClass({
render() {
return <div>Welcome to the app!</div>
}
})
React.render((
<Router>
<Route path="/" component={App}>
{/* 当 url 为/时渲染 Dashboard */}
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
让 UI 从 URL 中解耦出来
如果我们可以将 /inbox
从 /inbox/messages/:id
中去除,并且还能够让 Message
嵌套在 App ->Inbox
中渲染,那会非常赞。绝对路径可以让我们做到这一点。
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
{/* 使用 /messages/:id 替换 messages/:id */}
<Route path="/messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
在多层嵌套路由中使用绝对路径的能力让我们对 URL 拥有绝对的掌控。我们无需在 URL 中添加更多的层级,从而可以使用更简洁的 URL。
提醒:绝对路径可能在动态路由中无法使用
兼容旧的 URL
等一下,我们刚刚改变了一个 URL! 这样不好。 现在任何人访问 /inbox/messages/5
都会看到一个错误页面。:(
不要担心。我们可以使用 <Redirect> 使这个 URL 重新正常工作(重新跳转)。
import { Redirect } from 'react-router'
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="/messages/:id" component={Message} />
{/* 跳转 /inbox/messages/:id 到 /messages/:id */}
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
</Route>
</Router>
), document.body)
现在当有人点击 /inbox/messages/5
这个链接,他们会被自动跳转到 /messages/5
。 :raised_hands:
进入和离开的Hook
Route 可以定义 onEnter 和 onLeave 两个 hook ,这些hook会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。
import { Redirect } from 'react-router'
React.render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
// 绑定route hook
<Route path="/messages/:id" component={Message} onEnter="(nextState, replaceState)=> { replaceState(null, '/messages/' + nextState.params.id)" />
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
</Route>
</Router>
), document.body)
在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。
继续我们上面的例子,如果一个用户点击链接,从 /messages/5
跳转到 /about
,下面是这些 hook 的执行顺序:
/messages/:id
的onLeave
/inbox
的onLeave
/about
的onEnter
替换的配置方式(使用原生 route 数组对象代替)
因为 route 一般被嵌套使用,所以使用 JSX 这种天然具有简洁嵌套型语法的结构来描述它们的关系非常方便。然而,如果你不想使用 JSX,也可以直接使用原生 route 数组对象。
上面我们讨论的路由配置可以被写成下面这个样子:
const routeConfig = [
{ path: '/',
component: App,
indexRoute: { component: Dashboard },
childRoutes: [
{ path: 'about', component: About },
{ path: 'inbox',
component: Inbox,
childRoutes: [
{ path: '/messages/:id', component: Message },
{ path: 'messages/:id',
onEnter: function (nextState, replaceState) {
replaceState(null, '/messages/' + nextState.params.id)
}
}
]
}
]
}
]
React.render(<Router routes={routeConfig} />, document.body)
路由匹配原理
路由拥有三个属性来决定是否“匹配“一个 URL:
嵌套关系
React Router 使用路由嵌套的概念来让你定义 view 的嵌套集合,当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。嵌套路由被描述成一种树形结构。React Router 会深度优先遍历整个路由配置来寻找一个与给定的 URL 相匹配的路由。
路径语法
路由路径是匹配一个(或一部分)URL 的 一个字符串模式。大部分的路由路径都可以直接按照字面量理解,除了以下几个特殊的符号:
:paramName
– 匹配一段位于/
、?
或#
之后的 URL。 命中的部分将被作为一个参数()
– 在它内部的内容被认为是可选的*
– 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个splat
参数(下例就是匹配到 .* 为止)
<Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
如果一个路由使用了相对路径
,那么完整的路径将由它的所有祖先节点的路径
和自身指定的相对路径
拼接而成。使用绝对路径可以使路由匹配行为忽略嵌套关系。
优先级
最后,路由算法会根据定义的顺序自顶向下匹配路由。因此,当你拥有两个兄弟路由节点配置时,你必须确认前一个路由不会匹配后一个路由中的路径
。例如,千万不要这么做:
<Route path="/comments" ... />
<Redirect from="/comments" ... />
Histories
简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location
对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
常用的 history 有三种形式, 但是你也可以使用 React Router 实现自定义的 history。
browserHistory(浏览器history模式,类似vue的history模式)
hashHistory(hash模式)
createMemoryHistory(自定义history模式)
你可以从 React Router 中引入它们:
// JavaScript 模块导入(译者注:ES6 形式)
import { browserHistory } from 'react-router'
然后将它们传递给<Router>
:
render(
<Router history={browserHistory} routes={routes} />,
document.getElementById('app')
)
browserHistory
Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path
这样真实的 URL 。
服务器配置
服务器需要做好处理 URL 的准备。处理应用启动最初的 /
这样的请求应该没问题,但当用户来回跳转并在 /accounts/123
刷新时,服务器就会收到来自 /accounts/123
的请求,这时你需要处理这个 URL 并在响应中包含 JavaScript 应用代码(对每个路径都要有相应处理)。
IE8, IE9 支持情况
如果我们能使用浏览器自带的 window.history
API,那么我们的特性就可以被浏览器所检测到。如果不能,那么任何调用跳转的应用就会导致 全页面刷新,它允许在构建应用和更新浏览器时会有一个更好的用户体验,但仍然支持的是旧版的。
你可能会想为什么我们不后退到 hash history,问题是这些 URL 是不确定的。如果一个访客在 hash history 和 browser history 上共享一个 URL,然后他们也共享同一个后退功能,最后我们会以产生笛卡尔积数量级的、无限多的 URL 而崩溃。
hashHistory
Hash history 使用 URL 中的 hash(#
)部分去创建形如 example.com/#/some/path
的路由。
我应该使用 createHashHistory
吗?
Hash history 不需要服务器任何配置就可以运行,如果你刚刚入门,那就使用它吧。但是我们不推荐在实际线上环境中用到它,因为每一个 web 应用都应该渴望使用 browserHistory
。
像这样 ?_k=ckuvup
没用的在 URL 中是什么?(history和hash模式下参数的保存)
当一个 history 通过应用程序的 push
或 replace
跳转时,它可以在新的 location 中存储 “location state” 而不显示在 URL 中,这就像是在一个 HTML 中 post 的表单数据。
在 DOM API 中,这些 hash history 通过 window.location.hash = newHash
很简单地被用于跳转,且不用存储它们的location state。但我们想全部的 history 都能够使用location state,因此我们要为每一个 location 创建一个唯一的 key,并把它们的状态存储在 session storage 中。当访客点击“后退”和“前进”时,我们就会有一个机制去恢复这些 location state。
createMemoryHistory
Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。
和另外两种history的一点不同是你必须创建它,这种方式便于测试。
const history = createMemoryHistory(location)
实现示例
import React from 'react'
import { render } from 'react-dom'
import { browserHistory, Router, Route, IndexRoute } from 'react-router'
import App from '../components/App'
import Home from '../components/Home'
import About from '../components/About'
import Features from '../components/Features'
render(
<Router history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route path='features' component={Features} />
</Route>
</Router>,
document.getElementById('app')
)
默认路由(IndexRoute)与 IndexLink
场景:首页
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
现在 App
能够渲染 {this.props.children}
了值为home, 我们也有了一个最高层级的路由,使 Home
可以参与进来。
Index Links
如果你在这个 app 中使用 <Link to="/">Home</Link>
, 它会一直处于激活状态,因为所有的 URL 的开头都是 /
。 这确实是个问题,因为我们仅仅希望在 Home
被渲染后,激活并链接到它。
如果需要在 Home
路由被渲染后才激活的指向 /
的链接(只有当前的link是激活的),请使用 <IndexLink to="/">Home</IndexLink>
动态路由
对于大型应用来说,一个首当其冲的问题就是所需加载的 JavaScript 的大小。(采用按需加载,懒加载)
路由是个非常适于做代码分拆的地方:它的责任就是配置好每个 view。
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'),
])
})
// 或者使用import()
()=> {
Promise.all([
import('./routes/Announcements'),
import('./routes/Assignments'),
import('./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'))
})
}
}
跳转前确认
React Router 提供一个 routerWillLeave 生命周期钩子,这使得 React 组件可以拦截正在发生的跳转,或在离开 route 前提示用户。routerWillLeave 返回值有以下两种:
return false
取消此次跳转return
返回提示信息,在离开 route 前提示用户进行确认。
你可以在 route 组件 中引入 Lifecycle
mixin 来安装这个钩子。
import { Lifecycle } from 'react-router'
const Home = React.createClass({
// 假设 Home 是一个 route 组件,它可能会使用
// Lifecycle mixin 去获得一个 routerWillLeave 方法。
mixins: [ Lifecycle ],
routerWillLeave(nextLocation) {
if (!this.state.isSaved)
return 'Your work is not saved! Are you sure you want to leave?'
},
// ...
})
如果你在组件中使用了 ES6 类,你可以借助 react-mixin 包将 Lifecycle
mixin 添加到组件中,不过我们推荐使用 React.createClass
来创建组件,初始化路由的生命周期钩子函数。
如果你想在一个深层嵌套的组件中使用 routerWillLeave 钩子,只需在 route 组件 中引入 RouteContextmixin,这样就会把 route
放到 context 中。
import { Lifecycle, RouteContext } from 'react-router'
const Home = React.createClass({
// route 会被放到 Home 和它子组件及孙子组件的 context 中,
// 这样在层级树中 Home 及其所有子组件都可以拿到 route。
mixins: [ RouteContext ],
render() {
return <NestedForm />
}
})
const NestedForm = React.createClass({
// 后代组件使用 Lifecycle mixin 获得
// 一个 routerWillLeave 的方法。
mixins: [ Lifecycle ],
routerWillLeave(nextLocation) {
if (!this.state.isSaved)
return 'Your work is not saved! Are you sure you want to leave?'
},
// ...
})
组件生命周期
路由切换时,组件生命周期的变化情况
1. 当用户打开应用的 '/' 页面
2. 当用户从 '/' 跳转到 '/invoice/123'
App
从 router 中接收到新的 props(例如children
、params
、location
等数据), 所以App
触发了componentWillReceiveProps
和componentDidUpdate
两个生命周期方法Home
不再被渲染,所以它将被移除Invoice
首次被挂载
3. 当用户从 /invoice/123
跳转到 /invoice/789
4. 当从 /invoice/789
跳转到 /accounts/123
获取数据
虽然还有其他通过 router 获取数据的方法, 但是最简单的方法是通过组件生命周期 Hook 来实现。 前面我们已经理解了当路由改变时组件生命周期的变化, 我们可以在 Invoice
组件里实现一个简单的数据获取功能。
let Invoice = React.createClass({
getInitialState () {
return {
invoice: null
}
},
componentDidMount () {
// 上面的步骤2,在此初始化数据
this.fetchInvoice()
},
componentDidUpdate (prevProps) {
// 上面步骤3,通过参数更新数据
let oldId = prevProps.params.invoiceId
let newId = this.props.params.invoiceId
if (newId !== oldId)
this.fetchInvoice()
},
componentWillUnmount () {
// 上面步骤四,在组件移除前忽略正在进行中的请求
this.ignoreLastFetch = true
},
fetchInvoice () {
let url = `/api/invoices/${this.props.params.invoiceId}`
this.request = fetch(url, (err, data) => {
if (!this.ignoreLastFetch)
this.setState({ invoice: data.invoice })
})
},
render () {
return <InvoiceView invoice={this.state.invoice}/>
}
})
在组件外部使用导航
虽然在组件内部可以使用 this.context.router
来实现导航,但许多应用想要在组件外部使用导航。使用Router组件上被赋予的history可以在组件外部实现导航。
// your main file that renders a Router
import { Router, browserHistory } from 'react-router'
import routes from './app/routes'
render(<Router history={browserHistory} routes={routes}/>, el)
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
如何获得上一次路径?
通过props.location获取路径
<Route component={App}>
{/* ... 其它 route */}
</Route>
const App = React.createClass({
getInitialState() {
return { showBackButton: false }
},
componentWillReceiveProps(nextProps) {
const routeChanged = nextProps.location !== this.props.location
this.setState({ showBackButton: routeChanged })
}
})
特殊api说明
Route
getComponent(location, callback)
与 component
一样,但是是异步的,对于 code-splitting 很有用。
callback
signature
cb(err, component)
<Route path="courses/:courseId" getComponent={(location, cb) => {
// 做一些异步操作去查找组件
cb(null, Course)
}}/>
getComponents(location, callback)
与 component
一样,但是是异步的,对于 code-splitting 很有用。
callback
signature
cb(err, components)
<Route path="courses/:courseId" getComponent={(location, cb) => {
// 做一些异步操作去查找组件
cb(null, {sidebar: CourseSidebar, content: Course})
}}/>
onEnter(nextState, replaceState, callback?)
当 route 即将进入时调用。它提供了下一个路由的 state,一个函数重定向到另一个路径。this
会触发钩子去创建 route 实例。
当 callback
作为函数的第三个参数传入时,这个钩子将是异步执行的,并且跳转会阻塞直到 callback
被调用。
onLeave()
当 route 即将退出时调用
Route Components
当 route 匹配到 URL 时会渲染一个 route 的组件。路由会在渲染时将以下属性注入组件中:
history
Router 的 history history。
对于跳转很有用的 this.props.history.pushState(state, path, query)(跳转操作)
location
当前的 location。
params
URL 的动态段。
route
渲染组件的 route。
routeParams
this.props.params
是直接在组件中指定 route 的一个子集。例如,如果 route 的路径是 users/:userId
而 URL 是 /users/123/portfolios/345
,那么 this.props.routeParams
会是 {userId: '123'}
,并且 this.props.params
会是 {userId: '123', portfolioId: 345}
。
children
匹配到子 route 的元素将被渲染。如果 route 有已命名的组件,那么此属性会是 undefined,并且可用的组件会被直接替换到 this.props
上。
示例
render((
<Router>
<Route path="/" component={App}>
<Route path="groups" component={Groups} />
<Route path="users" component={Users} />
</Route>
</Router>
), node)
class App extends React.Component {
render() {
return (
<div>
{/* 这可能是 <Users> 或 <Groups> */}
{this.props.children}
</div>
)
}
}
已命名的组件(一个路由下面多个组件)
当一个 route 有一个或多个已命名的组件时,其子元素的可用性是通过 this.props
命名的。因此 this.props.children
将会是 undefined。那么所有的 route 组件都可以参与嵌套。
示例
render((
<Router>
<Route path="/" component={App}>
<Route path="groups" components={{main: Groups, sidebar: GroupsSidebar}} />
<Route path="users" components={{main: Users, sidebar: UsersSidebar}}>
<Route path="users/:userId" component={Profile} />
</Route>
</Route>
</Router>
), node)
class App extends React.Component {
render() {
// 在父 route 中,被匹配的子 route 变成 props
return (
<div>
<div className="Main">
{/* 这可能是 <Groups> 或 <Users> */}
{this.props.main}
</div>
<div className="Sidebar">
{/* 这可能是 <GroupsSidebar> 或 <UsersSidebar> */}
{this.props.sidebar}
</div>
</div>
)
}
}
class Users extends React.Component {
render() {
return (
<div>
{/* 如果在 "/users/123" 路径上这会是 <Profile> */}
{/* UsersSidebar 也会获取到作为 this.props.children 的 <Profile> 。
你可以把它放这渲染 */}
{this.props.children}
</div>
)
}
}
History Mixin
在组件中添加路由的 history 对象。
注意:你的 route components 不需要这个 mixin,它在 this.props.history
中已经是可用的了。这是为了组件更深次的渲染树中需要访问路由的 history
对象。
Methods
pushState(state, pathname, query)
跳转至一个新的 URL。
arguments
state
- location 的 state。即新的路由内组件的state属性pathname
- 没有 query 完整的 URL。query
- 通过路由字符串化的一个对象。
replaceState(state, pathname, query)
在不影响 history 长度的情况下(如一个重定向),用新的 URL 替换当前这个。
参数
state
- location 的 state。pathname
- 没有 query 完整的 URL。query
- 通过路由字符串化的一个对象。
go(n)
在 history 中使用 n
或 -n
进行前进或后退
goBack()
在 history 中后退。
goForward()
在 history 中前进。
createPath(pathname, query)
使用路由配置,将 query 字符串化加到路径名中。
createHref(pathname, query)
使用路由配置,创建一个 URL。例如,它会在 pathname
的前面加上 #/
给 hash history。
isActive(pathname, query, indexOnly)
根据当前路径是否激活返回 true
或 false
。通过 pathname
匹配到 route 分支下的每个 route 将会是 true(子 route 是激活的情况下,父 route 也是激活的),除非 indexOnly
已经指定了,在这种情况下,它只会匹配到具体的路径。
参数
pathname
- 没有 query 完整的 URL。query
- 如果没有指定,那会是一个包含键值对的对象,并且在当前的 query 中是激活状态的 - 在当前的 query 中明确是undefined
的值会丢失相应的键或undefined
indexOnly
- 一个 boolean(默认:false
)。
示例
import { History } from 'react-router'
React.createClass({
mixins: [ History ],
render() {
return (
<div>
<div onClick={() => this.history.pushState(null, '/foo')}>Go to foo</div>
<div onClick={() => this.history.replaceState(null, 'bar')}>Go to bar without creating a new history entry</div>
<div onClick={() => this.history.goBack()}>Go back</div>
</div>
)
}
})
假设你正在使用 bootstrap,并且在 Tab 中想让那些 li
获得 active
:
import { Link, History } from 'react-router'
const Tab = React.createClass({
mixins: [ History ],
render() {
let isActive = this.history.isActive(this.props.to, this.props.query)
let className = isActive ? 'active' : ''
return <li className={className}><Link {...this.props}/></li>
}
})
// 如 <Link/> 一样使用它,你会得到一个包进 `li` 的锚点
// 并且还有一个自动的 `active` class。
<Tab href="foo">Foo</Tab>
问题集锦
react-router-native、
react-router和react-router-dom的区别
react-router
: 实现了路由的核心功能react-router-dom
: 基于react-router
,加入了在浏览器运行环境下的一些功能,例如:Link
组件,会渲染一个a
标签,Link组件源码a标签行; BrowserRouter
和HashRouter
组件,前者使用pushState
和popState
事件构建路由,后者使用window.location.hash
和hashchange
事件构建路由。
react-router-native
: 基于react-router
,类似react-router-dom
,加入了react-native
运行环境下的一些功能。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)