diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 26ecdec..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -_book/ -npm-debug.log diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md deleted file mode 100644 index dd03460..0000000 --- a/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# [React Router 中文文档](https://github.com/react-guide/react-router-cn) - -> 本文档是基于 React-Router 2.X 版本翻译。最新版变化较多,请访问[官方站点](https://reacttraining.com/react-router/web/guides/philosophy),实在跟不上节奏啊😂😂 - -在线 Gitbook 地址:http://react-guide.github.io/react-router-cn/ - -英文原版:https://github.com/rackt/react-router/tree/master/docs - -React Router 是完整的 React 路由解决方案 - -React Router 保持 UI 与 URL 同步。它拥有简单的 API 与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理。你第一个念头想到的应该是 URL,而不是事后再想起。 - -**重点:这是 React Router 的 `master` 分支,其中包含了很多还没有发布的修改。如果要看到最新公布的代码,请浏览 [`latest` 标签](https://github.com/rackt/react-router/tree/latest)。** - -### 文档 & 帮助 - -- [API 文档与指南](/docs) -- [Change Log](https://github.com/rackt/react-router/blob/master/CHANGELOG.md) -- [Stack Overflow](http://stackoverflow.com/questions/tagged/react-router) -- [Codepen Boilerplate](http://codepen.io/anon/pen/xwQZdy?editors=001) 用于反馈 bug - -**注意:** **如果你仍然使用的是 React Router 0.13.x,可以在 [the 0.13.x branch](https://github.com/rackt/react-router/tree/0.13.x) 找到 [文档](https://github.com/rackt/react-router/tree/0.13.x/docs/guides)。升级信息可以查看 [change log](https://github.com/rackt/react-router/blob/master/CHANGELOG.md)。** - -如果有疑问和技术难点,请到[我们的 Reactiflux 频道](https://discord.gg/0ZcbPKXt5bYaNQ46)或 [Stack Overflow](http://stackoverflow.com/questions/tagged/react-router) 提问。这里的 issue 是**专门**为反馈 bug 和新特性提出所设立的。 - -### 浏览器支持 - -我们支持所有的浏览器和环境中运行 React。 - -### 安装 - -首先通过 [npm](https://www.npmjs.com/) 安装: - - $ npm install --save react-router - -然后使用一个支持 CommonJS 或 ES2015 的模块管理器,例如 [webpack](https://webpack.github.io/): - -```js -// 使用 ES6 的转译器,如 babel -import { Router, Route, Link } from 'react-router' - -// 不使用 ES6 的转译器 -var ReactRouter = require('react-router') -var Router = ReactRouter.Router -var Route = ReactRouter.Route -var Link = ReactRouter.Link -``` - -也可以在 [unpkg](https://unpkg.com) 上构建 UMD 格式: - -```html - -``` - -你可以在 `window.ReactRouter` 找到这个库。 - -### 版本控制和稳定性 - -React Router 遵循语义化版本控制,并很好地诠释了它。我们希望 React Router 是一个稳定的依赖库,这样易于保持流行性。这是我们对你的应用的升级策略。 - -假设我们目前是 1.0 版本: - -1. 2.0 完全向后兼容 1.0,所以你可以放心地升级,然后逐步更新你的代码。 -2. 所有在 1.0 被弃用的 API 都会在控制台以 warn 的形式打印出来,并链接到升级指南。 -3. 在 3.0 中将会完全移除 1.0 所弃用的东西。 -4. 3.0 将发布不早于 2.0 三个月后。最坏的情况是,给你一个新的 API,你需要花费 6 个月的时间去完美升级。 -5. 可以用 rackt/rackt-codemod 去自动升级你的代码 - -> 如果是完全向后兼容的,为什么这不是一个小版本呢? - -如果我们不提供向后兼容性,然后你就不会问这个问题 —— 升级后的应用将不可运行。这不是我们想要的结果,我们想要平稳地,逐步地升级。 - -在实践中,这意味着你可以: - -1. 从 1.0 升级到 2.0,你的应用仍可以运行。 -2. 逐步更新你的代码到新的 API,在下一个版本发布之前,你有 3 个月的时间去完成。 -3. 运行 codemods 去处理自动运行 (2) 部分。 -4. 如果您的代码运行没有警告,你可以使用 3.0 版本重复这个列表 - -### 这看起来像什么? - -```js -import React from 'react' -import { Router, Route, Link } from 'react-router' - -const App = React.createClass({/*...*/}) -const About = React.createClass({/*...*/}) -// 等等。 - -const Users = React.createClass({ - render() { - return ( -
++TODO: Need proofread https://github.com/rackt/react-router/blob/master/docs/API.md
+
React Router 的重要组件。它能保持 UI 和 URL 的同步。
+children (required)一个或多个的 Route 或 PlainRoute。当 history 改变时, <Router> 会匹配出 Route 的一个分支,并且渲染这个分支中配置的组件,渲染时保持父 route 组件嵌套子 route 组件。
routeschildren 的别名。
historyRouter 监听的 history 对象,由 history 包提供。
createElement(Component, props)当 route 准备渲染 route 组件的一个分支时,就会用这个函数来创建 element。当你使用某种形式的数据进行抽象时,你可以想要获取创建 element 的控制权,例如在这里设置组件监听 store 的变化,或者使用 props 为每个组件传入一些应用模块。
+<Router createElement={createElement} />
+
+// 默认行为
+function createElement(Component, props) {
+ // 确保传入了所有的 props!
+ return <Component {...props}/>
+}
+
+// 你可能会使用什么,如 Relay
+function createElement(Component, props) {
+ // 确保传入了所有的 props!
+ return <RelayContainer Component={Component} routerProps={props}/>
+}
+
+stringifyQuery(queryObject)一个用于把 Link 或调用 transitionTo 函数的对象转化成 URL query 字符串的函数。
parseQueryString(queryString)一个用于把 query 字符串转化成对象,并传递给 route 组件 props 的函数。
+onError(error)当路由匹配到时,也有可能会抛出错误,此时你就可以捕获和处理这些错误。通常,它们会来自那些异步的特性,如 route.getComponents,route.getIndexRoute,和 route.getChildRoutes。
onUpdate()当 URL 改变时,需要更新路由的 state 时会被调用。
+请看仓库中的示例目录 examples/,这些例子都广泛使用了 Router。
允许用户浏览应用的主要方式。<Link> 以适当的 href 去渲染一个可访问的锚标签。
<Link> 可以知道哪个 route 的链接是激活状态的,并可以自动为该链接添加 activeClassName 或 activeStyle。
to跳转链接的路径,如 /users/123。
query已经转化成字符串的键值对的对象。
+hashURL 的 hash 值,如 #a-hash。
注意:React Router 目前还不能管理滚动条的位置,并且不会自动滚动到 hash 对应的元素上。如果需要管理滚动条位置,可以使用 scroll-behavior 这个库。
+state保存在 location 中的 state。
activeClassName当某个 route 是激活状态时,<Link> 可以接收传入的 className。失活状态下是默认的 class。
activeStyle当某个 route 是激活状态时,可以将样式添加到链接元素上。
+onClick(e)自定义点击事件的处理方法。如处理 <a> 标签一样 - 调用 e.preventDefault() 来防止过度的点击,同时 e.stopPropagation() 可以阻止冒泡的事件。
你也可以在 <a> 标签上传入一些你想要的 props,如 title,id,className 等等。
如 <Route path="/users/:userId" /> 这样的 route:
<Link to={`/users/${user.id}`} activeClassName="active">{user.name}</Link>
+// 变成它们其中一个依赖在 History 上,当这个 route 是
+// 激活状态的
+<a href="/users/123" class="active">Michael</a>
+<a href="#/users/123">Michael</a>
+
+// 修改 activeClassName
+<Link to={`/users/${user.id}`} activeClassName="current">{user.name}</Link>
+
+// 当链接激活时,修改它的样式
+<Link to="/users" style={{color: 'white'}} activeStyle={{color: 'red'}}>Users</Link>
+
+敬请期待!
+在 context 中给定路由的 state、设置 history 对象和当前的 location,<RoutingContext> 就会去渲染组件树。
Route 是用于声明路由映射到应用程序的组件层。
pathURL 中的路径。
+它会组合父 route 的路径,除非它是从 / 开始的,
+将它变成一个绝对路径。
注意:在动态路由中,绝对路径可能不适用于 route 配置中。
+如果它是 undefined,路由会去匹配子 route。
+component当匹配到 URL 时,单个的组件会被渲染。它可以
+被父 route 组件的 this.props.children 渲染。
const routes = (
+ <Route component={App}>
+ <Route path="groups" component={Groups}/>
+ <Route path="users" component={Users}/>
+ </Route>
+)
+
+class App extends React.Component {
+ render () {
+ return (
+ <div>
+ {/* 这会是 <Users> 或 <Groups> */}
+ {this.props.children}
+ </div>
+ )
+ }
+}
+
+componentsRoute 可以定义一个或多个已命名的组件,当路径匹配到 URL 时,
+它们作为 name:component 对的一个对象去渲染。它们可以被
+父 route 组件的 this.props[name] 渲染。
// 想想路由外部的 context — 如果你可拔插
+// `render` 的部分,你可能需要这么做:
+// <App main={<Users />} sidebar={<UsersSidebar />} />
+
+const routes = (
+ <Route 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>
+)
+
+class App extends React.Component {
+ render () {
+ const { main, sidebar } = this.props
+ return (
+ <div>
+ <div className="Main">
+ {main}
+ </div>
+ <div className="Sidebar">
+ {sidebar}
+ </div>
+ </div>
+ )
+ }
+}
+
+class Users extends React.Component {
+ render () {
+ return (
+ <div>
+ {/* 当路径是 "/users/123" 是 `children` 会是 <Profile> */}
+ {/* UsersSidebar 也可以获取作为 this.props.children 的 <Profile> ,
+ 所以这有点奇怪,但你可以决定哪一个可以
+ 继续这种嵌套 */}
+ {this.props.children}
+ </div>
+ )
+ }
+}
+
+getComponent(location, callback)与 component 一样,但是是异步的,对于 code-splitting 很有用。
callback signaturecb(err, component)
<Route path="courses/:courseId" getComponent={(location, cb) => {
+ // 做一些异步操作去查找组件
+ cb(null, Course)
+}}/>
+
+getComponents(location, callback)与 component 一样,但是是异步的,对于 code-splitting 很有用。
callback signaturecb(err, components)
<Route path="courses/:courseId" getComponent={(location, cb) => {
+ // 做一些异步操作去查找组件
+ cb(null, {sidebar: CourseSidebar, content: Course})
+}}/>
+
+childrenRoute 可以被嵌套,this.props.children 包含了从子 route 组件创建的元素。由于这是路由设计中非常重要的部分,请参考 Route 配置。
onEnter(nextState, replaceState, callback?)当 route 即将进入时调用。它提供了下一个路由的 state,一个函数重定向到另一个路径。this 会触发钩子去创建 route 实例。
当 callback 作为函数的第三个参数传入时,这个钩子将是异步执行的,并且跳转会阻塞直到 callback 被调用。
onLeave()当 route 即将退出时调用。
+route 定义的一个普通的 JavaScript 对象。 Router 把 JSX 的 <Route> 转化到这个对象中,如果你喜欢,你可以直接使用它们。 所有的 props 都和 <Route> 的 props 一样,除了那些列在这里的。
childRoutes子 route 的一个数组,与在 JSX route 配置中的 children 一样。
getChildRoutes(location, callback)与 childRoutes 一样,但是是异步的,并且可以接收 location。对于 code-splitting 和动态路由匹配很有用(给定一些 state 或 session 数据会返回不同的子 route)。
callback signaturecb(err, routesArray)
let myRoute = {
+ path: 'course/:courseId',
+ childRoutes: [
+ announcementsRoute,
+ gradesRoute,
+ assignmentsRoute
+ ]
+}
+
+// 异步的子 route
+let myRoute = {
+ path: 'course/:courseId',
+ getChildRoutes(location, cb) {
+ // 做一些异步操作去查找子 route
+ cb(null, [ announcementsRoute, gradesRoute, assignmentsRoute ])
+ }
+}
+
+// 可以根据一些 state
+// 跳转到依赖的子 route
+<Link to="/picture/123" state={{ fromDashboard: true }}/>
+
+let myRoute = {
+ path: 'picture/:id',
+ getChildRoutes(location, cb) {
+ let { state } = location
+
+ if (state && state.fromDashboard) {
+ cb(null, [dashboardPictureRoute])
+ } else {
+ cb(null, [pictureRoute])
+ }
+ }
+}
+
+indexRouteindex route。这与在使用 JSX route 配置时指定一个 <IndexRoute> 子集一样。
getIndexRoute(location, callback)与 indexRoute 一样,但是是异步的,并且可以接收 location。与 getChildRoutes 一样,对于 code-splitting 和动态路由匹配很有用
callback signaturecb(err, route)
// 例如:
+let myIndexRoute = {
+ component: MyIndex
+}
+
+let myRoute = {
+ path: 'courses',
+ indexRoute: myIndexRoute
+}
+
+// 异步的 index route
+let myRoute = {
+ path: 'courses',
+ getIndexRoute(location, cb) {
+ // 做一些异步操作
+ cb(null, myIndexRoute)
+ }
+}
+
+在应用中 <Redirect> 可以设置重定向到其他 route 而不改变旧的 URL。
from你想由哪个路径进行重定向,包括动态段。
+to你想重定向的路径。
+query默认情况下,query 的参数只会经过,但如果你需要你可以指定它们。
+// 我们需要从 `/profile/123` 改变到 `/about/123`
+// 并且由 `/get-in-touch` 重定向到 `/contact`
+<Route component={App}>
+ <Route path="about/:userId" component={UserProfile}/>
+ {/* /profile/123 -> /about/123 */}
+ <Redirect from="profile/:userId" to="about/:userId" />
+</Route>
+
+注意,在 route 层 <Redirect> 可以被放在任何地方,尽管正常的优先 规则仍适用。如果你喜欢将下一个重定向到它各自的 route 上,from 的路径会匹配到一个与 path 一样的正常路径。
<Route path="course/:courseId">
+ <Route path="dashboard" />
+ {/* /course/123/home -> /course/123/dashboard */}
+ <Redirect from="home" to="dashboard" />
+</Route>
+
+当用户在父 route 的 URL 时,
+Index Routes 允许你为父 route 提供一个默认的 "child",
+并且为使<IndexLink> 能用提供了约定。
请看 Index Routes 指南。
+与 Route 的 props 一样,除了 path。
Index Redirects 允许你从一个父 route 的 URL 重定向到其他 route。 +它们被用于允许子 route 作为父 route 的默认 route, +同时保持着不同的 URL。
+请看 Index Routes 指南。
+与 Redirect 的 props 一样,除了 from。
当 route 匹配到 URL 时会渲染一个 route 的组件。路由会在渲染时将以下属性注入组件中:
+historyRouter 的 history history。
+对于跳转很有用的 this.props.history.pushState(state, path, query)
location当前的 location。
+paramsURL 的动态段。
+route渲染组件的 route。
+routeParamsthis.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>
+ )
+ }
+}
+
+在组件中添加一个钩子,当路由要从 route 组件的配置中跳转出来时被调用,并且有机会去取消这次跳转。主要用于表单的部分填写。
+在常规的跳转中, routerWillLeave 会接收到一个单一的参数:我们正要跳转的 location。去取消此次跳转,返回 false。
提示用户确认,返回一个提示信息(字符串)。在 web 浏览器 beforeunload 事件发生时,routerWillLeave 不会接收到一个 location 的对象(假设你正在使用 useBeforeUnload history 的增强方法)。在此之上,我们是不可能知道要跳转的 location,因此 routerWillLeave 必须在用户关闭标签之前返回一个提示信息。
routerWillLeave(nextLocation)当路由尝试从一个 route 跳转到另一个并且渲染这个组件时被调用。
+nextLocation - 下一个 location在组件中添加路由的 history 对象。
+注意:你的 route components 不需要这个 mixin,它在 this.props.history 中已经是可用的了。这是为了组件更深次的渲染树中需要访问路由的 history 对象。
pushState(state, pathname, query)跳转至一个新的 URL。
+state - location 的 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 的值会丢失相应的键或 undefinedindexOnly - 一个 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>
+
+++注意难道我们没有告诉你要使用 ES6 的 Classes?:)
+
https://twitter.com/soprano/status/610867662797807617
+在应用中少数组件由于 History mixin 的缘故而用得不爽,此时有以下几个选项:
this.props.history 通过 route 组件到达需要它的组件中。import { PropTypes } from 'react-router'
+
+class MyComponent extends React.Component {
+ doStuff() {
+ this.context.history.pushState(null, '/some/path')
+ }
+}
+
+MyComponent.contextTypes = { history: PropTypes.history }
+
+function connectHistory(Component) {
+ return React.createClass({
+ mixins: [ History ],
+ render() {
+ return <Component {...this.props} history={this.history} />
+ }
+ })
+}
+
+// 其他文件
+import connectHistory from './connectHistory'
+
+class MyComponent extends React.Component {
+ doStuff() {
+ this.props.history.pushState(null, '/some/where')
+ }
+}
+
+export default connectHistory(MyComponent)
+
+RouteContext mixin 提供了一个将 route 组件设置到 context 中的便捷方法。这对于 route 渲染元素并且希望用 生命周期 mixin 来阻止跳转是很有必要的。
+简单地将 this.context.route 添加到组件中。
useRoutes(createHistory)返回一个新的 createHistory 函数,它可以用来创建读取 route 的 history 对象。
match(location, cb)这个函数被用于服务端渲染。它在渲染之前会匹配一组 route 到一个 location,并且在完成时调用 callback(error, redirectLocation, renderProps)。
传给回调函数去 match 的三个参数如下:
error:如果报错时会出现一个 Javascript 的 Error 对象,否则是 undefined。redirectLocation:如果 route 重定向时会有一个 Location 对象,否则是 undefined。renderProps:当匹配到 route 时 props 应该通过路由的 context,否则是 undefined。如果这三个参数都是 undefined,这就意味着在给定的 location 中没有 route 被匹配到。
注意:你可能不想在浏览器中用它,除非你做的是异步 route 的服务端渲染。
+createRoutes(routes)创建并返回一个从给定对象 route 的数组,它可能是 JSX 的 route,一个普通对象的 route,或是其他的数组。
+routes一个或多个的 Route 或 PlainRoute。
敬请期待!
+ + +这是 React Router 库以及文档中常用术语的词汇表,并附有 type signatures(类型签名),以首字母顺序列出。
+type Action = 'PUSH' | 'REPLACE' | 'POP';
+
+Action 描述了URL变化的类型。有以下几种类型:
+PUSH — 表示有一个新条目加入了 historyREPLACE — 表示 history 中的当前条目被修改POP — 表示有一个新的当前条目,例如“current pointer(当前指针)”被修改type Component = ReactClass | string;
+
+这里的 “组件” 指的是React组件对应的类或者字符串(比如“div”)。基本上,它是任何可以作为第一个参数传入 React.createElement 的对象。
type EnterHook = (nextState: RouterState, replaceState: RedirectFunction, callback?: Function) => any;
+
+enter hook 是一个用户定义的函数,当路由将要开始被渲染之前,它会被调用。它接受下一个 router state 作为第一个参数。第二个参数 replaceState function 可以被用于触发URL的变化。
如果 enter hook 需要异步执行,那么第三个参数 callback 可以被用于设置回调,以便变换过程继续进行
注意: 在enter hook中使用 callback 会让变换过程处于阻塞状态,直到 callback 被回调。如果你不能快速地回调的话,这可能会导致整个UI失去响应。
type LeaveHook = () => any;
+
+leave hook 是一个用户定义的函数,在离开当前路由之前,它会被调用。
+type Location = {
+ pathname: Pathname;
+ search: QueryString;
+ query: Query;
+ state: LocationState;
+ action: Action;
+ key: LocationKey;
+};
+
+location 回答了两个重要的(哲学)问题:
+当URL变化时,新的locations将会产生。你可以在history的文档 中更详细地了解loaction。
type LocationKey = string;
+
+location key 是能代表一个 location 的唯一字符串。它能帮助你准确地回答“我在哪”这个问题。
type LocationState = ?Object;
+
+location state 是一个由任意数据构成的对象,以便和某个特殊的 location 联系起来。它可以被用于在 location 上绑定一些不能通过URL获取的额外状态。
这个类型得名于 HTML5 中 pushState 和 replaceState 的第一个参数
type Path = Pathname + QueryString;
+
+path(路径) 代表URL的路径。
+type Pathname = string;
+
+pathname(路径名) 是URL中用于描述分层路径的一部分,包括最前的 /。比如,在 http://example.com/the/path?the=query 这个URL中,/the/path 就是 pathname。它和浏览器中的 window.location.pathname 是同样的意思。
type QueryString = string;
+
+query string 是URL中紧跟在 pathname(路径名) 后面的一部分,包括最前的 ?。比如,在 http://example.com/the/path?the=query 这个URL中,?the=query 就是 query string。它和浏览器中的 window.loaction.search 是同样的意思。
type Query = Object;
+
+query 是 query string 解析后的版本。
+type Params = Object;
+
+params(参数) 指的是由URL中的 pathname(路径名) 经过解析之后生成的键/值对。它的值在通常情况下是字符串类型,只有当一个键对应了多个值时,才可以是数组。
+type RedirectFunction = (state: ?LocationState, pathname: Pathname | Path, query: ?Query) => void;
+
+redirect function(重定向函数) 在 onEnter hooks 中被用于触发跳转到新的URL。
type Route = {
+ component: RouteComponent;
+ path: ?RoutePattern;
+ onEnter: ?EnterHook;
+ onLeave: ?LeaveHook;
+};
+
+route(路由)指定了一个 component(组件) 作为UI的一部分。它应该嵌套在一个树状结构中,以便符合组件的层次。
+它或许有助于让你把路由看做整个 UI 的“入口”。当然,你不需要为组件层中的每个组件都设置路由,只需要为那些因 URL 变化而不同的组件设置即可。
+type RouteComponent = Component;
+
+route component(路由组件) 指的是一个直接被 route(路由)(例如 <Route component>)渲染出的 component(组件)。router(路由器)会从路由组件中创建元素,并且把它们作为 this.props.children 提供给上一层的路由组件。除了 children,路由组件还会接收以下props:
router – router(路由器)实例location – 当前的 locationparams – 当前的 params(参数)route – 声明了这个组件的 route(路由)routeParams – 在路由的 path 中指定参数的集合type RouteConfig = Array<Route>;
+
+route config(路由配置) 是一个由 route(路由) 组成的数组,它指定了路由器使用路由匹配URL时的先后顺序。
+type RouteHook = (nextLocation?: Location) => any;
+
+route hook 是用于防止用户离开某个路由的函数。在正常的路由变换时,它接收下一个
+location 作为参数,并且必须 return false 以取消变换,或者 return 提示消息给用户。 当它在web浏览器的 beforeunload 事件中被调用时,它不会接收任何参数,且必须 return 一个提示信息以取消变换。
type RoutePattern = string;
+
+route pattern(或者 “path”)是用于描述部分URL特征的字符串。Pattern 会被编译成函数,这些函数会被用于尝试匹配URL。Pattern 必须使用以下特殊字符:
+:paramName – 在下一个 /、? 或 # 前,匹配 URL 的局部。被匹配的部分被称为 param(参数)() – 匹配URL的一部分,是可选的* – 匹配任意字符(非贪婪)直到 Pattern 中的下一个字符,如果没有下一个字符,那么会一直匹配到URL尾部。同时生成一个 splat param(参数)Route pattern 都是是相对于parent route(父路由)而言的,除非是以 / 开头,才会从URL的开头进行匹配。
type Router = {
+ transitionTo: (location: Location) => void;
+ pushState: (state: ?LocationState, pathname: Pathname | Path, query?: Query) => void;
+ replaceState: (state: ?LocationState, pathname: Pathname | Path, query?: Query) => void;
+ go(n: Number) => void;
+ listen(listener: RouterListener) => Function;
+ match(location: Location, callback: RouterListener) => void;
+};
+
+router(路由器)是一个 history 对象(类似于 web 浏览器中的 window.history),用于改变 URL 或者监听 URL 的变化。
有两个主要接口用于计算路由器的下一个 state:
+history.listen 在有状态且需要在一段时间后更新UI的环境(例如 web 浏览器)下使用。此方法会立即调用它的 listener 参数一次,然后返回一个函数,这个函数必须被调用以停止监听变化。history.match 一个纯异步函数,它不会更新 history 的内部状态。这使得它非常适合服务器端环境中,在同一时间需要处理多个任务的需求。type RouterListener = (error: ?Error, nextState: RouterState) => void;
+
+router listener 是一个用于监听 router(路由器) 下 state 变化的函数。
+type RouterState = {
+ location: Location;
+ routes: Array<Route>;
+ params: Params;
+ components: Array<Component>;
+};
+
+router state 代表当前路由的状态,它包括了:
+locationroutes(路由) 的数组params(参数) 对象components(组件) 组成的数组,并按照层级排列。React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。
+为了向你说明 React Router 解决的问题,让我们先来创建一个不使用它的应用。所有文档中的示例代码都会使用 ES6/ES2015 语法和语言特性。
+import React from 'react'
+import { render } from 'react-dom'
+
+const About = React.createClass({/*...*/})
+const Inbox = React.createClass({/*...*/})
+const Home = React.createClass({/*...*/})
+
+const App = React.createClass({
+ getInitialState() {
+ return {
+ route: window.location.hash.substr(1)
+ }
+ },
+
+ componentDidMount() {
+ window.addEventListener('hashchange', () => {
+ this.setState({
+ route: window.location.hash.substr(1)
+ })
+ })
+ },
+
+ render() {
+ let Child
+ switch (this.state.route) {
+ case '/about': Child = About; break;
+ case '/inbox': Child = Inbox; break;
+ default: Child = Home;
+ }
+
+ return (
+ <div>
+ <h1>App</h1>
+ <ul>
+ <li><a href="#/about">About</a></li>
+ <li><a href="#/inbox">Inbox</a></li>
+ </ul>
+ <Child/>
+ </div>
+ )
+ }
+})
+
+React.render(<App />, document.body)
+
+当 URL 的 hash 部分(指的是 # 后的部分)变化后,<App> 会根据 this.state.route 来渲染不同的 <Child>。看起来很直接,但它很快就会变得复杂起来。
现在设想一下 Inbox 下面嵌套一些分别对应于不同 URL 的 UI 组件,就像下面这样的列表-详情视图:
path: /inbox/messages/1234
+
++---------+------------+------------------------+
+| About | Inbox | |
++---------+ +------------------------+
+| Compose Reply Reply All Archive |
++-----------------------------------------------+
+|Movie tomorrow| |
++--------------+ Subject: TPS Report |
+|TPS Report From: boss@big.co |
++--------------+ |
+|New Pull Reque| So ... |
++--------------+ |
+|... | |
++--------------+--------------------------------+
+还可能有一个状态页,用于在没有选择 message 时展示:
+path: /inbox
+
++---------+------------+------------------------+
+| About | Inbox | |
++---------+ +------------------------+
+| Compose Reply Reply All Archive |
++-----------------------------------------------+
+|Movie tomorrow| |
++--------------+ 10 Unread Messages |
+|TPS Report | 22 drafts |
++--------------+ |
+|New Pull Reque| |
++--------------+ |
+|... | |
++--------------+--------------------------------+
+为了让我们的 URL 解析变得更智能,我们需要编写很多代码来实现指定 URL 应该渲染哪一个嵌套的 UI 组件分支:App -> About, App -> Inbox -> Messages -> Message, App -> Inbox -> Messages -> Stats,等等。
让我们用 React Router 重构这个应用。
+import React from 'react'
+import { render } from 'react-dom'
+
+// 首先我们需要导入一些组件...
+import { Router, Route, Link } from 'react-router'
+
+// 然后我们从应用中删除一堆代码和
+// 增加一些 <Link> 元素...
+const App = React.createClass({
+ render() {
+ return (
+ <div>
+ <h1>App</h1>
+ {/* 把 <a> 变成 <Link> */}
+ <ul>
+ <li><Link to="/about">About</Link></li>
+ <li><Link to="/inbox">Inbox</Link></li>
+ </ul>
+
+ {/*
+ 接着用 `this.props.children` 替换 `<Child>`
+ router 会帮我们找到这个 children
+ */}
+ {this.props.children}
+ </div>
+ )
+ }
+})
+
+// 最后,我们用一些 <Route> 来渲染 <Router>。
+// 这些就是路由提供的我们想要的东西。
+React.render((
+ <Router>
+ <Route path="/" component={App}>
+ <Route path="about" component={About} />
+ <Route path="inbox" component={Inbox} />
+ </Route>
+ </Router>
+), document.body)
+
+React Router 知道如何为我们搭建嵌套的 UI,因此我们不用手动找出需要渲染哪些 <Child> 组件。举个例子,对于一个完整的 /about 路径,React Router 会搭建出 <App><About /></App>。
在内部,router 会将你树级嵌套格式的 <Route> 转变成路由配置。但如果你不熟悉 JSX,你也可以用普通对象来替代:
const routes = {
+ path: '/',
+ component: App,
+ childRoutes: [
+ { path: 'about', component: About },
+ { path: 'inbox', component: Inbox },
+ ]
+}
+
+React.render(<Router routes={routes} />, document.body)
+
+好了,现在我们准备在 inbox UI 内嵌套 inbox messages。
+// 新建一个组件让其在 Inbox 内部渲染
+const Message = React.createClass({
+ render() {
+ return <h3>Message</h3>
+ }
+})
+
+const Inbox = React.createClass({
+ render() {
+ return (
+ <div>
+ <h2>Inbox</h2>
+ {/* 渲染这个 child 路由组件 */}
+ {this.props.children || "Welcome to your Inbox"}
+ </div>
+ )
+ }
+})
+
+React.render((
+ <Router>
+ <Route path="/" component={App}>
+ <Route path="about" component={About} />
+ <Route path="inbox" component={Inbox}>
+ {/* 添加一个路由,嵌套进我们想要嵌套的 UI 里 */}
+ <Route path="messages/:id" component={Message} />
+ </Route>
+ </Route>
+ </Router>
+), document.body)
+
+现在访问 URL inbox/messages/Jkei3c32 将会匹配到一个新的路由,并且它成功指向了 App -> Inbox -> Message 这个 UI 的分支。
<App>
+ <Inbox>
+ <Message params={ {id: 'Jkei3c32'} } />
+ </Inbox>
+</App>
+为了从服务器获取 message 数据,我们首先需要知道它的信息。当渲染组件时,React Router 会自动向 Route 组件中注入一些有用的信息,尤其是路径中动态部分的参数。我们的例子中,它指的是 :id。
const Message = React.createClass({
+
+ componentDidMount() {
+ // 来自于路径 `/inbox/messages/:id`
+ const id = this.props.params.id
+
+ fetchMessage(id, function (err, message) {
+ this.setState({ message: message })
+ })
+ },
+
+ // ...
+
+})
+
+你也可以通过 query 字符串来访问参数。比如你访问 /foo?bar=baz,你可以通过访问 this.props.location.query.bar 从 Route 组件中获得 "baz" 的值。
这就是 React Router 的奥秘。应用的 UI 以盒子中嵌套盒子的方式来表现;然后你可以让这些盒子与 URL 始终保持同步,而且很容易地把它们链接起来。
+这个关于 路由配置 的文档深入地描述了 router 的功能。
+ + +<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 升级的细节,请参阅更新日志,可以查看所有相关的 commit 和 issue。
感谢你的耐心等待,终于迎来了这次重大变更。虽然表面上看来只是更新了一些 API,而实际上为了适用于更大型的应用场景,我们几乎重写了整个代码库。新的 API 提供了按需载入路由和组件、基于 session 的路由匹配、服务端渲染、整合 redux 和 relay 库,等等等等。
+现在,我们来对比一下新旧 API 的区别。
+// v0.13.x
+Router.run(routes, (Handler) => {
+ React.render(<Handler/>, el);
+})
+
+// v1.0
+React.render(<Router>{routes}</Router>, el)
+
+// 类似这样:
+React.render((
+ <Router>
+ <Route path="/" component={App}/>
+ </Router>
+), el);
+
+// 当然也可以这样
+React.render(<Router routes={routes}/>, el)
+
+现在,location 被叫做 history(它会映射 location)。你需要从 history 包导入它们,而不是 react-router。
// v0.13.x
+Router.run(routes, Router.BrowserHistory, (Handler) => {
+ React.render(<Handler/>, el);
+})
+
+// v1.0
+import createBrowserHistory from 'history/lib/createBrowserHistory'
+let history = createBrowserHistory()
+React.render(<Router history={history}>{routes}</Router>, el)
+
+如果没有指定 history 的类型(就像上面),那你要注意下在升级到 1.0.0 后的异常行为。一个并非你所定义的名为 _k 的 querystring 和路由所需的 hash 一并出现在了 URL 的后面。类似这样:?_k=umhx1s。
这是有意为之的,它是 createHashHistory 部分的内容(也是当你没有指定时,默认的 history 方法)。你可以在这里了解更多关于它的特性,详细的文档在这里。
+你依旧可以像上面那样嵌套路由,路径同样也会从父组件继承而来,不过属性名称有变化。
+// v0.13.x
+<Route name="about" handler={About}/>
+
+// v1.0
+<Route path="about" component={About}/>
+
+具名路由已被移除(现在可以查看相关的讨论)
+NotFound 确实搞晕了好多人,搞不清楚是 API 中找不到资源还是没有可匹配的路由。而实际上它仅仅是一个简单的 * 路径,我们已经彻底移除了它。
// v0.13.x
+<NotFoundRoute handler={NoMatch}/>
+
+// v1.0
+<Route path="*" component={NoMatch}/>
+
+from 必须使用绝对路径// v0.13.x
+<Redirect from="some/where/:id" to="somewhere/else/:id" params={{id: 2}}/>
+
+// v1.0
+// 可以像上面一样正常工作,除了去掉 params 参数,将它们放到了路径当中
+<Redirect from="/some/where/:id" to="/somewhere/else/2"/>
+
+// v0.13.x
+<Link to="user" params={{userId: user.id}}>Mateusz</Link>
+
+// v1.0
+// 由于具名路由被移除,链接改为完成路径,你不再需要获取每个参数的名称。同时,字符串插值新特性非常棒。
+// 注意,query 并没有改变。
+<Link to={`/users/${user.id}`}>Mateusz</Link>
+
+在 0.13.x link 组件会默认添加 “active” 类,你可以通过 activeClassName 重写它,或者提供 activeStyle。而事实上,绝大多数 link 并不需要它,并且检测起来(目前来看)还是很昂贵的。
Link 不会默认添加 “active” 类,你可以选择增加一个;如果没有 activeClassName 或者 activeStyle,即便被激活,也不会去检测 link。
// v0.13.x
+<Link to="about">About</Link>
+
+// v1.0
+<Link to="/about" activeClassName="active">About</Link>
+
+由于具名路由被移除,如果默认路由是 /,指向 / 的 link 就会一直处于激活状态。所以,我们介绍的 IndexLink 仅仅是当默认路由处于激活状态时。
注意:DefaultRoute 已经废弃。
// v0.13.x
+// 配置路由
+<Route path="/" handler={App}>
+ <DefaultRoute name="home" handler={Home}/>
+ <Route name="about" handler={About}/>
+</Route>
+
+// 仅仅在 home 被激活是它才会被激活,在 about 激活时则不会
+<Link to="home">Home</Link>
+
+// v1.0
+<Route path="/" component={App}>
+ <IndexRoute component={Home}/>
+ <Route path="about" component={About}/>
+</Route>
+
+// 仅仅在 home 被激活是它才会被激活,在 about 激活时则不会
+<IndexLink to="/">Home</IndexLink>
+
+RouteHandler 被移除。现在 Router 可以基于激活的路由自动填充组件的 this.props.children。
// v0.13.x
+<RouteHandler/>
+<RouteHandler someExtraProp={something}/>
+
+// v1.0
+{this.props.children}
+{React.cloneElement(this.props.children, {someExtraProp: something })}
+
+如果你正在用 Navigation mixin,那么可以使用 History mixin 代替。
// v0.13.x
+var Assignment = React.createClass({
+ mixins: [ Navigation ],
+ navigateAfterSomethingHappened () {
+ this.transitionTo('/users', { userId: user.id }, query);
+ // this.replaceWith('/users', { userId: user.id }, query);
+ }
+})
+
+// v1.0
+var Assignment = React.createClass({
+ mixins: [ History ],
+ navigateAfterSomethingHappened () {
+ // 现在的路由构建于 rackt/history 之上,并且它是路由中用于导航的第一类 API
+ this.history.pushState(null, `/users/${user.id}`, query);
+ // this.history.replaceState(null, `/users/${user.id}`, query);
+ }
+})
+
+下面的所有 Navigation 方法同样可以在 history 对象中找到,主要的区别还是移除了 params 以及路由名称,仅仅剩下路径名称。
| v0.13 | +v1.0 | +
|---|---|
go(n) |
+go(n) |
+
goBack() |
+goBack() |
+
goForward() |
+goForward() |
+
makeHref(routeName, params, query) |
+createHref(pathname, query) |
+
makePath(routeName, params, query) |
+createPath(pathname, query) |
+
// v0.13.x
+var Assignment = React.createClass({
+ mixins: [ State ],
+ foo () {
+ this.getPath()
+ this.getParams()
+ // 等...
+ }
+})
+
+// v1.0
+// 如果是一个路由组件...
+<Route component={Assignment} />
+
+var Assignment = React.createClass({
+ foo () {
+ this.props.location // 包含路径信息
+ this.props.params // 包含参数
+ this.props.history.isActive
+ }
+})
+
+// 如果不是一个路由组件,你需要借助 context 将 location 经组件树传递下去。
+// 我们也可能提供一个更加高阶组件来帮助你完成这个,不过现在还不行。
+// 下面看一下借助 context 我们还可以传递哪些信息。
+var Assignment = React.createClass({
+ contextTypes: {
+ location: React.PropTypes.object
+ },
+ foo () {
+ this.context.location
+ }
+})
+
+下面的表格展示了用于替换 State mixin 的方法。如果是一个组件路由的话,可以通过 this.props 获取到
| v0.13 (this) | +v1.0 (this.props) | +
|---|---|
getPath() |
+location.pathname+location.search |
+
getPathname() |
+location.pathname |
+
getParams() |
+params |
+
getQuery() |
+location.search |
+
getRoutes() |
+routes |
+
isActive(to, params, query) |
+history.isActive(pathname, query, onlyActiveOnIndex) |
+
下面是另外一张表格,展示了在非路由组件下借助 this.context 替换 State mixin 的方法。
| v0.13 (this) | +v1.0 (this.context) | +
|---|---|
getPath() |
+location.pathname+location.search |
+
getPathname() |
+location.pathname |
+
getQuery() |
+location.search |
+
isActive(to, params, query) |
+history.isActive(pathname, query, onlyActiveOnIndex) |
+
注意,在 v1.0 中并非所有的 State 特性都可以通过 context 访问到。比如,通过 context 不能获取到 params。
在 0.13.x 中,有两个接口用于还原滚动位置。我们意识到可以在最外层组件创建一个更优雅的实现,并且很快就要去完成,在最终的 1.0 release 之前。不过没必要受到以前路由的约束。
+willTransitionTo 和 willTransitionFrom现在,路由定义了该行为:
+// v0.13.x
+var Home = React.createClass({
+ statics: {
+ willTransitionTo (transition, params, query, callback) {
+ }
+ willTransitionFrom (component, transition, params, query, callback) {
+ }
+ }
+})
+
+// v1.0
+<Route
+ component={Home}
+ onEnter={(location, replaceWith) => {}}
+ onLeave={() => {}}
+/>
+
+想要取消一个“transition from”,请参阅跳转前确认部分。
+有很多旧的 API 已被我们舍弃,烦请详读新的文档,帮助我们完善它。感谢!
+Like many others in the community, we misunderstood the "mixins are
+going away" sentiment. Mixins, along with React.createClass are not
+going away any time soon, and definitely not until ES6 classes have
+better answers to replace what mixins do (like decorators).
So, don't access context, use the mixins, sorry for the churn, we know +it can be frustrating.
+Upgrade path from 0.13.2 to 0.13.3 is to put your code back to how
+it was in 0.12.x. The context stuff will still work, so you can do it
+incrementally.
SKIP THIS UPGRADE AND GO STRAIGHT TO 0.13.3
+0.13.3 has the same API as 0.12.x, so please upgrade to 0.13.3 and
+skip the 0.13.0-0.13.2 stuff and leave your code alone :)
React introduced the ability to use ES6 classes for component
+definitions, which has the side-effect of mixins not being "the thing"
+anymore. Our mixins like State and Navigation just proxied calls to
+some methods on an undocumented feature of React called context, that
+in turn called methods on the router instance under the hood.
Without mixins we needed a way for you to get access to these methods. +We decided the simplest solution was to stop hiding the router instance +and just put the whole thing on context.
+You can think about context as values that are floating around a render
+tree that parent components (Handler in the Router.run callback) can
+explicitly define and descendent components can explicitly ask for. The
+stuff on context doesn't show up in a component unless you ask for it.
Note: You can still use our mixins, you'll just get a deprecation warning.
+// 0.12.x
+var Foo = React.createClass({
+ mixins: [ Router.State ],
+ render: function () {
+ var id = this.getParams().id;
+ var searchTerm = this.getQuery().searchTerm;
+ // etc. ...
+ }
+});
+
+// 0.13.x w/o ES6 fanciness
+var Foo = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.func
+ },
+
+ render: function () {
+ var router = this.context.router;
+ var id = router.getCurrentParams().id;
+ var searchTerm = router.getCurrentQuery().searchTerm;
+ // etc.
+ }
+});
+
+// 0.13.x w/ ES6 fanciness
+class Foo extends React.Component {
+ render () {
+ var { router } = this.context;
+ var id = router.getCurrentParams().id;
+ var searchTerm = router.getCurrentQuery().searchTerm;
+ // etc.
+ }
+}
+
+Foo.contextTypes = {
+ router: React.PropTypes.func
+};
+
+Most of the time we prefer to just pass the state down the props tree +and not mess with context:
+Router.run(routes, (Handler, state) => {
+ React.render(<Handler {...state}/>, document.body);
+});
+
+// and then when rendering route handlers, keep passing it down
+<RouteHandler {...this.props}/>
+
+// and then in your methods you have what you need on props
+var id = this.props.params.id;
+var searchTerm = this.props.query.searchTerm;
+
+transition.wait was removed, you now use a callback instead:
// 0.11.x
+var SomeHandler = React.createClass({
+ statics: {
+ willTransitionTo (transition) {
+ transition.wait(somePromise());
+ }
+ }
+});
+
+// 0.12.0
+var SomeHandler = React.createClass({
+ statics: {
+ willTransitionTo (transition, params, query, callback) {
+ somePromise().then(callback);
+ }
+ }
+});
+
+The router changed a lot in this release. While you won't have to +change too much of your app, you will have to change it in a lot of +places. The fundamental change is that you, rather than the router, get +to have control of your view instances.
+If you find anything is missing from this list, please open an issue and +we will get it added here ASAP.
+You must upgrade to 0.12.x before you can use version 0.11.x of the
+router.
<Routes/> and starting the router<Routes/> is gone, there is a new API that gives you complete control
+of your views.
// 0.10.x
+var routes = (
+ <Routes location="history">
+ <Route handler={App}>
+ <Route name="dashboard" handler={Dashboard}/>
+ </Route>
+ </Routes>
+);
+
+React.render(routes, el);
+
+// 0.11.x
+var routes = (
+ <Route handler={App}>
+ <Route name="dashboard" handler={Dashboard}/>
+ </Route>
+);
+
+Router.run(routes, Router.HistoryLocation, function (Handler) {
+ React.render(<Handler/>, el);
+});
+
+// or default to hash location
+Router.run(routes, function (Handler) {
+ React.render(<Handler/>, el);
+});
+
+this.props.activeRouteHandler() -> <RouteHandler/>// 0.10.x
+var Something = React.createClass({
+ render: function () {
+ return (
+ <div>
+ <this.props.activeRouteHandler />
+ </div>
+ );
+ }
+});
+
+// 0.11.x
+var RouteHandler = Router.RouteHandler;
+
+var Something = React.createClass({
+ render: function () {
+ return (
+ <div>
+ <RouteHandler />
+ </div>
+ );
+ }
+});
+
+this.props.params and this.props.queryThey are no longer available on props, use the State mixin.
// 0.10.x
+var Something = React.createClass({
+ render: function () {
+ var name = this.props.params.name;
+ var something = this.props.query.something;
+ // ...
+ }
+});
+
+// 0.11.x
+
+// pass it down the view hierarchy to get the same lifecycle hooks to
+// trigger as before
+Router.run(routes, function (Handler, state) {
+ React.render(<Handler params={state.params} query={state.query} />, el);
+ // make sure to `<RouteHandler {...this.props}/>` to continue
+ // passing it down the hierarchy
+});
+
+// or use the `State` mixin
+var Something = React.createClass({
+ mixins: [ Router.State ],
+ render: function () {
+ var name = this.getParams().name;
+ var something = this.getQuery().something;
+ // ...
+ }
+});
+
+// Also, if you're using a flux-style app, you can trigger a "transition"
+// action in the `run` callback with the params/query in the payload, then
+// subscribe in your handlers to the store that grabs the data.
+
+ActiveState -> State, and methods tooThis mixin's name has changed, and all of its methods that had the word
+active in it, too. For example, getActiveParams() becomes getParams().
// v0.10.x
+var Something = React.createClass({
+ mixins: [ Router.ActiveState ],
+ render: function () {
+ var name = this.getActiveParams().name;
+ // ...
+ }
+});
+
+// v0.11.x
+var Something = React.createClass({
+ mixins: [ Router.State ]
+ render: function () {
+ var name = this.getParams().name;
+ // ...
+ }
+});
+
+CurrentPath -> StateYou can find this.getPath() on the Router.State mixin.
// v0.10.x
+var Something = React.createClass({
+ mixins: [ Router.CurrentPath ],
+ render: function () {
+ var path = this.getCurrentPath();
+ // ...
+ }
+});
+
+// v0.11.x
+var Something = React.createClass({
+ mixins: [ Router.State ],
+ render: function () {
+ var path = this.getPath();
+ // ...
+ }
+});
+
+addHandlerKey propThis option has been removed, you will need to add handler keys +yourself:
+// 0.10.x
+<Route handler={App}>
+ <Route addHandlerKey={true}/>
+</Route>
+
+// 0.11.x
+var App = React.createClass({
+ mixins: [ Router.State ],
+
+ getHandlerKey: function () {
+ // this will all depend on your needs, but here's a typical
+ // scenario that's pretty much what the old prop did
+ var childDepth = 1; // have to know your depth
+ var childName = this.getRoutes()[childDepth].name;
+ var id = this.getParams().id;
+ var key = childName+id;
+ return key;
+ },
+
+ render: function () {
+ return (
+ <div>
+ <RouteHandler key={this.getHandlerKey()} />
+ </div>
+ );
+ }
+});
+
+<Routes onError={fn}/><Routes/> is gone, instead create a router with your error handler as
+an option:
// 0.10.x
+<Routes onError={fn}>
+ // ...
+</Routes>
+
+// 0.11.x
+var router = Router.create({
+ onError: fn,
+ // ...
+});
+router.run(callback);
+
+Router.renderRoutesTo*These methods have been removed because you, not the router, are in +control of rendering.
+// v0.10.x
+Router.renderRoutesToString(routes, path, function (html) {
+ // do something with `html`
+});
+
+// v0.11.x
+Router.run(routes, path, function (Handler) {
+ var html = React.renderToString(<Handler/>);
+});
+
+In 0.10.x you could add props to your route that would make their way
+down to your handlers. While convenient, conflating routes with their
+handlers was confusing to a lot of folks.
To get the same effect, you can either create your handlers with a +function and close over the information you need, or simply define those +properties on your handlers.
+// 0.10.x
+<Route name="users" foo="bar" handler={Something}/>
+
+var Something = React.createClass({
+ render () {
+ return <div>{this.props.name} {this.props.foo}</div>
+ }
+});
+
+// 0.11.x
+
+// close over technique
+<Route name="users" handler={makeSomething("users", "bar")}/>
+
+function makeSomething(name, foo) {
+ return React.createClass({
+ render () {
+ return <div>{name} {foo}</div>
+ }
+ });
+}
+
+// handler definition technique
+<Route name="users" handler={Something}/>
+
+var Something = React.createClass({
+ foo: "bar",
+ name: "users",
+ render () {
+ return <div>{this.name} {this.foo}</div>
+ }
+});
+
+Nothing changed, this was simply React 0.12.0 compatibility. Note,
+your code needs to use the React 0.11.x API for things to work, there
+will be lots of warnings in the console.
ActiveState mixin isActiveisActive is now an instance method.
// 0.7.x
+var SomethingActive = React.createClass({
+ mixins: [ActiveState],
+
+ render: function () {
+ var isActive = SomethingActive.isActive(...);
+ }
+});
+
+// 0.9.x
+var SomethingActive = React.createClass({
+ mixins: [ActiveState],
+
+ render: function () {
+ var isActive = this.isActive(...);
+ }
+});
+
+<Routes onActiveStateChange/> -> <Routes onChange />// 0.7.x
+<Routes onActiveStateChange={fn} />
+
+function fn(nextState) {}
+
+// 0.9.x
+<Routes onChange={fn} />
+
+function fn() {
+ // no arguments
+ // `this` is the routes instance
+ // here are some useful methods to get at the data you probably need
+ this.getCurrentPath();
+ this.getActiveRoutes();
+ this.getActiveParams();
+ this.getActiveQuery();
+}
+
+. in params support. used to be a delimiter like /, but now it's a valid character in
+your params.
transition.retry()transition.retry() used to use transitionTo, creating a new history
+entry, it now uses replaceWith.
// 0.7.x
+React.createClass({
+ login: function () {
+ // ...
+ transition.retry();
+ }
+});
+
+// 0.9.x
+React.createClass({
+ mixins: [Navigation],
+ login: function () {
+ // ...
+ this.transitionTo(transition.path);
+ }
+});
+
+Transition hooks are now sync, unless you opt-in to async with
+transition.wait(promise).
// 0.7.x
+React.createClass({
+ statics: {
+ willTransitionTo: function (transition) {
+ return somePromise();
+ }
+ }
+});
+
+// 0.9.x
+React.createClass({
+ statics: {
+ willTransitionTo: function (transition) {
+ transition.wait(somePromise());
+ }
+ }
+});
+
+preserveScrollPosition -> scrollBehaviorpreserveScrollPosition was totally broken and should have been named
+perverseScrollPosition.
There are now three scroll behaviors you can use:
+'browser''scrollToTop''none'browser is the default, and imitates what browsers do in a typical
+page reload scenario (preserves scroll positions when using the back
+button, scrolls up when you come to a new page, etc.) Also, you can no
+longer specify scroll behavior per <Route/> anymore, only <Routes/>
<Routes scrollBehavior="scrollToTop"/>
+This was not a public module, but we know some people were using it.
+It's gone now. We have made getting at the current routes incredibly
+convenient now with additions to the ActiveState mixin.
Router.transitionTo, replaceWith, goBackThese methods have been moved to mixins.
+var Router = require('react-router');
+
+// 0.7.x
+React.createClass({
+ whenever: function () {
+ Router.transitionTo('something');
+ Router.replaceWith('something');
+ Router.goBack();
+ }
+});
+
+// 0.9.x
+var Navigation = Router.Navigation;
+
+React.createClass({
+ mixins: [Navigation],
+ whenever: function () {
+ this.transitionTo('something');
+ this.replaceWith('something');
+ this.goBack();
+ }
+});
+
+<Routes onTransitionError onAbortedTransition/>These were removed, there is no upgrade path in 0.9.0 but we will have
+something soon. These weren't intended to be used.
ActiveState lifecycle method updateActiveState removedWe didn't actually need this. Just use this.isActive(to, params,
+query).
AsyncState mixin removedThere is no upgrade path. Just use comoponentDidMount to request
+state. This was some groundwork for server-side rendering but we are
+going a different direction now (using props passed in to route
+handlers) so we've removed it.
Please don't upgrade to 0.8.0, just skip to 0.9.x.
0.8.0 had some transient mixins we didn't intend to document, but had
+some miscommunication :(. If you were one of three people who used some
+of these mixins and need help upgrading from 0.8.0 -> 0.9.x find us on
+freenode in #rackt or open a ticket. Thanks!
The package root modules were removed. Please import modules from the
+Router default export.
// 0.6.x
+var Link = require('react-router/Link');
+
+// 0.7.x
+var Router = require('react-router');
+var Link = Router.Link;
+
+Paths that start with / are absolute and work exactly as they used to.
+Paths that don't start with / are now relative, meaning they extend
+their parent route.
Simply add / in front of all your paths to keep things working.
<!-- 0.5.x -->
+<Route path="/foo">
+ <Route path="bar"/>
+</Route>
+
+<!-- 0.6.x -->
+<Route path="/foo">
+ <Route path="/bar"/>
+</Route>
+
+Though you may want to embrace this new feature:
+<!-- 0.5.x -->
+<Route path="/course/:courseId">
+ <Route path="/course/:courseId/assignments"/>
+ <Route path="/course/:courseId/announcements"/>
+</Route>
+
+<!-- 0.6.x -->
+<Route path="/course/:courseId">
+ <Route path="assignments"/>
+ <Route path="announcements"/>
+</Route>
+
+Also . is no longer matched in dynamic segments.
<!-- 0.5.x -->
+<Route path="/file/:filename" />
+
+<!-- 0.6.x -->
+<Route path="/file/:filename.?:ext?" />
+
+<!--
+ or for a looser match to allow for multiple `.` note that the data
+ will be available on `this.props.params.splat` instead of
+ `this.props.params.filename`
+-->
+<Route path="/file/*" />
+
+Links should now pass their params in the params property, though the
+old behavior will still work, you should update your code soon because
+it will be removed by v1.0
// 0.5.x
+<Link to="user" userId="123"/>
+
+// 0.6.x
+<Link to="user" params={{userId: "123"}}/>
+
+If you have dynamic segments and are depending on getInitialState,
+componentWillMount, or componentDidMount to fire between transitions
+to the same route--like users/123 and users/456--then you have two
+options:
addHandlerKey={true} to your route and keep the previous
+behavior (but lose out on performance), orcomponentWillReceiveProps.// 0.5.x
+<Route handler={User} path="/user/:userId"/>
+
+// 0.6.x
+<Route handler={User} path="/user/:userId" addHandlerKey={true} />
+
+// 0.5.x
+var User = React.createClass({
+ getInitialState: function () {
+ return {
+ user: getUser(this.props.params.userId);
+ }
+ }
+});
+
+// 0.6.x
+var User = React.createClass({
+ getInitialState: function () {
+ return this.getState();
+ },
+
+ componentWillReceiveProps: function (newProps) {
+ this.setState(this.getState(newProps));
+ },
+
+ getState: function (props) {
+ props = props || this.props;
+ return {
+ user: getUser(props.params.userId)
+ };
+ }
+});
+
+We brought back <Routes/>.
// 0.4.x
+var routes = (
+ <Route handler={App} location="history">
+ <Route name="about" handler="about"/>
+ </Route>
+);
+
+// 0.5.x
+var routes = (
+ <Routes location="history">
+ <Route handler={App}>
+ <Route name="about" handler="about"/>
+ </Route>
+ </Routes>
+);
+
+NPM users should point their apps to react-router instead of
+react-nested-router. Make sure to npm prune!
0.11.x is now required.this.props.activeRoute became this.props.activeRouteHandler()// 0.2.x
+
+var App = React.createClass({
+ render: function () {
+ return (
+ <div>
+ {this.props.activeRoute}
+ </div>
+ );
+ }
+});
+
+// 0.3.x
+var App = React.createClass({
+ render: function () {
+ // now you can send extra props to the active route handler
+ // and use the new jsx syntax
+ // <this.props.activeRouteHandler extraProp={something}/>
+ return (
+ <div>
+ {this.props.activeRouteHandler()}
+ </div>
+ );
+ }
+});
+
+The Router function was removed.
// 0.1.x
+var router = Router(routes);
+router.renderComponent(element);
+
+// 0.2.x
+React.renderComponent(routes, element);
+
+
+
+ 在开发应用时,理解路由组件的生命周期是非常重要的。 +后面我们会以获取数据这个最常见的场景为例,介绍一下路由改变时,路由组件生命周期的变化情况。
+路由组件的生命周期和 React 组件相比并没有什么不同。 +所以让我们先忽略路由部分,只考虑在不同 URL 下,这些组件是如何被渲染的。
+路由配置如下:
+<Route path="/" component={App}>
+ <IndexRoute component={Home}/>
+ <Route path="invoices/:invoiceId" component={Invoice}/>
+ <Route path="accounts/:accountId" component={Account}/>
+</Route>
+
+| 组件 | +生命周期 | +
|---|---|
| App | +componentDidMount |
+
| Home | +componentDidMount |
+
| Invoice | +N/A | +
| Account | +N/A | +
| 组件 | +生命周期 | +
|---|---|
| App | +componentWillReceiveProps, componentDidUpdate |
+
| Home | +componentWillUnmount |
+
| Invoice | +componentDidMount |
+
| Account | +N/A | +
App 从 router 中接收到新的 props(例如 children、params、location 等数据),
+所以 App 触发了 componentWillReceiveProps 和 componentDidUpdate 两个生命周期方法Home 不再被渲染,所以它将被移除Invoice 首次被挂载/invoice/123 跳转到 /invoice/789| 组件 | +生命周期 | +
|---|---|
| App | +componentWillReceiveProps, componentDidUpdate | +
| Home | +N/A | +
| Invoice | +componentWillReceiveProps, componentDidUpdate | +
| Account | +N/A | +
所有的组件之前都已经被挂载, +所以只是从 router 更新了 props.
+/invoice/789 跳转到 /accounts/123| 组件 | +生命周期 | +
|---|---|
| App | +componentWillReceiveProps, componentDidUpdate | +
| Home | +N/A | +
| Invoice | +componentWillUnmount | +
| Account | +componentDidMount | +
虽然还有其他通过 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}/>
+ }
+
+})
+
+
+
+ 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 组件 中引入 RouteContext mixin,这样就会把 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?'
+ },
+
+ // ...
+
+})
+
+
+
+ React Router 适用于小型网站,比如 React.js Training,也可以支持 Facebook 和 Twitter 这类大型网站。
+对于大型应用来说,一个首当其冲的问题就是所需加载的 JavaScript 的大小。程序应当只加载当前渲染页所需的 JavaScript。有些开发者将这种方式称之为“代码分拆” —— 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。
+对于底层细节的修改不应该需要它上面每一层级都进行修改。举个例子,为一个照片浏览页添加一个路径不应该影响到首页加载的 JavaScript 的大小。也不能因为多个团队共用一个大型的路由配置文件而造成合并时的冲突。
+路由是个非常适于做代码分拆的地方:它的责任就是配置好每个 view。
+React Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。在首次加载包中你只需要有一个路径定义,路由会自动解析剩下的路径。
+Route 可以定义 getChildRoutes,getIndexRoute 和 getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React Router 会逐渐的匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。
如果配合 webpack 这类的代码分拆工具使用的话,一个原本繁琐的构架就会变得更简洁明了。
+const CourseRoute = {
+ path: 'course/:courseId',
+
+ getChildRoutes(location, callback) {
+ require.ensure([], function (require) {
+ callback(null, [
+ require('./routes/Announcements'),
+ require('./routes/Assignments'),
+ require('./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'))
+ })
+ }
+}
+
+现在,可以看一下 webpack 都做了哪些。开玩笑,我现在不想让你伤心。
+运行 huge apps 实例,打开浏览器的审查元素选项。你会发现在路由发生改变时,资源按需加载。
+ + +虽然在组件内部可以使用 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')
+
+
+
+ 服务端渲染与客户端渲染有些许不同,因为你需要:
+500 的响应30x 的响应为了迎合这一需求,你要在 <Router> API 下一层使用:
match 在渲染之前根据 location 匹配 routeRoutingContext 同步渲染 route 组件它看起来像一个虚拟的 JavaScript 服务器:
+import { renderToString } from 'react-dom/server'
+import { match, RoutingContext } from 'react-router'
+import routes from './routes'
+
+serve((req, res) => {
+ // 注意!这里的 req.url 应该是从初始请求中获得的
+ // 完整的 URL 路径,包括查询字符串。
+ match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
+ if (error) {
+ res.send(500, error.message)
+ } else if (redirectLocation) {
+ res.redirect(302, redirectLocation.pathname + redirectLocation.search)
+ } else if (renderProps) {
+ res.send(200, renderToString(<RoutingContext {...renderProps} />))
+ } else {
+ res.send(404, 'Not found')
+ }
+ })
+})
+
+至于加载数据,你可以用 renderProps 去构建任何你想要的形式——例如在 route 组件中添加一个静态的 load 方法,或如在 route 中添加数据加载的方法——由你决定。
React Router 是建立在 history 之上的。
+简而言之,一个 history 知道如何去监听浏览器地址栏的变化,
+并解析这个 URL 转化为 location 对象,
+然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
常用的 history 有三种形式, +但是你也可以使用 React Router 实现自定义的 history。
+ +你可以从 React Router 中引入它们:
+// JavaScript 模块导入(译者注:ES6 形式)
+import { browserHistory } from 'react-router'
+
+然后将它们传递给<Router>:
render(
+ <Router history={browserHistory} routes={routes} />,
+ document.getElementById('app')
+)
+
+browserHistoryBrowser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path这样真实的 URL 。
服务器需要做好处理 URL 的准备。处理应用启动最初的 / 这样的请求应该没问题,但当用户来回跳转并在 /accounts/123 刷新时,服务器就会收到来自 /accounts/123 的请求,这时你需要处理这个 URL 并在响应中包含 JavaScript 应用代码。
一个 express 的应用可能看起来像这样的:
+const express = require('express')
+const path = require('path')
+const port = process.env.PORT || 8080
+const app = express()
+
+// 通常用于加载静态资源
+app.use(express.static(__dirname + '/public'))
+
+// 在你应用 JavaScript 文件中包含了一个 script 标签
+// 的 index.html 中处理任何一个 route
+app.get('*', function (request, response){
+ response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
+})
+
+app.listen(port)
+console.log("server started on port " + port)
+
+如果你的服务器是 nginx,请使用 try_files 指令:
server {
+ ...
+ location / {
+ try_files $uri /index.html
+ }
+}
+当在服务器上找不到其他文件时,这可以让 nginx 服务器提供静态文件服务并指向index.html 文件。
对于Apache服务器也有类似的方式,创建一个.htaccess文件在你的文件根目录下:
RewriteBase /
+RewriteRule ^index\.html$ - [L]
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule . /index.html [L]
+如果我们能使用浏览器自带的 window.history API,那么我们的特性就可以被浏览器所检测到。如果不能,那么任何调用跳转的应用就会导致 全页面刷新,它允许在构建应用和更新浏览器时会有一个更好的用户体验,但仍然支持的是旧版的。
你可能会想为什么我们不后退到 hash history,问题是这些 URL 是不确定的。如果一个访客在 hash history 和 browser history 上共享一个 URL,然后他们也共享同一个后退功能,最后我们会以产生笛卡尔积数量级的、无限多的 URL 而崩溃。
+hashHistoryHash history 使用 URL 中的 hash(#)部分去创建形如 example.com/#/some/path 的路由。
createHashHistory吗?Hash history 不需要服务器任何配置就可以运行,如果你刚刚入门,那就使用它吧。但是我们不推荐在实际线上环境中用到它,因为每一个 web 应用都应该渴望使用 browserHistory。
?_k=ckuvup 没用的在 URL 中是什么?当一个 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。
createMemoryHistoryMemory 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) 的用例之前,我们来设想一下,一个不使用默认路由的路由配置是什么样的:
<Router>
+ <Route path="/" component={App}>
+ <Route path="accounts" component={Accounts}/>
+ <Route path="statements" component={Statements}/>
+ </Route>
+</Router>
+
+当用户访问 / 时, App 组件被渲染,但组件内的子元素却没有,
+App 内部的 this.props.children 为 undefined 。
+你可以简单地使用 `{this.props.children ||
但现在,Home 无法参与到比如 onEnter hook 这些路由机制中来。
+在 Home 的位置,渲染的是 Accounts 和 Statements。
+由此,router 允许你使用 IndexRoute ,以使 Home 作为最高层级的路由出现.
<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 可以参与进来。
如果你在这个 app 中使用 <Link to="/">Home</Link> ,
+它会一直处于激活状态,因为所有的 URL 的开头都是 / 。
+这确实是个问题,因为我们仅仅希望在 Home 被渲染后,激活并链接到它。
如果需要在 Home 路由被渲染后才激活的指向 / 的链接,请使用 <IndexLink to="/">Home</IndexLink>
路由配置是一组指令,用来告诉 router 如何匹配 URL以及匹配后如何执行代码。我们来通过一个简单的例子解释一下如何编写路由配置。
+import React from 'react'
+import { Router, Route, Link } from 'react-router'
+
+const App = React.createClass({
+ render() {
+ return (
+ <div>
+ <h1>App</h1>
+ <ul>
+ <li><Link to="/about">About</Link></li>
+ <li><Link to="/inbox">Inbox</Link></li>
+ </ul>
+ {this.props.children}
+ </div>
+ )
+ }
+})
+
+const About = React.createClass({
+ render() {
+ return <h3>About</h3>
+ }
+})
+
+const Inbox = React.createClass({
+ render() {
+ return (
+ <div>
+ <h2>Inbox</h2>
+ {this.props.children || "Welcome to your Inbox"}
+ </div>
+ )
+ }
+})
+
+const Message = React.createClass({
+ render() {
+ return <h3>Message {this.props.params.id}</h3>
+ }
+})
+
+React.render((
+ <Router>
+ <Route path="/" component={App}>
+ <Route path="about" component={About} />
+ <Route path="inbox" component={Inbox}>
+ <Route path="messages/:id" component={Message} />
+ </Route>
+ </Route>
+ </Router>
+), document.body)
+
+通过上面的配置,这个应用知道如何渲染下面四个 URL:
+| URL | +组件 | +
|---|---|
/ |
+App |
+
/about |
+App -> About |
+
/inbox |
+App -> Inbox |
+
/inbox/messages/:id |
+App -> Inbox -> Message |
+
想象一下当 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)
+
+现在,App 的 render 中的 this.props.children 将会是 <Dashboard>这个元素。这个功能类似 Apache 的DirectoryIndex 以及 nginx的 index指令,上述功能都是在当请求的 URL 匹配某个目录时,允许你制定一个类似index.html的入口文件。
我们的 sitemap 现在看起来如下:
+| URL | +组件 | +
|---|---|
/ |
+App -> Dashboard |
+
/about |
+App -> About |
+
/inbox |
+App -> Inbox |
+
/inbox/messages/:id |
+App -> Inbox -> Message |
+
如果我们可以将 /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 | +组件 | +
|---|---|
/ |
+App -> Dashboard |
+
/about |
+App -> About |
+
/inbox |
+App -> Inbox |
+
/messages/:id |
+App -> Inbox -> Message |
+
提醒:绝对路径可能在动态路由中无法使用。
+等一下,我们刚刚改变了一个 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:
Route 可以定义 onEnter 和 onLeave 两个 hook ,这些hook会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。
在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。
继续我们上面的例子,如果一个用户点击链接,从 /messages/5 跳转到 /about,下面是这些 hook 的执行顺序:
/messages/:id 的 onLeave/inbox 的 onLeave/about 的 onEnter因为 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" ... />
+
+
+
+ $2>")+u[2],f=u[0];while(f--)s=s.lastChild;jQuery.merge(c,s.childNodes),s=l.firstChild,s.textContent=""}}l.textContent="",h=0;while(i=c[h++]){if(r&&jQuery.inArray(i,r)!==-1)continue;a=jQuery.contains(i.ownerDocument,i),s=getAll(l.appendChild(i),"script"),a&&setGlobalEval(s);if(n){f=0;while(i=s[f++])rscriptType.test(i.type||"")&&n.push(i)}}return l},cleanData:function(e){var t,n,r,i,s=jQuery.event.special,o=0;for(;(n=e[o])!==undefined;o++){if(jQuery.acceptData(n)){i=n[data_priv.expando];if(i&&(t=data_priv.cache[i])){if(t.events)for(r in t.events)s[r]?jQuery.event.remove(n,r):jQuery.removeEvent(n,r,t.handle);data_priv.cache[i]&&delete data_priv.cache[i]}}delete data_user.cache[n[data_user.expando]]}}}),jQuery.fn.extend({text:function(e){return access(this,function(e){return e===undefined?jQuery.text(this):this.empty().each(function(){if(this.nodeType===1||this.nodeType===11||this.nodeType===9)this.textContent=e})},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var t=manipulationTarget(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var t=manipulationTarget(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?jQuery.filter(e,this):this,i=0;for(;(n=r[i])!=null;i++)!t&&n.nodeType===1&&jQuery.cleanData(getAll(n)),n.parentNode&&(t&&jQuery.contains(n.ownerDocument,n)&&setGlobalEval(getAll(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++)e.nodeType===1&&(jQuery.cleanData(getAll(e,!1)),e.textContent="");return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return jQuery.clone(this,e,t)})},html:function(e){return access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&t.nodeType===1)return t.innerHTML;if(typeof e=="string"&&!rnoInnerhtml.test(e)&&!wrapMap[(rtagName.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(rxhtmlTag,"<$1>$2>");try{for(;n