前言
此处介绍一下React-Router的核心原理。特别细致的标点符号的不予讨论。
学前小知识
React-Router
其实最核心的东西是Route
组件和由统一作者开发的History
库来建立的。接下来跟着镜头一起走进神秘的ßReact-Router
世界吧。
已经知道怎么使用的直接跳过,到下面的源码分析去┗|`O′|┛ 嗷~~
简单示例
一起建一个简单的示例吧。先用react
官网的create-react-app
脚手架弄个react
出来。
npx create-react-app my-app
cd my-app
npm start
npm install react-router-dom
在大家安装之余,我简答的介绍一下react-router
和react-router-dom
的区别。
react-router和react-router-dom的区别。
先看提供的API
import {
Switch, Route, Router } from 'react-router';
import {
Swtich, Route, BrowserRouter, HashHistory, Link } from 'react-router-dom';
React-router
提供了路由的核心api。如Router、Route、Switch等,但没有提供有关dom操作进行路由跳转的api;
React-router-dom
提供了BrowserRouter
、Route
、Link
等api,可以通过dom操作触发事件控制路由。
Link
组件,会渲染一个a标签;BrowserRouter
和HashRouter
组件,前者使用pushState
和popState
事件构建路由,后者使用hash
和 hashchange
事件构建路由。
react-router-dom
在react-router
的基础上扩展了可操作dom
的api
。
Swtich
和 Route
都是从react-router
中导入了相应的组件并重新导出,没做什么特殊处理。
react-router-dom
中package.json
依赖中存在对react-router
的依赖,故此,不需要npm
安装react-router
。
- 可直接 npm 安装 react-router-dom,使用其api。
简单修改
👌,大家🔥应该已经安装完了吧?接下来简单的修改一下脚手架里面的内容。主要是为了熟悉对手,知己知彼百战百胜。
修改一下src/app.js
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
} from "react-router-dom";
function Home() {
return (
<>
<h1>首页</h1>
<Link to="/login">登录</Link>
</>
)
}
function Login() {
return (
<>
<h1>登录页</h1>
<Link to="/">回首页</Link>
</>
);
}
function App() {
return (
<Router>
<Switch>
<Route path="/login" component={
Login}/>
<Route path="/" component={
Home}/>
</Switch>
</Router>
);
}
export default App;
这样就完成了一个最简单的示例了。
SPA的核心思想
- 监听
URL
的变化 - 改变某些
context
的值 - 获取对应的页面组件
render
新的页面
源码攻读
BrowserRouter
从前面的简单示例中我们,发现有一个最外层的伙计,叫BrowserRouter
。我们直接干到他的github源码去瞅瞅。
链接:https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/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__) {
BrowserRouter.propTypes = {
basename: PropTypes.string,
children: PropTypes.node,
forceRefresh: PropTypes.bool,
getUserConfirmation: PropTypes.func,
keyLength: PropTypes.number
};
BrowserRouter.prototype.componentDidMount = function() {
warning(
!this.props.history,
"<BrowserRouter> ignores the history prop. To use a custom history, " +
"use `import { Router }` instead of `import { BrowserRouter as Router }`."
);
};
}
export default BrowserRouter;
抛开一些七七八八的判断。重新看。
import React from "react";
import {
Router } from "react-router";
import {
createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
history = createHistory(this.props);
render() {
return <Router history={
this.history} children={
this.props.children} />;
}
}
写着几个大字:《瓜子二手车》他只是一个中间商,他在赚差价。(打钱!
BrowserRouter
是依赖于两个库:分别为history
和react-router
。ok,我们一探究竟。
react-router
源码链接:https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Router.js
import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";
这两个东西其实很简单,都是引用了一个叫做createContext
,目的也很简单,这里其实就是创建的普通context,只不过拥有特定的名称而已。源码如下。就几行。
// TODO: Replace with React.createContext once we can assume React 16+
import createContext from "mini-create-react-context";
const createNamedContext = name => {
const context = createContext();
context.displayName = name;
return context;
};
export default createNamedContext;
OK,重点是接下来,抛开context之后,我们需要关注的东西。我整理一下。
先看构造函数
构造函数
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.
// _isMounted 表示组件是否加载完成
this._isMounted = false;
// 组件未加载完毕,但是 location 发生的变化,暂存在 _pendingLocation 字段中
this._pendingLocation = null;
// 没有 staticContext 属性,表示是 HashRouter 或是 BrowserRouter
if (!props.staticContext) {
this.unlisten = props.history.listen(location => {
if (this._isMounted) {
// 组件加载完毕,将变化的 location 方法 state 中
this.setState({
location });
} else {
this._pendingLocation = location;
}
});
}
}
有两个值☞_isMounted
和_pendingLocation
分别是 是否挂载 待定
内部维护了一个location,默认值是由外面传入的history,我找到传入的地方。
其实就是之前看到的BrowserRouter
import React from "react"