一、实现createBrowserHistory
createBrowserHistory()
是基于html5提供的history实现的,最终按照打印的结果作为返回对象。
1. 实现action、push、location
1-1. action
action
:表示路径变化的方式。有三个值:
'POP':go/goBack/goForwrd
'PUSH':pushState
'REPLACE':replaceState
复制代码
1-2. location
location
表示当前路径。
来自于当前地址栏的路径,因此我们可以通过window.location
可以获取到path
和state
。
1-3. push
push
方法有两个入参:pathname
路径、nextState
新状态。其作用是:添加一个条目并且移动指针到栈顶。首先我们要改变action
的值为PUSH
。
push方法传参的两种方式:
- url和state分开传 push(url, {...state})
- 作为一个整体对象传 push({ pathname, state: {...} })
因此在push方法内,我们需要判断入参如果是对象,就分别给pathname
和state
赋值,如果不是对象,直接把state
的值更新为入参nextState
的值。调用原生history的pushState
方法跳转路径。
最后把location
对象传给监听函数触发更新。
2. 实现路由的动态监听
创建listen
方法监听url变化,接收一个变化的回调。首先把这个回调push
到数组中保存。最终方法返回一个取消此监听函数函数的方法。return () => listeners = listeners.filter(l !== listener )
监听到这次变化后把这次的监听函数从数组中移除。
3. 实现路径跳转的方法
- go():借助history原生的go方法,跳几步根据参数决定
- goBack():借助history原生的go方法,往后跳一步即可。
- goForward():借助history原生的go方法,往后跳一步即可。
最后给popState
也添加上监听url变化触发页面刷新。
二、实现createHashHistory
思路与createHashHistory
大概一致,最主要的区别是,hash模式下没有原生的history对象。因此push方法我们就不能使用pushState方法。要自己手动实现。
1. 获取当前最新的url
首先把当前url赋值给window.location.hash
。然后通过hashChangeHandler
方法监听到url的变化。
2. 实现hashChangeHandler
取出当前路径名,给history
对象赋值:action
、location
,location
即我们取出的路径名。然后进行判断,如果action
为PUSH
,说明是push操作,我们就要往历史栈中推入当前条目。随后触发监听回调。
3. 实现路径跳转的方法
go方法操作路径时,action
的值为POP
。随后把当前索引+1,然后从历史栈中取出新的location
,再把新的location
中的pathname
属性赋值给window上的hash
。
那么goBack
就是go(1)、goForward
就是go(-1)
二、代码实现
1. src/history
1-1. history/index.js
export { default as createHashHistory } from "./createHashHistory";
export { default as createBrowserHistory } from "./createBrowserHistory";
复制代码
1-2. history/createBrowserHistory.js
function createBrowserHistory() {
const globalHistory = window.history;
let state;
let listeners = [];
/**
* 添加新的路由条目,并移动指针指向栈顶
* @param {*} pathname 路径
* @param {*} nextState 新状态
*/
function push(pathname, nextState) {
const action = "PUSH";
// 给puah传参的两种方式:
// push('/user', {id:1, name:'zs'})
// push({ pathname: '/user', state: {id:1, name:'zs'} })
if (typeof pathname === "object") {
state = pathname.state;
pathname = pathname.pathname;
} else {
state = nextState;
}
// 调用原生的history的pushState方法跳转到路径
globalHistory.pushState(state, null, pathname);
// 触发监听
let location = {
pathname,
state,
};
// notify(action, location);
notify({ action, location });
}
// 触发监听
function notify(newHistory) {
// history.action = action
// history.location = location ==>
Object.assign(history, newHistory);
listeners.forEach((listener) => listener(history.location)); // 保证history.location与location保持一致
}
// 监听函数
function listen(listener) {
listeners.push(listener);
// 监听方法返回取消此监听的方法
return () => (listeners = listeners.filter((l) => l !== listener));
}
window.addEventListener("popstate", () => {
let location = {
pathname: window.location.pathname,
state: window.location.state,
};
notify({ action: "POP", location });
});
function go(n) {
globalHistory.go(n);
}
function goBack() {
globalHistory.go(-1);
}
function goForward() {
globalHistory.go(1);
}
// history对象
const history = {
action: "POP", // 三个值 pushState-PUSH back、forward-POP、replaceState-PLACE
push, // 指向pushState
listen,
go,
goBack,
goForward,
location: {
pathname: window.location.pathname,
state: window.location.state,
},
};
return history;
}
export default createBrowserHistory;
复制代码
1-3. history/createHashHistory.js
/**
* hash不能使用 浏览器的history对象了
* @returns
*/
function createHashHistory() {
let stack = []; // 历史栈 存放路径
let index = -1; // 栈顶指针 默认是-1
let action = "POP"; // 动作
let state; // 最新的状态
let listeners = []; // 监听函数的数组
function listen(listener) {
listeners.push(listener);
return () => {
listeners = listeners.filter((item) => item != listener);
};
}
function go(n) {
action = "POP";
// 更改栈顶的指针
index += n;
// 取出指定索引对应的路径对象
let nextLocation = stack[index];
// 取出此location对应的状态
state = nextLocation.state;
// 修改hash值-->修改当前的路径
window.location.hash = nextLocation.pathname;
}
let hashChangeHandler = () => {
// 取出最新的hash值对应的路径 #/user
let pathname = window.location.hash.slice(1);
Object.assign(history, { action, location: { pathname, state } });
if (action === "PUSH") {
// 说明是调用push方法,需要往历史栈中添加新的条目
stack[++index] = history.location;
}
listeners.forEach((listener) => listener(history.location));
};
function push(pathname, nextState) {
action = "PUSH";
if (typeof pathname === "object") {
state = pathname.state;
pathname = pathname.pathname;
} else {
state = nextState;
}
window.location.hash = pathname;
}
//当hash发生变化的话,会执行回调
window.addEventListener("hashchange", hashChangeHandler);
function goBack() {
go(-1);
}
function goForward() {
go(1);
}
const history = {
action: "POP",
go,
goBack,
goForward,
push,
listen,
location: {},
location: { pathname: "/", state: undefined },
};
if (window.location.hash) {
//如果初始的情况下,如果hash是有值的
action = "PUSH";
hashChangeHandler();
} else {
window.location.hash = "/";
}
return history;
}
export default createHashHistory;
复制代码
2. src/react-router-dom
2-1. react-router-dom/BrowserRouter.js
import React, { Component } from "react";
import { Router } from "../react-router";
import { createBrowserHistory } from "../history";
export default class BrowserRouter extends Component {
history = createBrowserHistory(this.props);
render() {
return <Router history={this.history}>{this.props.children}</Router>;
}
}
复制代码
2-2. react-router-dom/HashRouter.js
import React, { Component } from "react";
import { Router } from "../react-router";
import { createHashHistory } from "../history";
export default class HashRouter extends Component {
history = createHashHistory();
render() {
return <Router history={this.history}>{this.props.children}</Router>;
}
}
复制代码