React-Router有兩種模式,這兩種模式都是依賴於window對象的方法實現路由跳轉html
window.onhashchange事件,點擊詳見MDNreact
### hashChange 實現hack
if(!window.HashChangeEvent)(function(){
var lastURL=document.URL;
window.addEventListener("hashchange",function(event){
Object.defineProperty(event,"oldURL",{enumerable:true,configurable:true,value:lastURL});
Object.defineProperty(event,"newURL",{enumerable:true,configurable:true,value:document.URL});
lastURL=document.URL;
});
}());
# 使用
window.addEventListener('hashChange', function(event) {
console.log('新URL', event.newURL);
console.log('老URL', event.oldURL)
})
複製代碼
經過history API完成連接的跳轉git
pushState() 須要三個參數: 一個狀態對象, 一個標題 (目前被忽略), 和 (可選的) 一個URL. 讓咱們來解釋下這三個參數詳細內容:github
狀態對象 — 狀態對象state是一個JavaScript對象,經過pushState () 建立新的歷史記錄條目。不管何時用戶導航到新的狀態,popstate事件就會被觸發,且該事件的state屬性包含該歷史記錄條目狀態對象的副本。正則表達式
標題 — Firefox 目前忽略這個參數,但將來可能會用到。在此處傳一個空字符串應該能夠安全的防範將來這個方法的更改。或者,你能夠爲跳轉的state傳遞一個短標題。數組
URL — 該參數定義了新的歷史URL記錄。注意,調用 pushState() 後瀏覽器並不會當即加載這個URL,但可能會在稍後某些狀況下加載這個URL,好比在用戶從新打開瀏覽器時。新URL沒必要須爲絕對路徑。若是新URL是相對路徑,那麼它將被做爲相對於當前URL處理。新URL必須與當前URL同源,不然 pushState() 會拋出一個異常。該參數是可選的,缺省爲當前URL。瀏覽器
let stateObj = {
foo: "bar"
}
history.pushState(stateObj,"擺設參數","/index.html")
複製代碼
技術小結:做爲頂層路由,此處主要定義了路由中所須要的狀態參數。其中路由間的跳轉採用window.history.pushState方法實現,做爲獨立使用的組件,這個例子也靈活使用了React的context傳參的高級使用,值得我在平時負責組件時借鑑使用。安全
import React from 'react';
import Context from './context';
let pushState = window.history.pushState;
window.history.pushState = (state, title, url) => {
pushState.call(window.history, state, title, url);
window.onpushstate.call(this, state, url)
}
export default class HashRouter extends React.Component {
state = {
location: {
pathname: window.loation.pathname,
state:null
}
}
componentDidMount() {
window.onpopstate = (event) => {
if(this.block) {
let confirm = window.confirm(this.block(this.state.location))
if(!confirm) return;
}
this.setState({
location: {
...this.state.location,
pathname: window.location.pathname,
state: event.state
}
})
}
window.onpushstate = (state, pathname) => {
this.setState({
location: {
...this.state.location,
pathname,
state
}
})
}
}
render() {
let that = this;
let value = {
location: that.state.location,
history: {
push(to) {
if(that.block) {
let confirm = window.confirm(that.block(typeof to === 'object'?to:{pathname:to}));
if(!confirm) return;
}
if(typeof to === 'object') {
let { pathname, state } = to;
window.history.pushState(state, '', pathname)
} else {
window.history.pushState(null, '', to)
}
},
block(message) {
that.block = message
}
}
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
複製代碼
與BrowserRouter實現類似bash
import React from 'react';
import Context from './context';
export default class HashRouter extends React.Component {
state: {
location: {pathname: window.location.hash.slice(1), state: null }
}
locationState = null;
componentDidMount() {
window.location.hash = window.location.hash || '/';
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1),
state: this.locationState
}
})
})
}
render() {
let that = this;
let value = {
location: that.state.location,
history: {
push(to) {
if(that.block) {
let confirm = window.confirm(that.block(typeof to === 'object'?to:{pathname:to}));
if(!confirm) return;
}
if(typeof to === 'object'){
let {pathname,state} = to;
that.locationState = state;
window.location.hash = pathname;
}else{
that.locationState = null;
window.location.hash = to;
}
},
block(message){
that.block = message;
}
}
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
複製代碼
做爲公共消費引用的對象,單獨提取context文件session
import React from 'react';
const context = React.createContext();
export default context;
複製代碼
import Route from 'react'
import RouterContext from './context'
import pathToRegexp from 'path-to-regexp'
export default class Route extends React.Component {
static contextType = RouterContext;
render() {
let {
path="/",
component: Component,
exact: false,
render,
children
} = this.props;
let paramNames = [];
# https://github.com/pillarjs/path-to-regexp
# 使用pathToRegexp正則庫來解析生成路徑參數
let regxp = pathToRegexp(path, paramNames,{end: exact});
let result = pathname.match(regxp);
let props = {
location: this.context.location,
history: this.context.history
}
# 若是路徑匹配
if(result) {
paramNames = paramNames.map(item => item.name);
let {url, ...values} = result;
let params = {};
for(let i = 0; i < paramNames.length; i++) {
params[paramNames[i]] = values[i]
}
props.match = {
path,
url,
isExact: url === pathname,
params
}
# 存在Component,render或者render狀況下,渲染參數
if(Component) {
return <Component {...props} />
} else if(render){
return render(props);
} else if(children) {
return children(props);
} else {
return null
}
} else {
if(children) {
return children(props)
} else {
return null
}
}
}
}
複製代碼
若是咱們直接使用Router對象,就會發現瀏覽器並不能精確匹配顯示所對應的路由組件,所以咱們須要在最外層包裹Switch
import React from 'react';
import pathToRegexp from 'path-to-regexp';
import RouterContext from './context';
export default class Switch extends React.Component {
static contextType = RouteContext;
render() {
# 解耦當前地址欄的路徑
let {pathname} = this.context.location;
# 統一子元素對象格式爲數組格式
let children = Array.isArray(this.props.children)? this.props.children:[this.props.children];
for(let i = 0; i< children.length; i++) {
let child = children[i];
let {
path = '/',
exact = false
} = child.props;
let paramNames = [];
# 生成正則表達式
let regexp = pathToRegexp(path, paramNames, {end:exact});
let result = pathname.match(regexp);
if(result) {
reutrn child;
}
}
return null
}
}
複製代碼
對a元素的封裝
import React from 'react';
import ReactRouter from './context';
export default class Link extends React.Component {
static contextType = RouterContext
render() {
return (
<a {...this.props} onClick={() => this.context.history.push(this.props.to)}>{this.props.children}</a>
)
}
}
複製代碼
重定向
import React from 'react';
import RouterCOntext from './context';
export default class Redirect extends React.Component {
static contextType = RouterContext;
render() {
this.context.history.push(this.props.to);
return null;
}
}
複製代碼
當咱們套用多層組件的時候,history參數是空的,這是須要這個高階組件傳參
import React from 'react';
import Route from './Route';
export default function(WrappedComponent) {
return props => <Route component={WrappedComponent} />
}
複製代碼
跳轉校驗,不過基本沒用
import React from 'react';
import RouterContext from './context';
export default class Prompt extends React.Component {
static contextType = RouterContext;
componentWillUnmount() {
this.context.history.block(null)
}
render() {
let history = this.context.history;
const {when,message} = this.props;
if(when) {
history.block(message)
}else {
history.block(null)
}
return null;
}
}
複製代碼