「源码解析 」这一次彻底弄懂react-router路由原理(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: react-router 核心实现

写在前面:为什么要学习react-router底层源码? 为什么要弄明白整个路由流程? 笔者个人感觉学习react-router,有助于我们学习单页面应用(spa)路由跳转原理,让我们理解从history.push,到组件页面切换的全套流程,使我们在面试的时候不再为路由相关的问题发怵,废话不说,让我们开启深入react-router源码之旅吧。

一 正确理解react-router

1 理解单页面应用

什么是单页面应用?

个人理解,单页面应用是使用一个html下,一次性加载js, css等资源,所有页面都在一个容器页面下,页面切换实质是组件的切换。

2 react-router初探,揭露路由原理面纱

react-router-domreact-routerhistory库三者什么关系

history 可以理解为react-router的核心,也是整个路由原理的核心,里面集成了popState,history.pushState等底层路由实现的原理方法,接下来我们会一一解释。

react-router可以理解为是react-router-dom的核心,里面封装了Router,Route,Switch等核心组件,实现了从路由的改变到组件的更新的核心功能,在我们的项目中只要一次性引入react-router-dom就可以了。

react-router-dom,在react-router的核心基础上,添加了用于跳转的Link组件,和histoy模式下的BrowserRouter和hash模式下的HashRouter组件等。所谓BrowserRouter和HashRouter,也只不过用了history库中createBrowserHistory和createHashHistory方法

react-router-dom 我们不多说了,这里我们重点看一下react-router

②来个小demo尝尝鲜?

import {
   
    BrowserRouter as Router, Switch, Route, Redirect,Link } from 'react-router-dom'

import Detail from '../src/page/detail'
import List from '../src/page/list'
import Index from '../src/page/home/index'

const menusList = [
  {
   
   
    name: '首页',
    path: '/index'
  },
  {
   
   
    name: '列表',
    path: '/list'
  },
  {
   
   
    name: '详情',
    path: '/detail'
  },
]
const index = () => {
   
   
  return <div >
    <div >

      <Router  >
      <div>{
   
   
        /* link 路由跳转 */
         menusList.map(router=><Link key={
   
   router.path} to={
   
    router.path } >
           <span className="routerLink" >{
   
   router.name}</span>
         </Link>)
      }</div>
        <Switch>
          <Route path={
   
   '/index'} component={
   
   Index} ></Route>
          <Route path={
   
   '/list'} component={
   
   List} ></Route>
          <Route path={
   
   '/detail'} component={
   
   Detail} ></Route>
          {
   
   /*  路由不匹配,重定向到/index  */}
          <Redirect from='/*' to='/index' />
        </Switch>
      </Router>
    </div>
  </div>
}

效果如下

二 单页面实现核心原理

单页面应用路由实现原理是,切换url,监听url变化,从而渲染不同的页面组件。

主要的方式有history模式和hash模式。

1 history模式原理

①改变路由

history.pushState

history.pushState(state,title,path)

1 state:一个与指定网址相关的状态对象, popstate 事件触发时,该对象会传入回调函数。如果不需要可填 null。

2 title:新页面的标题,但是所有浏览器目前都忽略这个值,可填 null。

3 path:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个地址。

history.replaceState

history.replaceState(state,title,path)

参数和pushState一样,这个方法会修改当前的 history 对象记录, history.length 的长度不会改变。

②监听路由

popstate事件

window.addEventListener('popstate',function(e){
   
   
    /* 监听改变 */
})

同一个文档的 history 对象出现变化时,就会触发 popstate 事件

history.pushState 可以使浏览器地址改变,但是无需刷新页面。注意⚠️的是:用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()、history.forward()、history.go()方法。

2 hash模式原理

①改变路由

window.location.hash

通过window.location.hash 属性获取和设置 hash 值。

②监听路由

onhashchange

window.addEventListener('hashchange',function(e){
   
   
    /* 监听改变 */
})

三 理解history库

react-router路由离不开history库,history专注于记录路由history状态,以及path改变了,我们应该做写什么
在history模式下用popstate监听路由变化,在hash模式下用hashchange监听路由的变化。

接下来我们看 Browser模式下的createBrowserHistoryHash模式下的 createHashHistory方法。

1 createBrowserHistory

Browser模式下路由的运行 ,一切都从createBrowserHistory开始。这里我们参考的history-4.7.2版本,最新版本中api可能有些出入,但是原理都是一样的,在解析history过程中,我们重点关注setState ,push ,handlePopState,listen方法

const PopStateEvent = 'popstate'
const HashChangeEvent = 'hashchange'
/* 这里简化了createBrowserHistory,列出了几个核心api及其作用 */
function createBrowserHistory(){
   
   
    /* 全局history  */
    const globalHistory = window.history
    /* 处理路由转换,记录了listens信息。 */
    const transitionManager = createTransitionManager()
    /* 改变location对象,通知组件更新 */
    const setState = () => {
   
    /* ... */ }

    /* 处理当path改变后,处理popstate变化的回调函数 */
    const handlePopState = () => {
   
    /* ... */ }

    /* history.push方法,改变路由,通过全局对象history.pushState改变url, 通知router触发更新,替换组件 */
    const push=() => {
   
    /*...*/ }

    /* 底层应用事件监听器,监听popstate事件 */
    const listen=()=>{
   
    /*...*/ } 
    return {
   
   
       push,
       listen,
       /* .... */ 
    }
}

下面逐一分析各个api,和他们之前的相互作用

const PopStateEvent = 'popstate'
const HashChangeEvent = 'hashchange'

popstatehashchange是监听路由变化底层方法。

①setState

const setState = (nextState) => {
   
   
    /* 合并信息 */
    Object.assign(history, nextState)
    history.length = globalHistory.length
    /* 通知每一个listens 路由已经发生变化 */
    transitionManager.notifyListeners(
      history.location,
      history.action
    )
  }

代码很简单:统一每个transitionManager管理的listener路由状态已经更新。

什么时候绑定litener, 我们在接下来的React-Router代码中会介绍。

②listen

const listen = (listener) => {
   
   
    /* 添加listen */
    const unlisten = transitionManager.appendListener(listener)
    checkDOMListeners(1)

    return () => {
   
   
      checkDOMListeners(-1)
      unlisten()
    }
}

checkDOMListeners

const checkDOMListeners = (delta) => {
   
   
    listenerCount += delta
    if (listenerCount === 1) {
   
   
      addEventListener(window, PopStateEvent, handlePopState)
      if (needsHashChangeListener)
        addEventListener(window, HashChangeEvent, handleHashChange)
    } else if (listenerCount === 0) {
   
   
      removeEventListener(window, PopStateEvent, handlePopState)
      if (needsHashChangeListener)
        removeEventListener(window, HashChangeEvent, handleHashChange)
    }
  }

listen本质通过checkDOMListeners的参数 1-1 来绑定/解绑 popstate 事件,当路由发生改变的时候,调用处理函数handlePopState**

接下来我们看看push方法。

③push

 const push = (path, state) => {
    
    
    const action = 'PUSH'
    /* 1 创建location对象 */
    const location = createLocation(path, state, createKey(), history.location)
    /* 确定是否能进行路由转换,还在确认的时候又开始了另一个转变 ,可能会造成异常 */
    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
    
    
      if (!ok)
        return
      const href = createHref(location)
      const {
    
     key, state } = location
      if (canUseHistory) {
    
    
        /* 改变 url */
        globalHistory.pushState({
    
     key, state }, null, href)
        if (forceRefresh) {
    
    
          window.location.href = href
        } else {
    
    
          /* 改变 react-router location对象, 创建更新环境 */
          setState({
    
     action, location })
        }
      } else {
    
    
        window.location.href = href
      }
    })
  }

push ( history.push ) 流程大致是 首先生成一个最新的location对象,然后通过window.history.pushState方法改变浏览器当前路由(即当前的path),最后通过setState方法通知React-Router更新,并传递当前的location对象,由于这次url变化的,是history.pushState产生的,并不会触发popState方法,所以需要手动setState,触发组件更新

④handlePopState

最后我们来看看当popState监听的函数,当path改变的时候会发生什么,

/* 我们简化一下handlePopState */
const handlePopState = (event)=>{
    
    
     /* 获取当前location对象 */
    const location = getDOMLocation(event.state)
    const action = 'POP'

    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, (ok) => {
    
    
        if (ok) {
    
    
          setState({
    
     action, location })
        } else {
    
    
          revertPop(location)
        }
    })
}

handlePopState 代码很简单 ,判断一下action类型为pop,然后 setState ,从新加载组件。

2 createHashHistory

hash 模式和 history API类似,我们重点讲一下 hash模式下,怎么监听路由,和push , replace方法是怎么改变改变路径的。

监听哈希路由变化

  const HashChangeEvent = 'hashchange'
  const checkDOMListeners = (delta) => {
    
    
    listenerCount += delta
    if (listenerCount === 1) {
    
    
      addEventListener(window, HashChangeEvent, handleHashChange)
    } else if (listenerCount === 0) {
    
    
      removeEventListener(window, HashChangeEvent, handleHashChange)
    }
  }

和之前所说的一样,就是用hashchange来监听hash路由的变化。

改变哈希路由


/* 对应 push 方法 */
const pushHashPath = (path) =>
  window.location.hash = path

/* 对应replace方法 */
const replaceHashPath = (path) => {
    
    
  const hashIndex = window.location.href.indexOf('#')

  window.location.replace(
    window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path
  )
}

hash模式下 ,history.push 底层是调用了window.location.href来改变路由。history.replace底层是掉用
window.location.replace改变路由。

总结

我们用一幅图来描述了一下history库整体流程。

相关文章
|
26天前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
63 17
|
14天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
14天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
14天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
1月前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
58 8
|
1月前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
15天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
JavaScript 前端开发
Vue与React编程式路由导航
Vue与React编程式路由导航
React-36:编程式路由导航
React-36:编程式路由导航
131 0
React-36:编程式路由导航
|
8月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
395 0

推荐镜像

更多