react-router学习笔记

react-router学习笔记
author: @TiffanysBear

基本介绍

React Router 是完整的 React 路由解决方案

React Router 保持 UI 与 URL 同步。它拥有简单的 API 与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理。

基础部分

路由配置

index路由配置:添加首页,设置默认页面,使用 IndexRoute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)

重定向 Redireact

通过 <Redirect> 中的 from 和 to 进行路由的重定向。

1
2
3
4
5
6
7
8
9
import { Redirect } from 'react-router'

React.render((
<Router>
<Route>
<Redirect from="messages/:id" to="/messages/:id" />
</Route>
</Router>
));

进入和离开的 Hook

Route 可以定义 onEnter 和 onLeave 两个 hook ,这些hook会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。

在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。

继续我们上面的例子,如果一个用户点击链接,从 /messages/5 跳转到 /about,下面是这些 hook 的执行顺序:

  • /messages/:id 的 onLeave
  • /inbox 的 onLeave
  • /about 的 onEnter

路由配置方式

可以使用标签形式,也可以使用路由配置的方式进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 呢?

  • 嵌套关系:深度优先遍历整个路由配置
  • 路径语法:相对路径的话,会根据嵌套关系,与自身路径进行拼接;绝对路径会忽略嵌套关系
  • 优先级:路由算法会根据定义的顺序自顶向下匹配路由,要注意前一个路由不会被后一个路由匹配所忽略替换。

History

React Router 是建立在 history 上的,简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。

常见的 history 有三种形式:

  • browserHistory
  • hashHistory
  • createMemoryHistory

三者之间的区别:

browserHistory

Browser history 是使用 React Router 的应用推荐的 history。它使用浏览器中的 History API 用于处理 URL,创建一个像example.com/some/path这样真实的 URL 。
真实路由需要服务器也进行相应的配置。

hashHistory

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。

createMemoryHistory

Memory history 不会在地址栏被操作或读取。这就解释了我们是如何实现服务器渲染的。同时它也非常适合测试和其他的渲染环境(像 React Native )。

和另外两种history的一点不同是你必须创建它,这种方式便于测试。

默认路由 IndexRoute

1
2
3
4
5
6
7
<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 来安装这个钩子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 实现组件外部的导航。

1
2
3
4
5
6
7
8
// 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,可以做到路由级别动态加载,或者更细粒度的模块级别动态加载:

1
2
3
4
5
6
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 也很好用) 可以实现路由级别的动画:

1
2
3
4
5
6
7
8
9
10
11
12
<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 简单完成滚动条复位的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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 的嵌套实现的。

在任何组件,都可以使用如下代码实现嵌套路由:

1
<Route path={`${this.props.match.url}/:id`} component={NestComponent} />

这样将路由功能切分到各个组件中,我现在的项目甚至已经没有 route.js 文件了,路由由 layout 与各个组件自身承担。这种设计思路与 Nestjs 的描述性路由具有相同的思想 - 在 nodejs 中,我们可以通过装饰器,在任意一个 Action 上描述其访问的 URL:

1
2
@POST("/api/service")
async someAction() {}

常见的使用和属性

  • : 渲染第一个被匹配到的路由
  • withRouter : 为组件注入

服务端渲染原理 React SSR

广告时间啦~

投递地址:字节内推传送门,海量职位等你挑~
  • 内推完全免费,简历可获得优先筛选,进入面试人数众多;
  • 直接通过我提供的链接进入投递,即可算作是我的内推,可靠、方便、快捷;
  • 搜索 zhouf_Tiffany 加我个人微信(需要备注:字节内推-职位-真名,例如:字节内推-Android-张三,否则不加),一对一跟进进度,处理异常进度,人肉跟 HR 联系处理(已救活数人);
  • 及时同步最新招聘消息,有问题也可及时反馈给候选人;