3. react-router-dom源码揭秘 - BrowserRouter

栏目: 服务器 · 发布时间: 5年前

内容简介:今天开始,我们开始揭开react-router-dom神秘的头盖骨,哦不,面纱。 在此之前,我们需要了解一些预备知识:React的context和react-router-dom的基本使用。需要复习的同学请移步:下面是我跟小S同学一起阅读源码的过程。 大家可以参照这个思路,进行其他开源项目源码的学习。我:小S,今天我们来一起学习React-router-dom的源码吧

今天开始,我们开始揭开react-router-dom神秘的头盖骨,哦不,面纱。 在此之前,我们需要了解一些预备知识:React的context和react-router-dom的基本使用。需要复习的同学请移步:

下面是我跟小S同学一起阅读源码的过程。 大家可以参照这个思路,进行其他开源项目源码的学习。

我:小S,今天我们来一起学习React-router-dom的源码吧

3. react-router-dom源码揭秘 - BrowserRouter 好呀!

我:首先,react-router的官网上,有基本的使用方法。这里 (中文点击这里) 列出了常用的组件,以及它们的用法

  1. Router (BrowserRouter, HashRouter)
  2. Route
  3. Switch
  4. Link

3. react-router-dom源码揭秘 - BrowserRouter 好的, 继续

我:先从这些组件的源码入手,那肯定第一个就是BrowserRouter,或者HashRouter

3. react-router-dom源码揭秘 - BrowserRouter 那应该怎么入手呢?

我:首先,从 github 上,得到与文档版本对应的代码。

我:接着看路径结构。是这样的:

3. react-router-dom源码揭秘 - BrowserRouter

3. react-router-dom源码揭秘 - BrowserRouter 接下来我一般就是找教程先简单过一遍,代码下下来然后把node__modules复制出来debugger 然后看不懂了就放弃

我:不,你进入细节之前,要先搞清楚代码的结构

3. react-router-dom源码揭秘 - BrowserRouter 恩啊, 不然怎么找代码

我:你看到这个路径之后,第一步,应该看一看,这些文件夹都是干啥的,哪个是你需要的

3. react-router-dom源码揭秘 - BrowserRouter

script是build, website是doc, packges是功能

3. react-router-dom源码揭秘 - BrowserRouter

这个都差不多

我:对。打开各个文件夹,会发现,packages里面的东西,是我们想要的源码。

3. react-router-dom源码揭秘 - BrowserRouter

我:我们肯定先从源码看起,因为这次读源码首先要学习的是 实现原理 ,并不是如何构建

我:那咱们就从 react-router-dom 开始呗

我:打开react-router-dom,奔着modules去

3. react-router-dom源码揭秘 - BrowserRouter

3. react-router-dom源码揭秘 - BrowserRouter 直接从github上下载master的分支么

我: 嗯

3. react-router-dom源码揭秘 - BrowserRouter

为啥看modules

3. react-router-dom源码揭秘 - BrowserRouter

不应该先看package.json和rollup么

我:核心代码,肯定是在modules里了。我要先看看整个的结构,有个大致的印象

3. react-router-dom源码揭秘 - BrowserRouter 恩恩

我:打开modules就看到了我们刚刚文档中提及的几个组件了

我:我们先从 BrowserRouter.js 入手

3. react-router-dom源码揭秘 - BrowserRouter 嗯哼

我:那我要打开这个文件,开始看代码了

我:我先不关注package.json这些配置文件

3. react-router-dom源码揭秘 - BrowserRouter 残暴

我:因为我这次是要看原理,不是看整个源码如何build

我:配置文件也是辅助而已

3. react-router-dom源码揭秘 - BrowserRouter

嗯啊。

3. react-router-dom源码揭秘 - BrowserRouter

可是有时候还是很重要的

我:那就用到了再说

3. react-router-dom源码揭秘 - BrowserRouter 是不是至少看一下都用了什么和几个入口

我:用到了什么也不需要在package.json中看,因为我关注的那几个组件,用到啥会import的。所以看源码,最重要的是focus on。你要有关注点,因为有的源码,是非常庞大的。一不小心就掉进了细节的海洋出不来了。

3. react-router-dom源码揭秘 - BrowserRouter

有道理

3. react-router-dom源码揭秘 - BrowserRouter

比如react

我:对,你不可能一次就读懂他里面的东西,所以你要看很多次

我:每次的关注点可以不同

3. react-router-dom源码揭秘 - BrowserRouter

恩啊

3. react-router-dom源码揭秘 - BrowserRouter

确实如此

我:都揉到一起,会觉得非常乱,最后就放弃了

我:而且,我们学习源码,也不一定要把源码中的每个特性都在同一个项目中都用到,还是要分开学,分开用

3. react-router-dom源码揭秘 - BrowserRouter

有道理

3. react-router-dom源码揭秘 - BrowserRouter

我就总忍不住乱看

我:那就先看 BrowserRouter.js 了。

我:打开文件,看了一下,挺开心,代码没几行

import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";

/**
 * The public API for a <Router> that uses HTML5 history.
 */
class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}

if (__DEV__) {
  //此处省略若干行代码
}

export default BrowserRouter;
复制代码

3. react-router-dom源码揭秘 - BrowserRouter 然后一脸懵逼记不住, 看不懂

我:哈哈,代码这么少,那肯定是有依赖组件了

我:先看看依赖了哪些组件

我:我最感兴趣的是history和react-router。如下:

import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";
复制代码
3. react-router-dom源码揭秘 - BrowserRouter

history是库啊

3. react-router-dom源码揭秘 - BrowserRouter

等等

3. react-router-dom源码揭秘 - BrowserRouter

我有点没跟上

我:等待了30秒......

为啥我感兴趣这俩呢

3. react-router-dom源码揭秘 - BrowserRouter

你的兴趣点对

3. react-router-dom源码揭秘 - BrowserRouter

我以前看过源码相关教程,了解一点history

我:嗯。官网说了啊。

Routers

At the core of every React Router application should be a router component. For web projects, react-router-dom provides and routers. Both of these will create a specialized history object for you.

我:在实现路由的时候,肯定是用到history的

我:所以,这个可能会作为读源码的预备知识。( 如果伙伴们有需求,请在评论中说明,我们可以再加一篇关于history的文章 )

我:但是我先不管他,看看影响react-router的阅读不

我:另外,之前说过,这个文件源码行数很少,肯定依赖了其他的组件。看起来,这个react-router担当了重要职责。

我:所以现在有两个Todos: historyreact-router

3. react-router-dom源码揭秘 - BrowserRouter

我:那一会需要关注的就是react-router这个包了

我:我暂时先不管刚才的两个todos,我把这个组件( BrowserRouter )先看看,反正代码又不多

class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}

if (__DEV__) {
  //此处省略若干行代码
}
复制代码

我:我要把if(__DEV__)的分支略过,因为我现在要看的是最最核心的东西

我:切记过早的进入__DEV__,那个是方便开发用的,通常与核心的概念关系不大

我:那就只剩俩东西了

//......
class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
//......
复制代码

我:所以现在BrowserRouter的任务,就是创建一个history对象,传给react-router的<Router>组件

3. react-router-dom源码揭秘 - BrowserRouter 这个时候

我:嗯,你说

3. react-router-dom源码揭秘 - BrowserRouter 你会选择看react-router还是history

我:哈哈,这个时候,我其实想看一眼HashRouter

3. react-router-dom源码揭秘 - BrowserRouter 我也是

我:因为import的那句话

import { createBrowserHistory as createHistory } from "history";
复制代码

所以我有理由怀疑,HashRouter的代码类似,只是从history包中导入了不同的函数

HashRouter.js

import React from "react";
import { Router } from "react-router";
import { createHashHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";
复制代码

还真是

我:那我就把关注点,放在react-router上了

我:因为

  1. history我猜出他是干啥了,跟浏览器路径有关
  2. router里面如果用到history了,我等到在读码时,遇到了阻碍,再去看history,这样行不行

3. react-router-dom源码揭秘 - BrowserRouter 恩啊

我:回到这个路径

3. react-router-dom源码揭秘 - BrowserRouter

我:去看react-router

3. react-router-dom源码揭秘 - BrowserRouter 为什么是他

我:因为它导入包时,没加相对路径啊

我:说明这是一个已经发布的node包,导入时需要在node_modules路径下找

import { Router } from "react-router";
复制代码

我:我就往上翻一翻呗,当然,估计在配置文件中,应该会有相关配置

3. react-router-dom源码揭秘 - BrowserRouter 恩恩

我:进这个路径,文件真tmd多,mmp的

3. react-router-dom源码揭秘 - BrowserRouter
我:

导入的是一个包,包下有index.js,我是不是应该先看这个js

3. react-router-dom源码揭秘 - BrowserRouter 我是这个习惯,先看index是不是只做了import

我:但是其实我们在使用recat-router-dom的时候,网上会有一些与react-router的比较的讨论,

3. react-router-dom源码揭秘 - BrowserRouter

没太注意

3. react-router-dom源码揭秘 - BrowserRouter

稀里糊涂

我:所以,react-router是一个已经发布的node包。但是,我并不确定他的代码在哪,如果找不到,我可能会从github上其他的位置找,或者从npm的官网找链接了

3. react-router-dom源码揭秘 - BrowserRouter 恩啊

我:进index.js吧

"use strict";

if (process.env.NODE_ENV === "production") {
  module.exports = require("./cjs/react-router.min.js");
} else {
  module.exports = require("./cjs/react-router.js");
}
复制代码

我:代码不多,分成production和else俩分支

我:我会选择else分支

我:但是发现一个问题啊,我艹

我:当前路径下,没有cjs文件夹

我:因为BrowserRouter导入的是一个包

我:所以这个包,得是build之后的

3. react-router-dom源码揭秘 - BrowserRouter 这个时候就要看packge的script了

我:嗯,可以的

我:不过我感觉略微跑偏了

我:我要回到router本身上

3. react-router-dom源码揭秘 - BrowserRouter

好好

3. react-router-dom源码揭秘 - BrowserRouter

继续

3. react-router-dom源码揭秘 - BrowserRouter

怎么回到router本身

我:/react-router下,有一个router.js文件

我:打开看,只有那两行代码,不是我要的东西啊

我:它导出的,还是index.js编译之后的

3. react-router-dom源码揭秘 - BrowserRouter 看modules

我:对,看modules

我:打开modules下的Router.js

3. react-router-dom源码揭秘 - BrowserRouter

要是我的话, 这个时候就跑偏了

3. react-router-dom源码揭秘 - BrowserRouter

直接去看rollup了

3. react-router-dom源码揭秘 - BrowserRouter

然后最后找到router

3. react-router-dom源码揭秘 - BrowserRouter

router.js

我:我也可能会跑偏

我:我之前就跑到history上去了

我:但是后来想想,这样不太好

我:从看源码角度说,直接找到modules下的Router.js很容易

我:因为其他文件,一看就不是源码实现

3. react-router-dom源码揭秘 - BrowserRouter 嗯啊

我:现在打开它,一看,挺像啊,那先看看有多少行

我:百十来行,有信心了,哈哈

import React from "react";
import PropTypes from "prop-types";
import warning from "tiny-warning";

import RouterContext from "./RouterContext";
import warnAboutGettingProperty from "./utils/warnAboutGettingProperty";

function getContext(props, state) {
  return {
    history: props.history,
    location: state.location,
    match: Router.computeRootMatch(state.location.pathname),
    staticContext: props.staticContext
  };
}

/**
 * The public API for putting history on context.
 */
class Router extends React.Component {
  static computeRootMatch(pathname) {
    return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
  }

  constructor(props) {
    super(props);

    this.state = {
      location: props.history.location
    };

    // This is a bit of a hack. We have to start listening for location
    // changes here in the constructor in case there are any <Redirect>s
    // on the initial render. If there are, they will replace/push when
    // they mount and since cDM fires in children before parents, we may
    // get a new location before the <Router> is mounted.
    this._isMounted = false;
    this._pendingLocation = null;

    if (!props.staticContext) {
      this.unlisten = props.history.listen(location => {
        if (this._isMounted) {
          this.setState({ location });
        } else {
          this._pendingLocation = location;
        }
      });
    }
  }

  componentDidMount() {
    this._isMounted = true;

    if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation });
    }
  }

  componentWillUnmount() {
    if (this.unlisten) this.unlisten();
  }

  render() {
    const context = getContext(this.props, this.state);

    return (
      <RouterContext.Provider
        children={this.props.children || null}
        value={context}
      />
    );
  }
}

// TODO: Remove this in v5
if (!React.createContext) {
  Router.childContextTypes = {
    router: PropTypes.object.isRequired
  };

  Router.prototype.getChildContext = function() {
    const context = getContext(this.props, this.state);

    if (__DEV__) {
      const contextWithoutWarnings = { ...context };

      Object.keys(context).forEach(key => {
        warnAboutGettingProperty(
          context,
          key,
          `You should not be using this.context.router.${key} directly. It is private API ` +
            "for internal use only and is subject to change at any time. Instead, use " +
            "a <Route> or withRouter() to access the current location, match, etc."
        );
      });

      context._withoutWarnings = contextWithoutWarnings;
    }

    return {
      router: context
    };
  };
}

if (__DEV__) {
  Router.propTypes = {
    children: PropTypes.node,
    history: PropTypes.object.isRequired,
    staticContext: PropTypes.object
  };

  Router.prototype.componentDidUpdate = function(prevProps) {
    warning(
      prevProps.history === this.props.history,
      "You cannot change <Router history>"
    );
  };
}

export default Router;
复制代码
3. react-router-dom源码揭秘 - BrowserRouter

然后这么少的代码

3. react-router-dom源码揭秘 - BrowserRouter

第一反应看一下引入

我:对

我:但是你看,一共五个

import React from "react";
import PropTypes from "prop-types";
import warning from "tiny-warning";

import RouterContext from "./RouterContext";
import warnAboutGettingProperty from "./utils/warnAboutGettingProperty";
复制代码

3. react-router-dom源码揭秘 - BrowserRouter 前三个忽略,一看就没用

我:是的

我:我现在其实有点关注第五个了

3. react-router-dom源码揭秘 - BrowserRouter 我会看render

我:先不着急

我:因为如果第五个的名字叫做warnXXXX

我:是警告的意思

3. react-router-dom源码揭秘 - BrowserRouter

恩恩

3. react-router-dom源码揭秘 - BrowserRouter

搜一下

我:警告通常都是开发版本的东西,如果能排除,那就剩第四个依赖了

3. react-router-dom源码揭秘 - BrowserRouter

可能没用

3. react-router-dom源码揭秘 - BrowserRouter

再一看,是在__DEV__里面的

我:对,当前文件搜索了一下,在__DEV__分支下,不看了,哈哈

我:那就剩一个context.js了呗

3. react-router-dom源码揭秘 - BrowserRouter 过分

我:我觉得我现在想扫一眼这个文件,如果内容不多,我就先搞他,如果多的话,那就先放那

3. react-router-dom源码揭秘 - BrowserRouter 恩恩

我:那我去看一看吧,哈哈

我:进RouterContext.js这个文件了

// TODO: Replace with React.createContext once we can assume React 16+
import createContext from "create-react-context";

const context = createContext();

context.Provider.displayName = "Router.Provider";
context.Consumer.displayName = "Router.Consumer";

export default context;
复制代码

我:我次奥了

3. react-router-dom源码揭秘 - BrowserRouter

我:十行不到,我把他搞定,我就可以专注Router.js那个文件了。那个文件里面的内容,就是全部Router的核心了

我:这里是标准context用法,店长推荐的,参见这个

我:返回Router.js了哈

3. react-router-dom源码揭秘 - BrowserRouter

然后呢

3. react-router-dom源码揭秘 - BrowserRouter

看createContext么

我:createContex就是最新的context用法,参见这个

我:所以,需要有准备知识,哈哈

我:简单点说,就是一个提供者(Provider),一个是消费者(Consumer)

我:我这次看的是react-router

我:别跑偏了

我:回到router.js去了

我:这个时候,可以稍微进入细节一些了

我:从第一个函数定义开始

function getContext(props, state) {
  return {
    history: props.history,
    location: state.location,
    match: Router.computeRootMatch(state.location.pathname),
    staticContext: props.staticContext
  };
}
复制代码

我:从名字看,是获取context的,每次调用返回一个 新创建 的对象,多余的不知道,先放着,往后看

3. react-router-dom源码揭秘 - BrowserRouter

我:我先大概扫一眼组件都有哪些方法。另外发现,除了组件,还有其他代码

我:除了组件内容,组件下面有一个判断,看起来应该是处理老版本react的兼容问题的。那我就先不看了

// TODO: Remove this in v5
if (!React.createContext) {
  Router.childContextTypes = {
    router: PropTypes.object.isRequired
  };

  Router.prototype.getChildContext = function() {
    const context = getContext(this.props, this.state);

    if (__DEV__) {
      const contextWithoutWarnings = { ...context };

      Object.keys(context).forEach(key => {
        warnAboutGettingProperty(
          context,
          key,
          `You should not be using this.context.router.${key} directly. It is private API ` +
            "for internal use only and is subject to change at any time. Instead, use " +
            "a <Route> or withRouter() to access the current location, match, etc."
        );
      });

      context._withoutWarnings = contextWithoutWarnings;
    }

    return {
      router: context
    };
  };
}
复制代码

我:所以,重点就是在这个组件里面了。组件里面就是一些生命周期函数

我:constructor、componentDidMount

我:这俩,是初始化的地方

3. react-router-dom源码揭秘 - BrowserRouter 嗯嗯

我:一个一个看

我:重点是那个判断

if (!props.staticContext) {
  this.unlisten = props.history.listen(location => {
    if (this._isMounted) {
      this.setState({ location });
    } else {
      this._pendingLocation = location;
    }
  });
}
复制代码

我:if (!props.staticContext) {}的作用,是保证Router里面再嵌套Router时,使用的是相同的history

我:里面是一个监听,监听history中的location的改变,也就是说,当通过这个history改变路径时,会统一监听,统一处理

3. react-router-dom源码揭秘 - BrowserRouter 嗯嗯

我:那里面就调用了setState了呗,接着render就执行了

3. react-router-dom源码揭秘 - BrowserRouter

我:render非常简单,就是把context的value值,修改了一下

3. react-router-dom源码揭秘 - BrowserRouter 嗯啊

我:我们知道,只要context的value一变化,对应的consumer的函数,就会被调用,是吧

3. react-router-dom源码揭秘 - BrowserRouter 嗯嗯

我:那现在Router就结束了

我:接下来,我们好奇的是,哪些组件使用了Consumer

3. react-router-dom源码揭秘 - BrowserRouter 找route

我:对。根据React-router的使用,估计就是每个<Route>,都会监听这个context,然后进行路径匹配,决定是否要渲染自己的component属性所指定的内容

我:接下来,我们就可以继续看这个组件了。先吃饭去吧,<Route>解读,且听下回分解。

3. react-router-dom源码揭秘 - BrowserRouter 嗯,好的。拜拜。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

一站式学习C编程

一站式学习C编程

宋劲杉 / 电子工业出版社 / 2011-3 / 59.00元

《一站式学习c编程》有两条线索,一条线索是以linux平台为载体全面深入地介绍c语言的语法和程序的工作原理,另一条线索是介绍程序设计的基本思想和开发调试方法。本书分为两部分:第一部分讲解编程语言和程序设计的基本思想方法,让读者从概念上认识c语言;第二部分结合操作系统和体系结构的知识讲解程序的工作原理,让读者从本质上认识c语言。 《一站式学习c编程》适合做零基础的初学者学习c语言的第一本教材,......一起来看看 《一站式学习C编程》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具