咱們使用React開發項目的時候,基本上是單頁面應用,也就離不開路由。路由看似神祕,當咱們簡單的模擬一下它的核心功能後,發現也就這麼回事兒。本文就詳細的介紹一下react-router-dom
的HashRouter
的核心實現邏輯。react
本文實現的功能主要包含:git
HashRouter
Route
Link
MenuLink
Switch
Redirect
先不說代碼是怎樣寫的,先上圖,讓你們看一下這個HashRouter
究竟是個什麼東東: github
HashRouter
是一個大的容器,它控制着他本身到底渲染成什麼樣子,那麼它是經過什麼控制的呢,看它的名字就能猜出來,那就是window.location.hash
。HashRouter
開始渲染的時候就會拿它本身身上的pathname
屬性跟它肚子裏的Route
的path
進行匹配,匹配上的話,就會渲染Route
的component
對應的組件。Link
是怎樣切換路由的呢,很簡單,就是經過this.props.history.push(path)
來改變HashRouter
中的pathname
屬性,進而驅動Route們
進行從新渲染,再次匹配咱們的路由,最終實現路由的切換。介紹了一下簡單的邏輯,接下來咱們就看一下具體是怎樣實現的吧,以下圖: react-router
HashRouter
是一個繼承了React.Component
的類,這個類上的state
包括location
,監聽着hash
的變化以驅動Route
組件的從新渲染,另外還有一個history
屬性,能夠切換頁面的路由。Route
、Link
、MenuLink
、Switch
、 Redirect
,其中Route
的是基礎是核心,MenuLink
和某些有特定邏輯的渲染都是在Route
的基礎上實現的。Route
組件上能夠接收三種變量,包括component
、render
、children
,其中render
、children
是都是函數,render
是根據特定的邏輯渲染元素,children
是用來渲染MenuLink
,這兩個函數都接收當前路由的props
,函數的返回值是要渲染的元素。Switch
實現的邏輯是,返回children
中跟hash
匹配到的第一個「孩子」。HashRouter
HashRouter
將window.loacation.hash
跟本身的state
掛鉤,經過改變本身的state
驅動頁面的從新渲染。dom
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class HashRouter extends Component {
constructor() {
super();
this.state = {
location: {
pathname: window.location.hash.slice(1) || '/', // 當前頁面的hash值
state: {} //保存的狀態
}
};
}
// 定義上下文的變量類型
static childContextTypes = {
location: PropTypes.object,
history: PropTypes.object
}
// 定義上下文的變量
getChildContext() {
return {
location: this.state.location,
history: {
push: (path) => { // 就是更新 window.hash值
if (typeof path === 'object') {
let {pathname, state} = path;
this.setState({
location: {
...this.state.location,
state // {from: '/profile'}
}
}, () => {
window.location.hash = pathname;
})
} else {
window.location.hash = path;
}
}
}
}
}
render() {
return this.props.children; // 渲染頁面元素
}
componentDidMount() {
window.location.hash = window.location.hash.slice(1) || '/';
// 監聽window的hash的變化,驅動頁面的從新刷新
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/'
}
});
})
}
}
複製代碼
Route
Route
的渲染核心邏輯就是將本身的path
和當前頁面的hash
進行匹配,匹配上了就渲染相應的元素,匹配不上就什麼都不渲染。函數
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp'
export default class Route extends Component {
// 定義上下文context的類型
static contextTypes = {
location: PropTypes.object,
history: PropTypes.object
}
render() {
// 解構傳入Route的props
let {path, component: Component, render, children} = this.props;
// 解構上下文的屬性
let {location, history} = this.context;
let props = {
location,
history
};
// 將傳入Route的path和當前的hash進行匹配
let keys = [];
let regexp = pathToRegexp(path, keys, {end: false});
keys = keys.map(key => key.name);
let result = location.pathname.match(regexp);
if (result) { // 匹配上了
let [url, ...values] = result;
props.match = {
path,
url,
params: keys.reduce((memo, key, index) => { // 獲取匹配到的參數
memo[key] = values[index];
return memo;
}, {})
};
if (Component) { // 普通的Route
return <Component {...props} />; } else if (render) { // 特定邏輯的渲染 return render(props); } else if (children) { // MenuLink的渲染 return children(props); } else { return null; } } else { // 沒有匹配上 if (children) { // MenuLink的渲染 return children(props); } else { return null; } } } } 複製代碼
Redirect
Redirect
就幹了一件事,就是改變HashRouter
的state
,驅動從新渲染。ui
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class Redirect extends Component {
// 定義上下文context的Type
static contextTypes = {
history: PropTypes.object
}
componentDidMount() {
// 跳轉到目標路由
this.context.history.push(this.props.to);
}
render() {
return null;
}
}
複製代碼
MenuLink
import React, {Component} from 'react';
import Route from "./Route";
import Link from './Link'
export default ({to, children}) => {
// 若是匹配到了,就給當前組件一個激活狀態的className
return <Route path={to} children={props => ( <li className={props.match ? "active" : ""}> <Link to={to}>{children}</Link> </li> ) }/> } 複製代碼
Link
Link
就是渲染成一個a標籤,而後給一個點擊事件,點擊的時候更改HashRouter
的狀態,驅動從新渲染。this
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class Link extends Component {
static contextTypes = {
history: PropTypes.object
}
render() {
return (
<a onClick={() => this.context.history.push(this.props.to)}>{this.props.children}</a>
)
}
}
複製代碼
Switch
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component {
static contextTypes = {
location: PropTypes.object
}
render() {
let {pathname} = this.context.location;
let children = this.props.children;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
let path = child.props.path;
if (pathToRegexp(path, [], {end: false}).test(pathname)) {
// 將匹配到的第一個元素返回
return child;
}
}
return null
}
}
複製代碼
好了,這幾個功能介紹完了,你是否對HashRouter
的原理有所瞭解了呢?本文只是貼出部分代碼,若是有須要請看demo能夠手動體驗一下哦。url
參考文獻:spa