react-router基于history库,它是一个管理js应用session会话历史的js库。它将不同环境(浏览器,node等)的变量统一成了一个简易的API来管理历史堆栈、导航、确认跳转、以及sessions间的持续状态。区别于浏览器的window.history,history是包含window.history的

来看看官方解释

The history library is a lightweight layer over browsers' built-in History and Location APIs. The goal is not to provide a full implementation of these APIs, but rather to make it easy for users to opt-in to different methods of navigation.

history库基于浏览器内置History和Location API,实现的加强版本

We provide 3 different methods for creating a history object, depending on the needs of your environment:

提供了三种API

createBrowserHistory is for use in modern web browsers that support the HTML5 history API (see cross-browser compatibility)

createBrowserHistory用于支持HTML5历史记录API的现代Web浏览器,不兼容老浏览器,需要服务器配置;用于操纵浏览器地址栏的变更;createBrowserHistory使用HTML5中的pushState和replaceState来防止在浏览时从服务器重新加载整个页面

createHashHistory is for use in situations where you want to store the location in the hash of the current URL to avoid sending it to the server when the page reloads

createHashHistory用于要在当前URL的哈希中存储位置以避免在页面重新加载时将其发送到服务器的情况

兼容老浏览器 IE8+ 都可以用;生产环境不推荐使用,如果服务器不配置,可用;用于操纵 hash 路径的变更

createMemoryHistory is used as a reference implementation and may also be used in non-DOM environments, like React Native or tests

Depending on the method you want to use to keep track of history, you'll import (or require, if you're using CommonJS) only one of these methods.

Memory history 不会在地址栏被操作或读取;服务器渲染用到,一般用于react-native;其实就是管理内存中的虚拟历史堆栈

应用

import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

// 获取当前location
const location = history.location; // 监听当前的地址变换
const unlisten = history.listen((location, action) => {
// location is an object like window.location
console.log(action, location.pathname, location.state);
}); // 将新入口放入历史URL堆栈
history.push('/home', { some: 'state' }); // 停止监听
unlisten();

history基于原生对象提供的api

  • push(location) 浏览器会添加新记录
  • replace(location) 当前页面不会保存到会话历史中(session History),这样,用户点击回退按钮时,将不会再跳转到该页面。
  • go(n)
  • goBack()
  • goForward()

Each history object has the following properties:

history.length - The number of entries in the history stack

history.location - The current location (see below)

history.action - The current navigation action (see below)

history = {
length: globalHistory.length,//返回当前session的history个数
action: "POP",//默认为pop
location: initialLocation,//Object
createHref,//接下来一系列方法
push,
replace,
go,
goBack,
goForward,
block,
listen
};

history和location

history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件

location docs from mdn

源码解析

createBrowserHistory

import warning from "warning";
import invariant from "invariant";
import { createLocation } from "./LocationUtils";
import {
addLeadingSlash,
stripTrailingSlash,
hasBasename,
stripBasename,
createPath
} from "./PathUtils";
import createTransitionManager from "./createTransitionManager";
import {
canUseDOM,
getConfirmation,
supportsHistory,
supportsPopStateOnHashChange,
isExtraneousPopstateEvent
} from "./DOMUtils"; const PopStateEvent = "popstate";
const HashChangeEvent = "hashchange"; const getHistoryState = () => {
try {
//一般控制台打印出的都是null
//经过overstackflow,发现webkit内核浏览器没有实现
//https://stackoverflow.com/questions/8439145/reading-window-history-state-object-in-webkit
//state必须由pushState或者replaceState产生,不然就是null
return window.history.state || {};
} catch (e) {
// IE 11 sometimes throws when accessing window.history.state
// See https://github.com/ReactTraining/history/pull/289
return {};
}
}; /**
* Creates a history object that uses the HTML5 history API including
* pushState, replaceState, and the popstate event.
*/
const createBrowserHistory = (props = {}) => {
invariant(canUseDOM, "Browser history needs a DOM"); const globalHistory = window.history;//这边拿到全局的history对象
const canUseHistory = supportsHistory();
const needsHashChangeListener = !supportsPopStateOnHashChange(); const {
forceRefresh = false,
getUserConfirmation = getConfirmation,
keyLength = 6
} = props;
//这边会传入一个基地址,一般传入的props为空,所以也就没有基地址
const basename = props.basename
? stripTrailingSlash(addLeadingSlash(props.basename))
: "";
//这个函数时获取封装之后的location
const getDOMLocation = historyState => {
const { key, state } = historyState || {};
//可以在控制台打印出window.location看一下
const { pathname, search, hash } = window.location;
//将域名后的部分拼接起来
let path = pathname + search + hash; warning(
!basename || hasBasename(path, basename),
"You are attempting to use a basename on a page whose URL path does not begin " +
'with the basename. Expected path "' +
path +
'" to begin with "' +
basename +
'".'
); if (basename) path = stripBasename(path, basename);
//看一下createLoaction,在下方
return createLocation(path, state, key);
}; const createKey = () =>
Math.random()
.toString(36)
.substr(2, keyLength); const transitionManager = createTransitionManager();
//这个地方更新了history,length,并添加上了监听器
const setState = nextState => {
Object.assign(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action);
}; const handlePopState = event => {
// Ignore extraneous popstate events in WebKit.
if (isExtraneousPopstateEvent(event)) return; handlePop(getDOMLocation(event.state));
}; const handleHashChange = () => {
handlePop(getDOMLocation(getHistoryState()));
}; let forceNextPop = false; const handlePop = location => {
if (forceNextPop) {
forceNextPop = false;
setState();
} else {
const action = "POP"; transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (ok) {
setState({ action, location });
} else {
revertPop(location);
}
}
);
}
}; const revertPop = fromLocation => {
const toLocation = history.location; // TODO: We could probably make this more reliable by
// keeping a list of keys we've seen in sessionStorage.
// Instead, we just default to 0 for keys we don't know. let toIndex = allKeys.indexOf(toLocation.key); if (toIndex === -1) toIndex = 0; let fromIndex = allKeys.indexOf(fromLocation.key); if (fromIndex === -1) fromIndex = 0; const delta = toIndex - fromIndex; if (delta) {
forceNextPop = true;
go(delta);
}
}; const initialLocation = getDOMLocation(getHistoryState());
let allKeys = [initialLocation.key]; // Public interface
//创建一个路由pathname
const createHref = location => basename + createPath(location);
//实现push方法,是类似于栈结构push进去一个新的路由
const push = (path, state) => {
warning(
!(
typeof path === "object" &&
path.state !== undefined &&
state !== undefined
),
"You should avoid providing a 2nd state argument to push when the 1st " +
"argument is a location-like object that already has state; it is ignored"
);
//这边将动作更换
const action = "PUSH";
//创建location对象,这个函数的解析在下面
const location = createLocation(path, state, createKey(), history.location);
//这边是更新路由前的确认操作,transition部分解析也在下面
transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (!ok) return; const href = createHref(location);
const { key, state } = location;
//可以使用就将路由推入
if (canUseHistory) {
//这个地方只是地址栏进行更新,但是浏览器不会加载页面
globalHistory.pushState({ key, state }, null, href);
//强制刷新选项
if (forceRefresh) {
window.location.href = href;
} else {
const prevIndex = allKeys.indexOf(history.location.key);
const nextKeys = allKeys.slice(
0,
prevIndex === -1 ? 0 : prevIndex + 1
); nextKeys.push(location.key);
allKeys = nextKeys;
//setState更新history对象
setState({ action, location });
}
} else {
warning(
state === undefined,
"Browser history cannot push state in browsers that do not support HTML5 history"
);
//不能用就直接刷新
window.location.href = href;
}
}
);
}; //replace操作,这是直接替换路由
const replace = (path, state) => {
warning(
!(
typeof path === "object" &&
path.state !== undefined &&
state !== undefined
),
"You should avoid providing a 2nd state argument to replace when the 1st " +
"argument is a location-like object that already has state; it is ignored"
); const action = "REPLACE";
const location = createLocation(path, state, createKey(), history.location); transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (!ok) return; const href = createHref(location);
const { key, state } = location; if (canUseHistory) {
globalHistory.replaceState({ key, state }, null, href); if (forceRefresh) {
window.location.replace(href);
} else {
const prevIndex = allKeys.indexOf(history.location.key); if (prevIndex !== -1) allKeys[prevIndex] = location.key; setState({ action, location });
}
} else {
warning(
state === undefined,
"Browser history cannot replace state in browsers that do not support HTML5 history"
); window.location.replace(href);
}
}
);
}; const go = n => {
globalHistory.go(n);
}; const goBack = () => go(-1); const goForward = () => go(1); let listenerCount = 0;
//这边是监听window.histoty对象上的几个事件
const checkDOMListeners = delta => {
listenerCount += delta; if (listenerCount === 1) {
window.addEventListener(PopStateEvent, handlePopState); if (needsHashChangeListener)
window.addEventListener(HashChangeEvent, handleHashChange);
} else if (listenerCount === 0) {
window.removeEventListener(PopStateEvent, handlePopState); if (needsHashChangeListener)
window.removeEventListener(HashChangeEvent, handleHashChange);
}
}; let isBlocked = false; const block = (prompt = false) => {
const unblock = transitionManager.setPrompt(prompt); if (!isBlocked) {
checkDOMListeners(1);
isBlocked = true;
} return () => {
if (isBlocked) {
isBlocked = false;
checkDOMListeners(-1);
} return unblock();
};
}; const listen = listener => {
const unlisten = transitionManager.appendListener(listener);
checkDOMListeners(1); return () => {
checkDOMListeners(-1);
unlisten();
};
}; //这边是最终导出的history对象
const history = {
length: globalHistory.length,//返回当前session的history个数
action: "POP",
location: initialLocation,//Object
createHref,//接下来一系列方法
push,
replace,
go,
goBack,
goForward,
block,
listen
}; return history;
}; export default createBrowserHistory;

createHashHistory

const HashChangeEvent = "hashchange";

const HashPathCoders = {
hashbang: {
encodePath: path =>
path.charAt(0) === "!" ? path : "!/" + stripLeadingSlash(path),
decodePath: path => (path.charAt(0) === "!" ? path.substr(1) : path)
},
noslash: {
encodePath: stripLeadingSlash,
decodePath: addLeadingSlash
},
slash: {
encodePath: addLeadingSlash,
decodePath: addLeadingSlash
}
}; const getHashPath = () => {
//这边给出了不用window.location.hash的原因是firefox会预解码
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
const href = window.location.href;
const hashIndex = href.indexOf("#");//找到#号出现的位置,并去掉
return hashIndex === -1 ? "" : href.substring(hashIndex + 1);
}; const pushHashPath = path => (window.location.hash = path); const replaceHashPath = path => {
const hashIndex = window.location.href.indexOf("#"); window.location.replace(
window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + "#" + path
);
}; const createHashHistory = (props = {}) => {
invariant(canUseDOM, "Hash history needs a DOM"); const globalHistory = window.history;
const canGoWithoutReload = supportsGoWithoutReloadUsingHash(); const { getUserConfirmation = getConfirmation, hashType = "slash" } = props;
const basename = props.basename
? stripTrailingSlash(addLeadingSlash(props.basename))
: ""; const { encodePath, decodePath } = HashPathCoders[hashType]; const getDOMLocation = () => {
//创建一个hash路由
let path = decodePath(getHashPath()); warning(
!basename || hasBasename(path, basename),
"You are attempting to use a basename on a page whose URL path does not begin " +
'with the basename. Expected path "' +
path +
'" to begin with "' +
basename +
'".'
); if (basename) path = stripBasename(path, basename);
//这个函数之前看到过的
return createLocation(path);
}; const transitionManager = createTransitionManager(); const setState = nextState => {
Object.assign(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action);
}; let forceNextPop = false;
let ignorePath = null; const handleHashChange = () => {
const path = getHashPath();
const encodedPath = encodePath(path); if (path !== encodedPath) {
// Ensure we always have a properly-encoded hash.
replaceHashPath(encodedPath);
} else {
const location = getDOMLocation();
const prevLocation = history.location; if (!forceNextPop && locationsAreEqual(prevLocation, location)) return; // A hashchange doesn't always == location change.
//hash变化不会总是等于地址变化 if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.
//如果我们在push/replace中setState就忽视 ignorePath = null; handlePop(location);
}
}; const handlePop = location => {
if (forceNextPop) {
forceNextPop = false;
setState();
} else {
const action = "POP"; transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (ok) {
setState({ action, location });
} else {
revertPop(location);
}
}
);
}
}; const revertPop = fromLocation => {
const toLocation = history.location; // TODO: We could probably make this more reliable by
// keeping a list of paths we've seen in sessionStorage.
// Instead, we just default to 0 for paths we don't know.
//注释说可以用sessiongStorage使得路径列表更可靠 let toIndex = allPaths.lastIndexOf(createPath(toLocation)); if (toIndex === -1) toIndex = 0; let fromIndex = allPaths.lastIndexOf(createPath(fromLocation)); if (fromIndex === -1) fromIndex = 0; const delta = toIndex - fromIndex; if (delta) {
forceNextPop = true;
go(delta);
}
}; // Ensure the hash is encoded properly before doing anything else.
const path = getHashPath();
const encodedPath = encodePath(path); if (path !== encodedPath) replaceHashPath(encodedPath); const initialLocation = getDOMLocation();
let allPaths = [createPath(initialLocation)]; // Public interface
//hash路由
const createHref = location =>
"#" + encodePath(basename + createPath(location)); const push = (path, state) => {
warning(
state === undefined,
"Hash history cannot push state; it is ignored"
); const action = "PUSH";
const location = createLocation(
path,
undefined,
undefined,
history.location
); transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (!ok) return;
//获取当前路径并比较有没有发生变化
const path = createPath(location);
const encodedPath = encodePath(basename + path);
const hashChanged = getHashPath() !== encodedPath; if (hashChanged) {
// We cannot tell if a hashchange was caused by a PUSH, so we'd
// rather setState here and ignore the hashchange. The caveat here
// is that other hash histories in the page will consider it a POP.
ignorePath = path;
pushHashPath(encodedPath); const prevIndex = allPaths.lastIndexOf(createPath(history.location));
const nextPaths = allPaths.slice(
0,
prevIndex === -1 ? 0 : prevIndex + 1
); nextPaths.push(path);
allPaths = nextPaths; setState({ action, location });
} else {
warning(
false,
"Hash history cannot PUSH the same path; a new entry will not be added to the history stack"
); setState();
}
}
);
}; const replace = (path, state) => {
warning(
state === undefined,
"Hash history cannot replace state; it is ignored"
); const action = "REPLACE";
const location = createLocation(
path,
undefined,
undefined,
history.location
); transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
if (!ok) return; const path = createPath(location);
const encodedPath = encodePath(basename + path);
const hashChanged = getHashPath() !== encodedPath; if (hashChanged) {
// We cannot tell if a hashchange was caused by a REPLACE, so we'd
// rather setState here and ignore the hashchange. The caveat here
// is that other hash histories in the page will consider it a POP.
ignorePath = path;
replaceHashPath(encodedPath);
} const prevIndex = allPaths.indexOf(createPath(history.location)); if (prevIndex !== -1) allPaths[prevIndex] = path; setState({ action, location });
}
);
}; const go = n => {
warning(
canGoWithoutReload,
"Hash history go(n) causes a full page reload in this browser"
); globalHistory.go(n);
}; const goBack = () => go(-1); const goForward = () => go(1); const history = {
length: globalHistory.length,
action: "POP",
location: initialLocation,
createHref,
push,
replace,
go,
goBack,
goForward,
block,
listen
}; return history;
}; export default createHashHistory;

使用

import { Router } from 'react-router';
import createBrowserHistory from 'history/createBrowerHistory';//or createHashHistory
const history = createBrowserHistory();
<Router history={history}>
<App />
</Router>
import React from 'react'
import createBrowserHistory from 'history/lib/createBrowserHistory'
import { Router, Route, IndexRoute } from 'react-router'
import App from '../components/App'
import Home from '../components/Home'
import About from '../components/About'
import Features from '../components/Features' React.render(
<Router history={createBrowserHistory()}>
<Route path='/' component={App}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route path='features' component={Features} />
</Route>
</Router>,
document.getElementById('app')
)

react-router分析 - 一、history的更多相关文章

  1. [转] React Router 使用教程

    PS:react-route就是一个决定生成什么父子关系的组件,一般和layout结合起来,保证layout不行,内部的子html进行跳转 你会发现,它不是一个库,也不是一个框架,而是一个庞大的体系. ...

  2. React Router 使用教程

    一.基本用法 React Router 安装命令如下. $ npm install -S react-router 使用时,路由器Router就是React的一个组件. import { Router ...

  3. React Router学习笔记(转自阮一峰老师博客)

    React Router是一个路由库,通过管理URL来实现组件切换和状态转变. 1.安装和使用 $ npm install -S react-router 在使用时,作为React组件导入 impor ...

  4. React Router 4.x 开发,这些雷区我们都帮你踩过了

    前言 在前端框架层出不穷的今天,React 以其虚拟 DOM .组件化开发思想等特性迅速占据了主流位置,成为前端开发工程师热衷的 Javascript 库.作为 React 体系中的重要组成部分:Re ...

  5. React Router 用法

    React Router 用法 一.DEMO import React from "react"; import { HashRouter as Router, Route, Li ...

  6. React躬行记(13)——React Router

    在网络工程中,路由能保证信息从源地址传输到正确地目的地址,避免在互联网中迷失方向.而前端应用中的路由,其功能与之类似,也是保证信息的准确性,只不过来源变成URL,目的地变成HTML页面. 在传统的前端 ...

  7. [Redux] Navigating with React Router <Link>

    We will learn how to change the address bar using a component from React Router. In Root.js: We need ...

  8. [Redux] Adding React Router to the Project

    We will learn how to add React Router to a Redux project and make it render our root component. Inst ...

  9. React Router基础使用

    React是个技术栈,单单使用React很难构建复杂的Web应用程序,很多情况下我们需要引入其他相关的技术 React Router是React的路由库,保持相关页面部件与URL间的同步 下面就来简单 ...

  10. react router 4.0以上的路由应用

    thead>tr>th{padding:8px;line-height:1.4285714;border-top:1px solid #ddd}.table>thead>tr& ...

随机推荐

  1. POJ 3463 Sightseeing 题解

    题目 Tour operator Your Personal Holiday organises guided bus trips across the Benelux. Every day the ...

  2. 6.29模拟赛 (T1:李时珍的皮肤衣 T2:马大嘴的废话 T3:SSY的队列 T4:清理牛棚);

    啊,又是考炸的一天,成功的退步了三名,啊,成共的看错了T1  的题意 ,水了80分. 第十五名就是我,额,已经有点倒数的感觉了,并且一道题都没AC  我太难了. 好了,废话不多说了,下面正式提接: 这 ...

  3. [PA2015]Siano 单调栈

    由于某人找了个单调栈的题解但是没研究透所以让我们来研究............ 首先先来考虑下面一种情况,假设第\(k\)次切割时,天数为\(d_k\),高度为\(b_k\),第\(k+1\)次切割时 ...

  4. P2034 选择数字——线性dp(单调队列优化)

    选择数字 题目描述 给定一行 \(n\) 个非负整数 \(a[1]...a[n]\) .现在你可以选择其中若干个数,但不能有超过 \(k\) 个连续的数字被选择.你的任务是使得选出的数字的和最大. 输 ...

  5. Least Cost Bracket Sequence,题解

    题目链接 题意: 给你一个含有(,),?的序列,每个?变成(或)有一定的花费,问变成课匹配的括号的最小花费. 分析: 首先如果能变成匹配的,那么就有右括号的个数始终不多于左括号且左右括号数量相等,那就 ...

  6. IIFE中的函数是函数表达式,而不是函数声明

    下面的代码打印什么内容,为什么? var b = 10; (function b(){ b = 20; console.log(b); })(); 针对这题,在知乎上看到别人的回答说: 函数表达式与函 ...

  7. MyBatis框架基础详细开发流程

    MyBatis 项目已托管到GitHub,大家可以去GitHub查看下载!并搜索关注微信公众号 码出Offer 领取各种学习资料! 一.框架概述 1.1 什么是框架? 软件的半成品,解决了软件开发过程 ...

  8. 数据可视化之powerBI技巧(四)使用Power BI制作帕累托图

    各种复杂现象的背后,其实都是受关键的少数因素和普通的大多数因素所影响,把主要精力放在关键的少数因素上,就能达到事半功倍的效果. 这就是大家常说的二八原则,也称为帕累托原则,最早是由意大利经济学家 V. ...

  9. Quartz.Net系列(十二):六大Calendar(Annual、Cron、Daily、Holiday、Monthly、Weekly)

    Quartz.Net中为了动态排除一些时间,而使用Calendar可以做到 1.DailyCalendar 可以动态的排除一天中的某些时间段 示例:在一天当中的13:00到14:00不要执行 publ ...

  10. ETag简介与作用

    ETag简介与作用 一.ETag简介 ETag是URL的tag,用来标示URL对象是否改变.这样可以应用于客户端的缓存:服务器产生ETag,并在HTTP响应头中将其传送到客户端,服务器用它来判断页面是 ...