React-Router源码分析-History库

history源码


history 在 v5 之前使用单独的包, v6 之后再 router 包中单独实现。

history源码

Action


路由切换的动作类型,包含三种类型:

  • POP
  • REPLACE
  • PUSH

Action 枚举:

typescript 复制代码
export enum Action {
  Pop = "POP",
  Push = "PUSH",
  Replace = "REPLACE",
}

关于三种动作类型意思,可以进入Actions了解。

createLocation(current, to , state, key)


创建一个含有唯一值key的location对象。

当前方法是一个公共方法,在createBrowserHistory/createHashHistory/createMemoryHistory中都使用其创建location对象。

如果你需要更深层次了解,请进入createLocation

getUrlBasedHistory(getLocation, createHref, validateLocation, options)


createBrowserHistory/createHashHistory函数都基于getUrlBasedHistory,执行getUrlBasedHistory后,会返回一个history对象。

History库


history库文件暴露出createMemoryHistory、createBrowserHistory、createHashHistory三个方法,每种方法作用不一样。

  • createMemoryHistory:用于非 dom 环境,react-native 和测试环境

  • createBrowserHistory/createHashHistory:用于浏览器环境,createBrowserHistory对应于history路由模式,而createHashHistory应用于hash模式路由,两者方法的底层都是利用了HTML5 history API方法实现(即监听popstate事件及replaceState、pushState无刷新更改location URL)

createBrowserHistory/createHashHistory函数都基于getUrlBasedHistory ,提供不同的:

  • getLocation
  • createHref
  • validateLocation
  • options

属性实现不同的 history 对象

执行步骤

history对象属性和方法


  1. action

    当前location对象变化的动作类型,关于三种动作类型意思,可以进入Actions了解。

  2. location

    返回当前的location对象。

    底层:getLocation => createLocation()

    具体内容可访问createLocation

  3. listen(fn: Listener)

    在createBrowserHistory函数(此刻以此举例)中,会有一个listener变量来接收传入的监听回调函数fn。

    注意:一个history中,有且仅有一个活跃的listen监听函数,否则会抛出一个异常。

    如果你想要继续传入一个监听回调事件,你可以先执行history.listen(fn)的返回值(作用:清除监听事件),再传入fn。

    监听location变化的函数,传入一个回调函数fn,并将代表location变化的一个update对象传入回调函数中。

    内部逻辑:

    (1) 创建一个popstate监听事件,回调函数handlePop

    (2) 将listener = fn;

    (3)返回一个函数(作用:执行这个函数,可以取消当前的listener);

    源码:

    typescript 复制代码
    listen(fn: Listener) {
       if (listener) {
         throw new Error("A history only accepts one active listener");
       }
       window.addEventListener(PopStateEventType, handlePop);
       listener = fn;
    
       return () => {
         window.removeEventListener(PopStateEventType, handlePop);
         listener = null;
       };
    },

    Listener interface

    typescript 复制代码
     export interface Listener {
       (update: Update): void;
     }

    Update interface

    typescript 复制代码
     export interface Update {
       action: Action; // 动作类型
       location: Location; // 新的location对象
       delta: number | null; // 目的location对象(也可以理解为新的location对象)与之前的location,在history栈中之间的增量
      }
  • createHref(to)

    创建地址

    createBrowserHistory

    如果to是一个string,返回to,否则createPath(to),如果想了解createPath,请访问createPath

    内部调用: createBrowserHref(window, to)

    typescript 复制代码
    function createBrowserHref(window: Window, to: To) {
      return typeof to === "string" ? to : createPath(to);
    }

    createHashHistory

    如果to是一个string,返回to,否则createPath(to),如果想了解createPath,请访问createPath

    内部调用: createBrowserHref(window, to)

    typescript 复制代码
    function createHashHref(window: Window, to: To) {
      let base = window.document.querySelector("base");
      let href = "";
    
      if (base && base.getAttribute("href")) {
        let url = window.location.href;
        let hashIndex = url.indexOf("#");
        href = hashIndex === -1 ? url : url.slice(0, hashIndex);
      }
    
      return href + "#" + (typeof to === "string" ? to : createPath(to));
    }
  • go(n)

    指定跳转地址,调用和HTML5 history api的go方法一样, 如果想了解原生的history,请访问history

  • push

    添加一个新的历史记录

    typescript 复制代码
    function push(to: To, state?: any) {
      // 1. 更改动作类型action
      action = Action.Push;
      // 2. 创建一个新的location对象
      let location = createLocation(history.location, to, state);
      if (validateLocation) validateLocation(location, to);
      // 3. 当前索引idx
      index = getIndex() + 1;
      // 4. state状态对象{ idx: index,usr: state, key: 唯一的key值 }
      let historyState = getHistoryState(location, index);
      let url = history.createHref(location);
    
      // try...catch because iOS limits us to 100 pushState calls :/
      try {
        globalHistory.pushState(historyState, "", url);
      } catch (error) {
        if (error instanceof DOMException && error.name === "DataCloneError") {
          throw error;
        }
        window.location.assign(url);
      }
    
      if (v5Compat && listener) {
        // 执行location变化的监听回调事件listener--调用history.listen中传入的事件
        listener({ action, location: history.location, delta: 1 });
      }
    }
  • replace

    替换当前的历史记录

    typescript 复制代码
    function replace(to: To, state?: any) {
      // 1. 更改动作类型action
      action = Action.Replace;
      // 2. 创建location对象
      let location = createLocation(history.location, to, state);
      if (validateLocation) validateLocation(location, to);
      // 3. 返回state状态对象中的idx,否则返回null
      index = getIndex();
      // 生成一个新对象,包含usr、key、idx:
      let historyState = getHistoryState(location, index);
      // 创建新的URL path
      let url = history.createHref(location);
      // history API中的replaceState替换当前的历史记录
      globalHistory.replaceState(historyState, "", url);
    
      if (v5Compat && listener) {
        listener({ action, location: history.location, delta: 0 });
      }
    }

更多内容,欢迎访问https://www.wangyuegyq.top

相关推荐
恋猫de小郭7 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅14 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端