react-router分析 - 一、history
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 使用它匹配到路由,最后正确地渲染对应的组件
源码解析
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的更多相关文章
- [转] React Router 使用教程
PS:react-route就是一个决定生成什么父子关系的组件,一般和layout结合起来,保证layout不行,内部的子html进行跳转 你会发现,它不是一个库,也不是一个框架,而是一个庞大的体系. ...
- React Router 使用教程
一.基本用法 React Router 安装命令如下. $ npm install -S react-router 使用时,路由器Router就是React的一个组件. import { Router ...
- React Router学习笔记(转自阮一峰老师博客)
React Router是一个路由库,通过管理URL来实现组件切换和状态转变. 1.安装和使用 $ npm install -S react-router 在使用时,作为React组件导入 impor ...
- React Router 4.x 开发,这些雷区我们都帮你踩过了
前言 在前端框架层出不穷的今天,React 以其虚拟 DOM .组件化开发思想等特性迅速占据了主流位置,成为前端开发工程师热衷的 Javascript 库.作为 React 体系中的重要组成部分:Re ...
- React Router 用法
React Router 用法 一.DEMO import React from "react"; import { HashRouter as Router, Route, Li ...
- React躬行记(13)——React Router
在网络工程中,路由能保证信息从源地址传输到正确地目的地址,避免在互联网中迷失方向.而前端应用中的路由,其功能与之类似,也是保证信息的准确性,只不过来源变成URL,目的地变成HTML页面. 在传统的前端 ...
- [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 ...
- [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 ...
- React Router基础使用
React是个技术栈,单单使用React很难构建复杂的Web应用程序,很多情况下我们需要引入其他相关的技术 React Router是React的路由库,保持相关页面部件与URL间的同步 下面就来简单 ...
- react router 4.0以上的路由应用
thead>tr>th{padding:8px;line-height:1.4285714;border-top:1px solid #ddd}.table>thead>tr& ...
随机推荐
- arm64-v8a 静态成员模板 undefined reference to
谷歌发布新包需要64位的so Application.mk 中 APP_ABI := armeabi armeabi-v7a x86 x86_64 arm64-v8a 添加了 arm64-v8a 和 ...
- java语法学习
// 单行注释 /* 多行注释 */ /** JavaDoc(Java文档)注释是这样的.可以用来描述类和类的属性. */ // 导入 java.util中的 ArrayList 类 import j ...
- UiAutomator源码学习(2)-- UiAutomationBridge
从上一章对UiDevice的学习,可以看出几乎所有的操作都离不开 UiAutomationBridge.重新看一下UIDevice的构造方法: private UiDevice(Instrumenta ...
- 数据可视化基础专题(十二):Matplotlib 基础(四)常用图表(二)气泡图、堆叠图、雷达图、饼图、
1 气泡图 气泡图和上面的散点图非常类似,只是点的大小不一样,而且是通过参数 s 来进行控制的,多的不说,还是看个示例: 例子一: import matplotlib.pyplot as plt im ...
- Python之网络编程 Socket编程
本节内容: Socket语法及相关 SocketServer实现多并发 Socket语法及相关 socket概念 socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实 ...
- web前端知识点(webpack篇)
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler).当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency gr ...
- C# - 设计- Struct与Class的选择
选择Struct的原则 该类型的实例较小且通常为短生存期,或者通常嵌入到其他对象中. 它以逻辑方式表示单个值,类似于基元类型( int .等 double ). 它的实例大小为16字节. 它是不可变的 ...
- Database Identifiers - SID
These options include your global database name and system identifier (SID). The SID is a unique ide ...
- Oracle RMAN 异机恢复一例
背景介绍:本例需求是将NBU备份的oracle数据库恢复到另一主机上. NBU环境配置.异机上的Oracle软件安装配置忽略,下面只介绍OracleDB恢复的过程. ----------------- ...
- 集训作业 洛谷P1010 幂次方
这个…… 这个题看上去有点难的样子. 仔细看看,感觉有点简单.啊,是递归啊,正经的看一看,好像是把一个数分成2的几次方的和. 然后余数和比他小的最大的2的次方数如果不是2的一次方或者2的0次方,就继续 ...