在 react-router-dom
中咱们经常使用到的方法有:HashRouter
,BrowserRouter
,Route
,Link
,Redirect
,Switch
。可是它内部是怎么实现的呢?react
HashRouter
中存放着 location
与 history
,Route
,Link
,Redirect
,Switch
的实现都依赖于它。HashRouter
是它们的根组件,存放着 context
的数据bash
import React from 'react';
const context = React.createContext();
export default context;
复制代码
把 history
和 location
对象放在 context
,子组件能够调用。 子组件调用push 方法,来改变 location.hash
,同时监听 hashchange
事件,完成相应的渲染。react-router
import React, {Component} from 'react';
import Context from './context';
export default class HashRouter extends Component {
// 定义一个初始化的state
state = {
location:{pathname:window.location.hash.slice(1)||'/'},
state:null
}
componentDidMount(){
// 核心就是监听 hashChange 事件
window.addEventListener('hashchange',()=>{
this.setState({
location:{
...this.state.location,
pathname:window.location.hash.slice(1), // #/a ---> /a
state:this.locationState
}
})
})
}
locationState = null;
render(){
let that = this;
let value = {
location:that.state.location,
history:{
push(to){// 定义一个history 对象,有一个push 方法用来跳转路径
if(typeof to === 'object'){
let {pathname,state} = to;
that.locationState = state;
window.location.hash = pathname;
}else{
that.locationState = null;
window.location.hash = to;
}
}
}
}
return (
// 这里的value 属性是专门给context 提供的,就是存放共享数据的
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
复制代码
import React, {Component} from 'react';
import Context from './context';
import reg from 'path-to-regexp';
export default class Route extends Component{
static contextType = Context;
render(){
let {pathname} = this.context.location;
let {path='/',component:Component,exact=false} = this.props;
let paramNames = [];
// 用正则对路由进行匹配
let regexp = reg(path,paramNames,{end:exact});
let result = pathname.match(regexp);
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
}
return (<Component {...props}/>);
}
return null;
}
}
复制代码
这里和 switch...case
的思想差很少,会和子组件进行匹配,一旦匹配到,就马上返回匹配到的组件app
import React, {Component} from 'react';
import Context from './context';
import PathToRegexp from 'path-to-regexp';
export default class Switch extends Component{
static contextType = Context;
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} = child.props;
let paramNames = [];
let regexp = PathToRegexp(path,paramNames,{end:exact});
let result = pathname.match(regexp);
if(result){
return child;
}
}
return null
}
}
复制代码
它的实现很简单,它都用在 Switch
中 ,至关于switch..case
语法中的default
。原理就是调用HashRouter
中 history
的 push
方法,来改变location
。dom
import React, {Component} from 'react';
import Context from './context';
export default class Redirect extends Component{
static contextType = Context
componentDidMount(){
this.context.history.push(this.props.to)
}
render(){
return null;
}
}
复制代码
Link
在react
中的做用是替代a
标签的,起到点击跳转的做用,它的原理和 Redirect
相似,都是是调用 HashRouter
中 history
的 push
方法。ide
import React, {Component} from 'react';
import Context from './context';
export default class Route extends Component{
static contextType = Context
render(){
return (
//这种也能够实现
// <a to={`#{this.props.to}`}>{this.props.children}</a>
<a {...this.props} onClick={()=>{this.context.history.push(this.props.to)}}>{this.props.children}</a>
)
}
}
复制代码
import React, {Component} from 'react';
import Route from "./Route";
import Link from './Link'
export default ({to, children}) => {
// 若是匹配到了,就给当前组件一个激活状态的className
return <Route path={to} children={props => (
<li className={props.match ? "active" : ""}>
<Link to={to}>{children}</Link>
</li>
)
}/>
}
复制代码
一个普通的组件也想拥有 Route
上的属性和方法,使用 withRouter
,它的本质是一个高阶函数。函数
import React, {Component} from 'react';
import Route from './Route';
export default function(WrappedComponent){
return ()=><Route component={WrappedComponent}/>
}
复制代码
使用ui
import React from 'react';
import {withRouter} from '../react-router-dom'
class NavHeader extends React.Component{
render(){
return (
<div className="navbar-heading">
// 未通过Route包装的 组件时没有 history 方法
<div onClick={()=>this.props.history.push('/')}>XX科技</div>
</div>
)
}
}
export default withRouter(NavHeader)
复制代码
它的实现是靠监听 popstate
和 pushstate
来完成的。this
import React, {Component} from 'react';
import Context from './context';
// let pushstate = window.history.pushState;
export default class BrowserRouter extends Component {
state = {
location:{pathname:window.location.pathname||'/'},
state:null
}
pushstate = window.history.pushState;
componentDidMount(){
// 由于原生的方法上没有onpushstate,因此须要改写 pushstate 方法
window.history.pushState = (state,title,url) => {
// 先调用原生的 方法
this.pushstate.call(window.history,state,title,url)
window.onpushstate.call(this,state,url)
}
window.onpopstate = (event) => {
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){// 定义一个history 对象,有一个push 方法用来跳转路径
if(typeof to === 'object'){
let {pathname,state} = to;
window.history.pushState(state,'',pathname)
}else{
window.history.pushState(null,'',to)
}
}
}
}
return (
// 这里的value 属性是专门给context 提供的,就是存放共享数据的
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
复制代码