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;
}
}
复制代码