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. 「状压DP」「暴力搜索」排列perm

    「状压DP」「暴力搜索」排列 题目描述: 题目描述 给一个数字串 s 和正整数 d, 统计 sss 有多少种不同的排列能被 d 整除(可以有前导 0).例如 123434 有 90 种排列能被 2 整 ...

  2. URL编码与二次encodeURI

    转自:http://foryougeljh.iteye.com/blog/1456706 一般来说,URL只能使用英文字母.阿拉伯数字和某些标点符号,不能使用其他文字和符号.比如,世界上有英文字母的网 ...

  3. mysql常用时间函数与类型转换

    一.用到的函数有: 1.时间格式化函数  DATE_FORMAT(date,format) 2.时间加减函数DATE_ADD(date,INTERVAL expr unit)DATE_SUB(date ...

  4. Mysql基础(十):MYSQL中使用事务的案例

    https://www.cnblogs.com/lsqbk/p/10145306.html 基本介绍 事务用于保证数据的一致性,它由一组相关的dml语句组成,该组的dml语句要么全部成功,要么全部失败 ...

  5. java 面向对象(四十三):反射(七)反射应用四:动态代理

    1.代理模式的原理:使用一个代理将对象包装起来, 然后用该代理对象取代原始对象.任何对原始对象的调用都要通过代理.代理对象决定是否以及何时将方法调用转到原始对象上. 2.静态代理2.1 举例:实现Ru ...

  6. java 面向对象(三十九):反射(三)了解ClassLoader

    1.类的加载过程----了解 2.类的加载器的作用 3.类的加载器的分类 4.Java类编译.运行的执行的流程 5.使用Classloader加载src目录下的配置文件 @Test public vo ...

  7. 数据可视化之 图表篇(四) 那些精美的Power BI可视化图表

    之前使用自定义图表,每次新打开一个新文件时,都需要重新添加,无法保存,在PowerBI 6月更新中,这个功能得到了很大改善,可以将自定义的图表固定在内置图表面板上了. 添加自定义图表后,右键>固 ...

  8. JVM系列-方法调用的原理

    JVM系列-方法调用的原理 最近重新看了一些JVM方面的笔记和资料,收获颇丰,尤其解决了长久以来心中关于JVM方法管理的一些疑问.下面介绍一下JVM中有关方法调用的知识. 目的 方法调用,目的是选择方 ...

  9. js&jsp规范问题

    1.js初始化问题    一般与数据库交互的需要进行初始化,固定控件一般不需要初始化.有些需要整体浏览器页面校准的可能需要初始化.    //初始化操作按钮        $(function(){  ...

  10. 小白从零开始阿里云部署react项目+node服务接口(三:部署到服务器)

    服务器 准备工具 依次安装即可 nginx 安装nginx https://www.runoob.com/linux/nginx-install-setup.html 配置全局nginx命令 http ...