今天开始,咱们开始揭开react-router-dom神秘的头盖骨,哦不,面纱。 在此以前,咱们须要了解一些预备知识:React的context和react-router-dom的基本使用。须要复习的同窗请移步:node
下面是我跟小S同窗一块儿阅读源码的过程。 你们能够参照这个思路,进行其余开源项目源码的学习。react
我: 小S,今天咱们来一块儿学习React-router-dom的源码吧git
好呀!
我: 首先,react-router的官网上,有基本的使用方法。 这里 (中文点击这里) 列出了经常使用的组件,以及它们的用法github
好的, 继续
我: 先从这些组件的源码入手,那确定第一个就是BrowserRouter,或者HashRouterweb
那应该怎么入手呢?
我: 首先,从github上,获得与文档版本对应的代码。
我: 接着看路径结构。是这样的:npm
接下来我通常就是找教程先简单过一遍,代码下下来而后把node__modules复制出来debugger 而后看不懂了就放弃
我: 不,你进入细节以前,要先搞清楚代码的结构json
恩啊, 否则怎么找代码
我: 你看到这个路径以后,第一步,应该看一看,这些文件夹都是干啥的,哪一个是你须要的浏览器
script是build, website是doc, packges是功能
这个都差很少
我: 对。打开各个文件夹,会发现,packages里面的东西,是咱们想要的源码。react-router
我: 咱们确定先从源码看起,由于此次读源码首先要学习的是实现原理,并非如何构建
我: 那我们就从react-router-dom开始呗
我: 打开react-router-dom,奔着modules去app
直接从github上下载master的分支么
我: 嗯
为啥看modules
不该该先看package.json和rollup么
我: 核心代码,确定是在modules里了。我要先看看整个的结构,有个大体的印象
恩恩
我: 打开modules就看到了咱们刚刚文档中说起的几个组件了
我: 咱们先从BrowserRouter.js入手
嗯哼
我: 那我要打开这个文件,开始看代码了
我: 我先不关注package.json这些配置文件
残暴
我: 由于我此次是要看原理,不是看整个源码如何build
我: 配置文件也是辅助而已
嗯啊。
但是有时候仍是很重要的
我: 那就用到了再说
是否是至少看一下都用了什么和几个入口
我: 用到了什么也不须要在package.json中看,由于我关注的那几个组件,用到啥会import的。因此看源码,最重要的是focus on。你要有关注点,由于有的源码,是很是庞大的。一不当心就掉进了细节的海洋出不来了。
有道理
好比react
我: 对,你不可能一次就读懂他里面的东西,因此你要看不少次
我: 每次的关注点能够不一样
恩啊
确实如此
我: 都揉到一块儿,会以为很是乱,最后就放弃了
我: 并且,咱们学习源码,也不必定要把源码中的每一个特性都在同一个项目中都用到,仍是要分开学,分开用
有道理
我就总忍不住乱看
我: 那就先看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; 复制代码
而后一脸懵逼记不住, 看不懂
我: 哈哈,代码这么少,那确定是有依赖组件了
我: 先看看依赖了哪些组件
我: 我最感兴趣的是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";
复制代码
history是库啊
等等
我有点没跟上
我: 等待了30秒......
为啥我感兴趣这俩呢
你的兴趣点对
我之前看过源码相关教程,了解一点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: history 和 react-router
嗯
我: 那一会须要关注的就是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>组件
这个时候
我: 嗯,你说
你会选择看react-router仍是history
我: 哈哈,这个时候,我其实想看一眼HashRouter
我也是
我: 由于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上了
我: 由于
恩啊
我: 回到这个路径
我: 去看react-router
为何是他
我: 由于它导入包时,没加相对路径啊
我: 说明这是一个已经发布的node包,导入时须要在node_modules路径下找
import { Router } from "react-router";
复制代码
我: 我就往上翻一翻呗,固然,估计在配置文件中,应该会有相关配置
恩恩
我: 进这个路径,文件真tmd多,mmp的
我是这个习惯,先看index是否是只作了import
我: 可是其实咱们在使用recat-router-dom的时候,网上会有一些与react-router的比较的讨论,
没太注意
稀里糊涂
我: 因此,react-router是一个已经发布的node包。可是,我并不肯定他的代码在哪,若是找不到,我可能会从github上其余的位置找,或者从npm的官网找连接了
恩啊
我: 进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以后的
这个时候就要看packge的script了
我: 嗯,能够的
我: 不过我感受略微跑偏了
我: 我要回到router自己上
好好
继续
怎么回到router自己
我: /react-router下,有一个router.js文件
我: 打开看,只有那两行代码,不是我要的东西啊
我: 它导出的,仍是index.js编译以后的
看modules
我: 对,看modules
我: 打开modules下的Router.js
要是个人话, 这个时候就跑偏了
直接去看rollup了
而后最后找到router
router.js
我: 我也可能会跑偏
我: 我以前就跑到history上去了
我: 可是后来想一想,这样不太好
我: 从看源码角度说,直接找到modules下的Router.js很容易
我: 由于其余文件,一看就不是源码实现
嗯啊
我: 如今打开它,一看,挺像啊,那先看看有多少行
我: 百十来行,有信心了,哈哈
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;
复制代码
而后这么少的代码
第一反应看一下引入
我: 对
我: 可是你看,一共五个
import React from "react";
import PropTypes from "prop-types";
import warning from "tiny-warning";
import RouterContext from "./RouterContext";
import warnAboutGettingProperty from "./utils/warnAboutGettingProperty";
复制代码
前三个忽略,一看就没用
我: 是的
我: 我如今其实有点关注第五个了
我会看render
我: 先不着急
我: 由于若是第五个的名字叫作warnXXXX
我: 是警告的意思
恩恩
搜一下
我: 警告一般都是开发版本的东西,若是能排除,那就剩第四个依赖了
可能没用
再一看,是在__DEV__里面的
我: 对,当前文件搜索了一下,在__DEV__分支下,不看了,哈哈
我: 那就剩一个context.js了呗
过度
我: 我以为我如今想扫一眼这个文件,若是内容很少,我就先搞他,若是多的话,那就先放那
恩恩
我: 那我去看一看吧,哈哈
我: 进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;
复制代码
我: 我次奥了
狗
我: 十行不到,我把他搞定,我就能够专一Router.js那个文件了。那个文件里面的内容,就是所有Router的核心了
我: 这里是标准context用法,店长推荐的,参见这个
我: 返回Router.js了哈
而后呢
看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的,每次调用返回一个新建立的对象,多余的不知道,先放着,日后看
嗯
我: 我先大概扫一眼组件都有哪些方法。另外发现,除了组件,还有其余代码
我: 除了组件内容,组件下面有一个判断,看起来应该是处理老版本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
我: 这俩,是初始化的地方
嗯嗯
我: 一个一个看
我: 重点是那个判断
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改变路径时,会统一监听,统一处理
嗯嗯
我: 那里面就调用了setState了呗,接着render就执行了
嗯
我: render很是简单,就是把context的value值,修改了一下
嗯啊
我: 咱们知道,只要context的value一变化,对应的consumer的函数,就会被调用,是吧
嗯嗯
我: 那如今Router就结束了
我: 接下来,咱们好奇的是,哪些组件使用了Consumer
找route
我: 对。根据React-router的使用,估计就是每一个<Route>,都会监听这个context,而后进行路径匹配,决定是否要渲染本身的component属性所指定的内容
我: 接下来,咱们就能够继续看这个组件了。先吃饭去吧,<Route>解读,且听下回分解。
嗯,好的。拜拜。