在 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>
)
}
}
複製代碼