React之路由篇

react-router-dom 中咱們經常使用到的方法有:HashRouterBrowserRouterRouteLinkRedirectSwitch。可是它內部是怎麼實現的呢?react

一、概述

HashRouter 中存放着 locationhistoryRouteLinkRedirectSwitch 的實現都依賴於它。HashRouter是它們的根組件,存放着 context的數據bash

  • context
import React from 'react';
const context = React.createContext();
export default context;
複製代碼

二、HashRouter 的實現

historylocation 對象放在 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>
    )
  }
}

複製代碼

三、Route的實現

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 的實現

這裏和 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
  }
}
複製代碼

五、Redirect 的實現

它的實現很簡單,它都用在 Switch 中 ,至關於switch..case 語法中的default。原理就是調用HashRouterhistorypush 方法,來改變locationdom

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 的實現

Linkreact中的做用是替代a 標籤的,起到點擊跳轉的做用,它的原理和 Redirect相似,都是是調用 HashRouterhistorypush 方法。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>
    )
  }
}
複製代碼

七、menuLink

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>
    )
    }/>
}

複製代碼

七、withRouter

一個普通的組件也想擁有 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)
複製代碼

八、BrowserRouter 的實現方法

它的實現是靠監聽 popstatepushstate 來完成的。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>
    )
  }
}
複製代碼

九、總結

相關文章
相關標籤/搜索