路由

路由是跟据不同的url地址展示不同的页面

后端路由

服务端解析url地址,发送数据,但是每一次切换都需要重新刷新页面,对用户体验不好

前端路由

用户在点击切换路由的时候不需要重新刷新页面,路由由前端维护,分为hash路由和history路由,带#号的为hash路由,hash路由兼容性比较好

history路由

H5新增了window.history.pushstate 和window.history.replaceState

hash路由

hashchange事件来监听url hash值的改变

react-router

架构

image.png

Route 拿到context 中的location 和history 给 component, 然后根据路径 path 和pathname 生出一个match 也传给component

实现

HashRouter

新建一个文件夹,react-router-dom, 在index.js 文件中导出 HashRouter,

export { default as HashRouter } from './HashRouter';
export { default as BrowserRouter } from './BrowserRouter'

复制代码

然后来写HashRouter.js 文件

实际上引用的还是Router组件,通过createHashHistory 创建hash的history对象,传给Router

// HashRouter.js
import { Router } from '../react-router'

import { createHashHistory } from 'history'

const HashRouter = (props) => {
  // 创建一个history对象,创建一个history对象,但是是使用hash实现
  let hashHistory = createHashHistory()
  return (
    <Router history={hashHistory}>
      {props.children}
    </Router>
  )

}

export default HashRouter
复制代码

新建一个文件夹,react-router

在Router传值给Route的时候,一共传了三个对象,history,location, match, 通过context的方法传递给下面的Route。在这里我们写一个RouterContext.js文件,用于所有组件都可以引用到这个context

// RouterContext.js
import React from 'react'

const RouterContext = React.createContext();

export default RouterContext;
复制代码

然后来实现Router

// Router.js

import React, {useState} from 'react'
import RouterContext from './RouterContext'

const Router = (props) => {

  const { history } = props; // 可能是hashHistory 可能是 browserHistory

  const [location, setLocation] = useState(history.location)

  // 监听历史对象中的路径变化,当路径发生变化后执行回调函数
  // 返回一个location 参数就是最新的对象
  
  history.listen(location => setLocation(location))
  

  // 里面有三个值
  const value = {
    history, // 这个里面的location和外面的是一样的
    // location 代表当前路径 url 地址
    // location: {pathname:'', state: undefined, search: '', hash:"", }
    // match: {
    //   isExact: false,
    //   path: '/',
    //   url:'/'
    // }
    location,
  }

  return (
    <RouterContext.Provider value = {value}>
      {props.children}
    </RouterContext.Provider>
  )
}

export default Router

复制代码

最后我们来实现Route进行渲染不同的组件

这里判断传给route的path路径和location.pathname(当前地址中的url地址)匹不匹配,匹配就渲染组件

// Route.js

import React, {useContext} from 'react'
import RouterContext from './RouterContext'

const Route = (props) => {
  const { history, location, match } = useContext(RouterContext);
  const { path, component: RouterComponent, exact } = props;
  
  let renderElement = null;
  
  // 将history和location传给组件
  let renderProps = {
    history, 
    location
  }

  if (path === location.pathname) {
    renderElement =  < RouterComponent {...renderProps} />
  } 

  return renderElement
}
export default Route;
复制代码

最后我们来实现 createHashHistory 这个方法, 用来创建出的 hash路由下面的 history 对象

在history文件夹下面新建一个createHashHistory.js 文件 用来实现history

// createHashHistory.js
const createHashHistory = () => {

  let action;
  let listenerList = [];
  let historyStack = [];
  let historyIndex = -1;
  let state;


  // 用于监听到location改变后修改location
  const listen = (handleLocation) => {
    // hadleLocation是一个改变location的函数
    listenerList.push(handleLocation)
    return () => {
      const index = listenerList.indexOf(handleLocation)
      listenerList.splice(index, 1);
    }

  }

  const handelHashChange = () => {
    // 当前的路径
    let pathname = window.location.hash.slice(1);
    // 将新的action和pathname赋值给histor.action
    Object.assign(history,{
      action,
      location: {pathname, state}
    })
    
    if (!action || action === 'PUSH') {
      historyStack[++historyIndex] = history.location
    } else if (action === 'REPLACE') {
      historyStack[historyIndex] = history.location
    }

    listenerList.forEach(handleLocation => handleLocation(history.location))
  }

  window.addEventListener('hashchange', handelHashChange)

  const push = (newPath, newState) => {
    action = 'PUSH';
    let pathname;
    if (typeof newPath === 'object') {
      state = newPath.state;
      pathname = newPath.pathname;
    } else {
      pathname = newPath;
      state = newState;
    }
    window.location.hash = pathname;
  }

  const replace = (newPath, nextState) => {
    action = 'REPLACE'
    let pathname;
    if (typeof newPath === 'object') {
      state = newPath.state;
      pathname = newPath.pathname
    } else {
      pathname = newPath;
      state = nextState
    }
    window.location.hash = pathname;
  } 

  const go = (n) => {
    action = 'POP'
    historyIndex += n;
    let newLocation = historyStack[historyIndex];
    state = newLocation.state;
    window.location.hash = newLocation.pathname;
  }

  const goBack = () => {
    go(-1)
  }

  const goForward = () => {
    go(1)
  }

  // 自己创建的一个对象,类似于window.history,用于hash路由,两者没有关系
  let history = {
    listen, //监听
    action,
    // 给location一个默认值
    location: {pathname:'/', state:undefined},
    go,
    goBack,
    goForward,
    push,
    replace,
  }


  // 当url输入的地址和当前地址是一样的时候,相当于刷新页面,这个时候是不会触发到hashchange事件的因为hash值没有变,所以需要判断一下,手动调用一下这个事件
  if (window.location.hash) {
    handelHashChange()
  } else {
    window.location.hash = "/"
  }

  // 返回自己创建的history对象
  return history

}

export default createHashHistory;

复制代码

现在我们已经简单实现了,来看一下效果吧

hash路由实现.gif

historyRouter

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享