react-router学习笔记 author: @TiffanysBear
React Router
是完整的 React 路由解决方案
React Router
保持 UI 与 URL 同步。它拥有简单的 API 与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理。
index路由配置:添加首页,设置默认页面,使用 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)
通过 <Redirect>
中的 from 和 to 进行路由的重定向。
import { Redirect } from 'react-router'
React.render((
<Router>
<Route>
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
</Router>
));
Route 可以定义 onEnter 和 onLeave 两个 hook ,这些hook会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。
在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。
继续我们上面的例子,如果一个用户点击链接,从 /messages/5 跳转到 /about,下面是这些 hook 的执行顺序:
可以使用标签形式,也可以使用路由配置的方式进行:
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 是建立在 history 上的,简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
常见的 history 有三种形式:
三者之间的区别:
Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path这样真实的 URL 。 真实路由需要服务器也进行相应的配置。
Hash history 使用 URL 中的 hash(#)部分去创建形如 example.com/#/some/path 的路由。 Hash history 不需要服务器任何配置就能运行,但是不推荐在实际线上环境中使用。
像这样 ?_k=ckuvup 没用的在 URL 中是什么?(用来作为恢复 location state 的唯一 key 标识) 当一个 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。
Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。
和另外两种history的一点不同是你必须创建它,这种方式便于测试。
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
如果你在这个 app 中使用 Home , 它会一直处于激活状态,因为所有的 URL 的开头都是 / 。 这确实是个问题,因为我们仅仅希望在 Home 被渲染后,激活并链接到它。
如果需要在 Home 路由被渲染后才激活的指向 / 的链接,请使用 Home
代码分拆,按需加载。 React Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。在首次加载包中你只需要有一个路径定义,路由会自动解析剩下的路径。 配合webpack,根据路由拆分组件,按需加载。
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?'
},
// ...
})
服务端渲染与客户端渲染有些许不同,因为你需要:
发生错误时发送一个 500 的响应 需要重定向时发送一个 30x 的响应 在渲染之前获得数据 (用 router 帮你完成这点) 为了迎合这一需求,你要在 API 下一层使用:
使用 match 在渲染之前根据 location 匹配 route 使用 RoutingContext 同步渲染 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 中添加数据加载的方法——由你决定。 这块需要仔细了解一下具体的实现和原理。
在路由切换期间,组件生命周期的变化。 https://react-guide.github.io/react-router-cn/docs/guides/advanced/ComponentLifecycle.html
所有的之前已经被挂载的组件,组件内部 props 参数的更新,走的是 componentWillReceiveProps
,所以只是从 router 更新了 props。
组件内部导航使用 this.context.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')
通过 react-loadable,可以做到路由级别动态加载,或者更细粒度的模块级别动态加载:
const AsyncHome = Loadable({
loader: () => import('../components/Home/Home'),
loading: LoadingPage
})
<Route exact path="/" component={AsyncHome} />
当然上面展示的是 ReactRouter 中的用法,AsyncHome 可以在任何 JSX 中引用,这样就提升到了模块级别的动态加载。
注意,无论是 webpack 的 Tree Shaking,还是动态加载,都只能以 Commonjs 的源码为分析目标,对 node_modules 中代码不起作用,所以 npm 包请先做好拆包。或者类似 antd 按照约定书写组件,并提供一种 webpack-loader 自动完成按需加载。
通过 React Router Transition (Ant Motion 也很好用) 可以实现路由级别的动画:
<Router>
<AnimatedSwitch
atEnter={{ opacity: 0 }}
atLeave={{ opacity: 0 }}
atActive={{ opacity: 1 }}
className="switch-wrapper"
>
<Route exact path="/" component={Home} />
<Route path="/about/" component={About}/>
<Route path="/etc/" component={Etc}/>
</AnimatedSwitch>
</Router>
并提供了一些生命周期的回调,具体可以参考文档。现在动画的思路比较靠谱的也大致是这种:通过添加/移除 class 的方式,利用 css3 做动效。
当页面回退时,将滚动条恢复到页面最顶部,可以让单页路由看起来更加正常。由于 React Router4.0 中,路由是一种组件,我们可以利用 componentDidUpdate 简单完成滚动条复位的功能:
<Router history={history}>
<ScrollToTop>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="*" component={NotFound} />
</Switch>
</div>
</ScrollToTop>
</Router>
@withRouter
class ScrollToTop extends Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0)
}
}
render() {
return this.props.children
}
}
非通过 Route 渲染的组件,可以通过 withRouter 拿到路由信息,仅当其为 Router 的子元素时有效。
React Router4.0 嵌套路由与 3.0 不同,是通过组件 Route 的嵌套实现的。
在任何组件,都可以使用如下代码实现嵌套路由:
<Route path={`${this.props.match.url}/:id`} component={NestComponent} />
这样将路由功能切分到各个组件中,我现在的项目甚至已经没有 route.js 文件了,路由由 layout 与各个组件自身承担。这种设计思路与 Nestjs 的描述性路由具有相同的思想 - 在 nodejs 中,我们可以通过装饰器,在任意一个 Action 上描述其访问的 URL:
@POST("/api/service")
async someAction() {}
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有