今天開始,咱們開始揭開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>解讀,且聽下回分解。
嗯,好的。拜拜。