1 要實現的功能
我們使用React開發專案的時候,基本上是單頁面應用,也就離不開路由。路由看似神祕,當我們簡單的模擬一下它的核心功能後,發現也就這麼回事兒。本文就詳細的介紹一下react-router-dom
的HashRouter
的核心實現邏輯。
本文實現的功能主要包含:
HashRouter
Route
Link
MenuLink
Switch
Redirect
2 實現的邏輯
先不說程式碼是怎樣寫的,先上圖,讓大家看一下這個HashRouter
到底是個什麼東東:
HashRouter
是一個大的容器,它控制著他自己到底渲染成什麼樣子,那麼它是通過什麼控制的呢,看它的名字就能猜出來,那就是window.location.hash
。- 當
HashRouter
開始渲染的時候就會拿它自己身上的pathname
屬性跟它肚子裡的Route
的path
進行匹配,匹配上的話,就會渲染Route
的component
對應的元件。 Link
是怎樣切換路由的呢,很簡單,就是通過this.props.history.push(path)
來改變HashRouter
中的pathname
屬性,進而驅動Route們
進行重新渲染,再次匹配我們的路由,最終實現路由的切換。
介紹了一下簡單的邏輯,接下來我們就看一下具體是怎樣實現的吧,如下圖:
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
匹配到的第一個“孩子”。
3 具體的程式碼邏輯
(1) HashRouter
HashRouter
將window.loacation.hash
跟自己的state
掛鉤,通過改變自己的state
驅動頁面的重新渲染。
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) || '/'
}
});
})
}
}
複製程式碼
(2) 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;
}
}
}
}
複製程式碼
(3) Redirect
Redirect
就幹了一件事,就是改變HashRouter
的state
,驅動重新渲染。
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;
}
}
複製程式碼
(4) 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>
)
}/>
}
複製程式碼
(5) Link
Link
就是渲染成一個a標籤,然後給一個點選事件,點選的時候更改HashRouter
的狀態,驅動重新渲染。
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>
)
}
}
複製程式碼
(6) 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
}
}
複製程式碼
4 寫在最後
好了,這幾個功能介紹完了,你是否對HashRouter
的原理有所瞭解了呢?本文只是貼出部分程式碼,如果有需要請看demo可以手動體驗一下哦。
參考文獻: