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

相关推荐
GoppViper13 分钟前
uniapp中实现<text>文本内容点击可复制或拨打电话
前端·后端·前端框架·uni-app·前端开发
Sam902922 分钟前
【Webpack--007】处理其他资源--视频音频
前端·webpack·音视频
Code成立24 分钟前
HTML5精粹练习第1章博客
前端·html·博客·html5
架构师ZYL35 分钟前
node.js+Koa框架+MySQL实现注册登录
前端·javascript·数据库·mysql·node.js
一只小白菜~2 小时前
实现实时Web应用,使用AJAX轮询、WebSocket、还是SSE呢??
前端·javascript·websocket·sse·ajax轮询
晓翔仔2 小时前
CORS漏洞及其防御措施:保护Web应用免受攻击
前端·网络安全·渗透测试·cors·漏洞修复·应用安全
GISer_Jing3 小时前
【前后端】大文件切片上传
前端·spring boot
csdn_aspnet3 小时前
npm 安装 与 切换 淘宝镜像
前端·npm·node.js
GHUIJS3 小时前
【Echarts】vue3打开echarts的正确方式
前端·vue.js·echarts·数据可视化
Mr.mjw4 小时前
项目中使用简单的立体3D柱状图,不用引入外部组件纯css也能实现
前端·css·3d