React
路由實現的兩種方式HashRouter
: 基於 window.onhashchange
實現BrowserRouter
:基於 HTML5 popstate event
實現Router
組件容器,經過建立上下文的方式,傳遞以下數據給後代組件使用;componentDidMount
/ useEffect
,訂閱 路由變化 的函數,修改相應的 路徑 匹配渲染對應的組件HashRouter
監聽的是 hashchange
事件BrowserRouter
監聽的是 pushstate
& popstate
事件注意:
HTML5
中並無pushstate
原生事件, 須要自行實現。html
下文中實現的方式均爲 Functional Component
函數式組件react
RouterContext.js
import { createContext } from 'react';
export default createContext();
複製代碼
HashRouter
實現當 一個窗口的 hash
(URL
中 #
後面的部分)改變時就會觸發 hashchange
事件瀏覽器
import React, { useEffect, useState } from 'react';
import RouterContext from './RouterContext';
export default function () {
const location = {
pathname: window.location.hash.slice(1) || '/',
state: null,
}
let locationState = null;
let [ initialLocation, setInitialLocation ] = useState(location);
useEffect(() => {
window.addEventListener('hashchange', () => {
setInitialLocation({
...initialLocation,
pathname: window.location.hash.slice(1) || '/',
state: locationState,
})
})
window.location.hash = window.location.hash || '#/';
// 賦值時加 #, 取值時無 #
});
const history = {
location: initialLocation,
push(to) {
if (history.prompt) {
const target = typeof to === 'string' ? { pathname: to } :to;
const yes = window.confirm(prompt(target));
if (!yes) return;
}
if (typeof to === 'object') {
const { pathname, state } = to;
locationState = state;
window.location.hash = pathname;
} else {
window.location.hash = to;
}
},
block(prmopt) {
history.prompt = prompt;
},
unblock() {
history.prompt = null;
},
}
const routerValue = {
history,
location: initialLocation
};
return (
<RouterContext value = { routerValue }></RouterContext>
)
}
複製代碼
BrowserRouter
實現調用 history.pushState()
或者 history.replaceState()
不會觸發 popstate
事件. popstate
事件只會在瀏覽器某些行爲下觸發, 好比點擊後退、前進按鈕(或者在JavaScript中調用 history.back()
、history.forward()
、history.go()
方法).app
import RouterContext from "./RouterContext"
import React, { useEffect, useState } from "react"
export default function (props) {
let location = {
pathname: window.location.pathname,
state: null,
}
let [ initialLocation, setInitialLocation ] = useState( location );
useEffect(() => {
window.onpushstate = (state, pathname) => {
setInitialLocation({
...initialLocation,
pathname,
state,
})
}
window.onpopstate = (event) => {
setInitialLocation({
...initialLocation,
pathname: window.location.pathname,
state: event.state,
})
}
});
const globalHistory = window.history;
let history = {
location: initialLocation,
push(to) {
if (history.prompt) {
let target = typeof to === 'string' ? { pathname: to } : to;
let yes = window.confirm(history.prompt(target));
if (!yes) return;
}
if (typeof to === 'object') {
let { pathname, state } = to;
globalHistory.pushState(state, null, pathname);
} else {
globalHistory.pushState(null, null, to);
}
},
block(prompt) {
history.prompt = prompt;
},
unblock() {
history.prompt = null;
}
}
const routerValue = {
history,
location: initialLocation,
};
return (
<RouterContext.Provider value={ routerValue }> { props.children } </RouterContext.Provider> ) } 複製代碼
pushState
方法方法體內部增長 onpushstate
觸發事件ide
index.html
<script>
!( history => {
const pushState = history.pushState;
history.pushState = function (state, title, url) {
if (typeof window.onpushstate === 'function') {
window.onpushState(state, url);
}
pushState.apply(history, arguments);
}
})(window.history)
</script>
複製代碼