React-Router API简介和源码实现

577 阅读5分钟

React路由库。基本react项目开发过程中都会用到。

API

Router

<BrowserRouter> 使用 HTML5 提供的 history API( pushState , replaceState 和 popstate 事件) 来保持 UI和 URL 的同步。(推荐使用)
<HashRouter>使用 URL 的 hash 部分(即window.location.hash )来保持 UI 和 URL 的同步。(不推荐使用)
<MemoryRourter>把 URL 的历史记录保存在内存中的 <Router>(不读取、不写入地址栏)。在测试和非浏览器环境中很有用,如ReactNative。

注意:<HashRouter> 不支持 location.key 和location.state。在以前的版本中,我们曾尝试 shim 这种行为,但是仍有一些边缘问题无法解决。因此任何依 赖此行为的代码或插件都将无法正常使用。由于该技术仅 用于支持旧浏览器,因此我们鼓励大家使用<BrowserHistory>

BrowserRouter与HashRouter对比:

  1. HashRouter最简单,不需要服务器端渲染,靠浏览器的# 的来区分path就可以,BrowserRouter需要服务器端对不 同的URL返回不同的HTML,后端需要配置。
  2. BrowserRouter使⽤用HTML5 history API( pushState, replaceState和popstate事件),让页面的UI于URL同 步。
  3. HashRouter不⽀支持location.key和location.state,动态路 由跳转需要通过?传递参数。
  4. Hash history 不需要服务器任何配置就可以运行,如果你刚入门,那就使用它吧。但是我们不推荐在实际线上环境中用到它,因为每一个 web应用都应该渴望使用browserHistory 。

Link

to: String

一个字符串形式的链接地址,通过 pathname 、 search 和hash 属性创建。

<Link to='/courses?sort=name' />
to: Object

个对象形式的链接地址,可以具有以下任何属性:

  • pathname 要链接到的路径
  • search 查询参数
  • hash URL中的hash,例如 #adc
  • state 存储到location中的状态数据
<Link to={{
    pathname: '/courses',
    search: '?sort=name',
    hash: '#the-hash',
    state: {
      redirect: '/login'
    }
}} />
replace: Boolean

当设置为 true 时,点击链接后将替换历史堆栈中的当前条目,而不是添加新条目。默认为 false 。

Route

当Route的path和location匹配时,展示相应的UI组件。

Route的三种渲染方式:
  • children: function
  • component
  • render: function

在不同的情况下使用不同的方式。在指定的<Route> 中,你应该只使用其中的一种。 Route渲染优先级:children > component > render

path: String

可以是 path-to-regexp 能够理解的任何有效的 URL 路径。

<Route path="/users/:id" component={User} />

没有定义path<Route>总是会被匹配

location: Object

⼀般情况下, <Route>尝试将其 path 与当前history location(通常是当前的浏览器 URL)进⾏行行匹配。但是,带 有不同pathnamelocation也可以与之匹配。 当你需要将<Route> 与一个不是当前locationlocation 进行匹配时,会发现这个api非常有用。如过渡动画。 如果⼀个<Route> 被包裹在一个<Switch> 中,并且与 <Switch>location相匹配(或者是当前的location),那 么<Route>location 参数将被<Switch>所使用的 location覆盖。 (ps:我也不太明白,等会去实践一下,这就是官网的解释)

Redirect

to:String

要重定向到的 URL,可以是 path-to-regexp 能够理解的任何 有效的 URL 路径。所有要使用的 URL 参数必须由 from 提供。

<Redirect to="/somewhere/else" />
to: Object

要重定向到的位置,其中 pathname 可以是 path-to-regexp 能够理解的任何有效的 URL 路径。

<Redirect to={{
    pathname: '/login',
    search: '?utm=your+face',
    state: {
        referrer: currentLocation
    }
}} />

state 对象可以在重定向到的组件中通过this.props.location.state 进行访问

Switch

用于渲染与路径匹配的第一个子<Route><Redirect>

<Route path="/login" component={Login} />
<Route path="/hoc" component={HocComponent} />
<Route path="/render" render={() => <h1>render div</h1>} />
<Route children={() => <h2>children div</h2>} />

这段代码,最后一个Route不管path匹不匹配都会一直渲染

<Switch>
    <Route path="/login" component={Login} />
    <Route path="/hoc" component={HocComponent} />
    <Route path="/render" render={() => <h1>render div</h1>} />
    <Route children={() => <h2>children div</h2>} />
</Switch>

加上Switch,当location匹配到/login/hoc/render的时候,就会显示对应的UI,当location和这些都不匹配,则展示最后一个没有Route

所有 <Switch> 的子元素都应该是<Route><Redirect> 。只有第⼀个匹配当前路径的子元素将被呈现。

源码实现

react router源码实现基于React 的高阶API React.Context实现。不了解的可以看下初学Context

BrowserRouter

简单分析功能:BrowserRouter作用是把history和location传递下去。当location改变时,监听改变location的数据。 简单实现:

import React, { Component } from 'react';
import {createBrowserHistory} from "history";
import { RouterProvider } from "./RouterContext";

export default class BrowserRouter extends Component {
  constructor(props) {
    super(props);
    this.history = createBrowserHistory();
    this.state={
      location: this.history.location
    }

    this.unlisten = this.history.listen((location) => {
      this.setState({location})
    })
  }

  componentWillUnmount(){
    this.unlisten = null;
  }
  
  render() {
    return (
      <RouterProvider value={{history: this.history, location: this.state.location}}>
        {this.props.children}
      </RouterProvider>
    )
  }
}

Link

简单分析功能:就是个<a>的功能,当用户点击的时候,改变location。 简单实现:

import React, { Component } from 'react';
import { RouterContext } from './RouterContext';

export default class Link extends Component {

  static contextType = RouterContext;

  handleClick = (e) => {
    e.preventDefault();
    this.context.history.push(this.props.to)
  }

  render() {
    return (
      <a onClick={this.handleClick}>
        {this.props.children}
      </a>
    )
  }
}

Route

简单分析功能:Route需要根据location和path进行比较,如果匹配,则返回相应的组件。但是Route的渲染方式有三种:children component render
渲染顺序依次是children > component > render

简单实现:

import React, { Component } from "react";
import { RouterConsumer, RouterProvider } from "./RouterContext";

export default class Route extends Component {
  render() {
    return (
      <RouterConsumer>
        {context => {
          const { path, component, children, render } = this.props;
          const location = this.props.location || context.location;
          const match = path === context.location.pathname;

          const props = {
            ...context,
            location,
            match
          };
          // 第一层
          // match 渲染顺序 children > component > render
          // no match 渲染children或者null
          // 第二层 
          // children 存在 判断children是不是函数,是 则调用,否 则返回children  
          // children 不存在 判断 component是否存在 
          // 第三层 
          //                 component 存在    返回 component
          //                 component 不存在  判断render是否存在 
          // 第四层 
          //                                   render 存在 调用render 
          //                                   render 不存在 返回null
          return (
            <RouterProvider value={props}>
              {
                match
                  ? ( children
                      ? typeof children === "function"
                        ? children(this.props)
                        : children
                      : component
                      ? React.createElement(component, this.props)
                      : render
                      ? render(this.props)
                      : null
                  ) : (
                    typeof children === "function" ? children(this.props) : null
                  )
              }
            </RouterProvider>
          )
        }}
      </RouterConsumer>
    );
  }
}

Switch

暂时还没实现... 等实现再加上


That's all. Thanks

脚踏实地,仰望星空。