JavaScriptの中のhistoryのソースコードのhashhistoryの実現を探求します.
9396 ワード
抜粋自:https://segmentfault.com/a/1190000012656017
history概要
通常は2種類のhistoryがあります.hashhistoryとbrowserHistoryです.ここではゼロからhashhistoryを実現します.
方案を実現する
1、createhashhistory関数を作成する
goメソッドの使用時には、history.go(-1)という形式があります.
これは一目で分かるはずです.
これも一目で分かるはずです.
historyオブジェクトのすべての属性と方法を一度に実現し、react-routerでは、historyオブジェクトをRouter、Routeなどのコンポーネントにカプセル化して、reactコンポーネントでthis.props.historyを通して読み取ることができます.ソースを見たら、historyの実現は本当に複雑ではないことが分かります.正確な考え方を探して、関数ごとに実現して、互換性を考えたら完璧です.他のブログで自分のルートプラグインを作ったと宣伝されました.
history概要
通常は2種類のhistoryがあります.hashhistoryとbrowserHistoryです.ここではゼロからhashhistoryを実現します.
hashHistory:'#/home'
browserHistory: '/home'
下の実装プログラムは、公式のhistoryソースコードに基づいて分析され、hashHistoryソースコードは、本論文の学習に結合されます.方案を実現する
1、createhashhistory関数を作成する
const createHashHistory = () => {
const history = {};
return history;
};
export default createHashHistory;
2、まずhistoryの対象の長さはどうですか?そして、私達は一つ一つそれを実現します.history = {
length: 1, // Number
action: "POP", // String
loaction: {}, // Object
createHref, //
push, //
replace, //
go, //
goBack, //
goForward, //
listen //
}
3、lengthを実現するには、windowの下にhistoryオブジェクトがあり、lengthを取得することができます.const globalHistory = window.history;
history = {
length: globalHistory.length
};
4、actionのデフォルトはPOPで、それはまたPUSHあるいはREPLACEかもしれません.私たちはこのステップでそれを実現しません.下でpushとreplaceを実現する時にまた実現します.5、location locationオブジェクトを実現するには以下のいくつかのkeyが含まれています.ここで使えるのはpathnameです.history.locationとwindow.locationは違います.history.locationはwindow.locationの簡潔版です.ブラウザコンソールでウインドウ.locationを印刷して、完全なlocationオブジェクトを見ることができます.location = {
hash: "",
pathname: "/",
search: "",
state: undefined
}
パッケージ後のlocationを取得するためのgetDOM Location関数を定義します.const decodePath = path =>
path.charAt(0) === "/" ? path : "/" + path
const getHashPath = () => {
// url #, #,
// :"http://localhost:8080/#/", '/'
const href = window.location.href;
const hashIndex = href.indexOf("#");
return hashIndex === -1 ? "" : href.substring(hashIndex + 1);
}
const getDOMLocation = () => {
// getHashPath url , #, #
let path = decodePath(getHashPath());
// location
return createLocation(path);
}
この一歩の核心はcreateLocationの実現です.しかし、複雑ではありません.コードが長いだけです.理解するには、ソースコードをご覧ください.import resolvePathname from "resolve-pathname"
import valueEqual from "value-equal"
import { parsePath } from "./PathUtils"
export const createLocation = (path, state, key, currentLocation) => {
let location
if (typeof path === "string") {
// Two-arg form: push(path, state)
location = parsePath(path)
location.state = state
} else {
// One-arg form: push(location)
location = { ...path }
if (location.pathname === undefined) location.pathname = ""
if (location.search) {
if (location.search.charAt(0) !== "?")
location.search = "?" + location.search
} else {
location.search = ""
}
if (location.hash) {
if (location.hash.charAt(0) !== "#") location.hash = "#" + location.hash
} else {
location.hash = ""
}
if (state !== undefined && location.state === undefined)
location.state = state
}
try {
location.pathname = decodeURI(location.pathname)
} catch (e) {
if (e instanceof URIError) {
throw new URIError(
'Pathname "' +
location.pathname +
'" could not be decoded. ' +
"This is likely caused by an invalid percent-encoding."
)
} else {
throw e
}
}
if (key) location.key = key
if (currentLocation) {
// Resolve incomplete/relative pathname relative to current location.
if (!location.pathname) {
location.pathname = currentLocation.pathname
} else if (location.pathname.charAt(0) !== "/") {
location.pathname = resolvePathname(
location.pathname,
currentLocation.pathname
)
}
} else {
// When there is no prior location and pathname is empty, set it to /
if (!location.pathname) {
location.pathname = "/"
}
}
return location
}
export const locationsAreEqual = (a, b) =>
a.pathname === b.pathname &&
a.search === b.search &&
a.hash === b.hash &&
a.key === b.key &&
valueEqual(a.state, b.state)
6、createHrefを実現するには、history.reate Href()を使ったことがないかもしれません.hashルートを作るために使われています.const createPath = location => {
const { pathname, search, hash } = location;
let path = pathname || "/";
if (search && search !== "?")
path += search.charAt(0) === "?" ? search : `?${search}`;
if (hash && hash !== "#") path += hash.charAt(0) === "#" ? hash : `#${hash}`;
return path;
}
const createHref = location =>
"#" + encodePath(createPath(location));
7、プッシュ方法を実現するために、私達はプッシュを使う時、通常はヒットマン・ピュー('/home')という形式で、自分で加えないでください.pushによって実現される原理:pushによってもたらされたルートは現在のurlのルートと同じかどうかを判断し、同じであれば、ルーティングを更新しない.そうでなければ、ルーティングを更新する.// history ,length、location action
const setState = nextState => {
Object.assign(history, nextState);
history.length = globalHistory.length;
transitionManager.notifyListeners(history.loation, history.action);
}
// notifyListeners history
const notifyListeners = (...args) => {
listeners.forEach(listener => listener(...args));
}
//
const pushHashPath = path => (windows.location.hash = path);
// push
const push = (path, state) => {
// action 'PUSH'
const action = "PUSH";
// location
const location = createLocation(
path,
undefined,
undefined,
history.location
);
// ,confirmTransitionTo , ok, , 。
transitionManager.confirmTransitionTo(
location,
action,
getUserConfirmation,
ok => {
// ,
if (!ok) return;
// location pathname, '/home'
const path = createPath(location);
const encodePath = encodePath(path);
// url push ,
// hashChanged true。
const hashChanged = getHashPath() !== encodePath;
if (hashChanged) {
//
ignorePath = path;
//
pushHashPath(encodePath);
const prevIndex = allPaths.lastIndexOf(createPath(history.location));
const nextPaths = allPaths.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
nextPaths.push(path);
allPaths = nextPaths;
// setState history 。
setState({ action, location });
} else {
// push ,
// "Hash history cannot PUSH the same path; a new entry will not be added to the history stack"
setState();
}
}
)
}
8、replace replaceとpushはいずれもルートを更新することができますが、replaceは現在のルートを更新するので、pushは歴史記録を追加します.//
const replaceHashPath = path => {
const hashIndex = window.location.href.indexOf("#");
window.location.replace(
window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + "#" + path;
);
}
// replace
const replace = (path, state) => {
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 encodePath = encodePath(path);
const hashChanged = getHashPath() !== encodePath;
// , push
if (hashChanged) {
ignorePath = path;
//
replaceHashPath(encodePath);
}
const prevIndex = allPaths.indexof(createPath(history.location));
if (prevIndex !== -1) allPaths[prevIndex] = path;
setState({ action, location });
}
)
}
9、goを実現するgoメソッドの使用時には、history.go(-1)という形式があります.
// globalHistory window.history
const go = n => globalHistory.go(n);
10、goBackを実現するこれは一目で分かるはずです.
const goBack = () => go(-1);
11、goForwardを実現するこれも一目で分かるはずです.
const goForward = () => go(1);
12、listenを実現するconst listen = listener => {
const unlisten = transitionManager.appendListener(listener);
checkDOMListeners(1);
return () => {
checkDOMListeners(-1);
unlisten();
}
}
// hashchange ,handleHashChange ,replace、push hash , 。
const checkDOMListeners = delta => {
listenerCount += delta;
if (listenerCount === 1) {
//
window.addEventListener('hashchange', handleHashChange);
} else if (listenerCount === 0) {
//
window.removeEvenListener('hashchange', handleHashChange);
}
}
// appendListener
ley listener = [];
const appendListener = fn => {
let isActive = true;
const listener = (...args) => {
if (isActive) fn(...args);
}
listeners.push(listener);
return () => {
isActive = false;
listeners = listeners.filter(item => item !== listener);
}
};
締め括りをつけるhistoryオブジェクトのすべての属性と方法を一度に実現し、react-routerでは、historyオブジェクトをRouter、Routeなどのコンポーネントにカプセル化して、reactコンポーネントでthis.props.historyを通して読み取ることができます.ソースを見たら、historyの実現は本当に複雑ではないことが分かります.正確な考え方を探して、関数ごとに実現して、互換性を考えたら完璧です.他のブログで自分のルートプラグインを作ったと宣伝されました.