内容简介:今天开始,我们开始揭开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的源码吧
好呀!
我:首先,react-router的官网上,有基本的使用方法。这里 (中文点击这里) 列出了常用的组件,以及它们的用法
- Router (BrowserRouter, HashRouter)
- Route
- Switch
- Link
好的, 继续
我:先从这些组件的源码入手,那肯定第一个就是BrowserRouter,或者HashRouter
那应该怎么入手呢?
我:首先,从 github 上,得到与文档版本对应的代码。
我:接着看路径结构。是这样的:
接下来我一般就是找教程先简单过一遍,代码下下来然后把node__modules复制出来debugger 然后看不懂了就放弃
我:不,你进入细节之前,要先搞清楚代码的结构
恩啊, 不然怎么找代码
我:你看到这个路径之后,第一步,应该看一看,这些文件夹都是干啥的,哪个是你需要的
script是build, website是doc, packges是功能
这个都差不多
我:对。打开各个文件夹,会发现,packages里面的东西,是我们想要的源码。
我:我们肯定先从源码看起,因为这次读源码首先要学习的是 实现原理 ,并不是如何构建
我:那咱们就从 react-router-dom 开始呗
我:打开react-router-dom,奔着modules去
直接从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上了
我:因为
- history我猜出他是干啥了,跟浏览器路径有关
- router里面如果用到history了,我等到在读码时,遇到了阻碍,再去看history,这样行不行
恩啊
我:回到这个路径
我:去看react-router
为什么是他
我:因为它导入包时,没加相对路径啊
我:说明这是一个已经发布的node包,导入时需要在node_modules路径下找
import { Router } from "react-router";
复制代码
我:我就往上翻一翻呗,当然,估计在配置文件中,应该会有相关配置
恩恩
我:进这个路径,文件真tmd多,mmp的
导入的是一个包,包下有index.js,我是不是应该先看这个js
我是这个习惯,先看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>解读,且听下回分解。
嗯,好的。拜拜。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- ZooKeeper 源码和实践揭秘
- ZooKeeper 源码和实践揭秘
- 微软 VSCode IDE 源码分析揭秘
- 由"B站源码泄露事件"揭秘Go语言的前世今生
- React 技术揭秘
- Redis replication 揭秘
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Security Testing Cookbook
Paco Hope、Ben Walther / O'Reilly Media / 2008-10-24 / USD 39.99
Among the tests you perform on web applications, security testing is perhaps the most important, yet it's often the most neglected. The recipes in the Web Security Testing Cookbook demonstrate how dev......一起来看看 《Web Security Testing Cookbook》 这本书的介绍吧!
CSS 压缩/解压工具
在线压缩/解压 CSS 代码
RGB转16进制工具
RGB HEX 互转工具