react-router学习笔记

时间:2022-07-25
本文章向大家介绍react-router学习笔记,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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)

重定向 Redireact

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

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

路由配置方式

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

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

<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() {}

常见的使用和属性

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

服务端渲染原理 React SSR