likes
comments
collection
share

React Router V6.4 (history 对象篇)

作者站长头像
站长
· 阅读数 14

react router 层级

之前有过基础篇是涉及的底层api,React Router 的架构是针对 web 平台和移动端平台:

  • react-router-dom
  • react-native

层级

  • 底层 api 层:web api 层级
  • history 层(是建立在 location web api 和 history web api) 通用封装
  • router 层:封装了创建 router 的方法和 router 对象
  • react-router 层:正对 React 组件封装了一层,注意这里还不区别平台的
  • react-router-dom/react-router-native 层: 平台层(针对不同的平台封装)

history 对象详解

action/path/location

action 表示当前 history 的进行的动作:POP/PUSH/REPLACE 三种不同的 action 变体

export enum Action {
  Pop = "POP",
  Push = "PUSH",
  Replace = "REPLACE",
}

path 包含 pathname/search/hash

export interface Path {
  pathname: string;
  search: string;
  hash: string;
}

提供了创建和解析函数:createPath/parsePath

location 包含 path & state/key

export interface Location extends Path {
  state: any;
  key: string; // 使用 createKey 方法创建
}

提供了创建函数 createLocation

有了三个对象,就可以开始 history 封装之旅了,

History

  • 类型
export interface History {
  readonly action: Action;
  readonly location: Location;
  createHref(to: To): string;
  encodeLocation(to: To): Path;
  push(to: To, state?: any): void;
  replace(to: To, state?: any): void;
  go(delta: number): void;
  listen(listener: Listener): () => void;
}

我们看到 History 中包含了 Action/Location 这两对象,

  • craeteHref 创建一个完整的 url 地址
  • encodeLocation 正对 location 的解析
  • history 的跳转函数 push 取代 pushState, replace 取代 replaceState, 以及 go 函数跳转
  • listen 监听当前 location 对象的变化

高层 History 封装

histoy 中包含了三种 History 对象:

  • MemoryHistory 对应的创建函数 createMemoryHistory,用于非 dom 环境,react-native 和测试环境
  • BrowserHistory 对应的创建函数 createBrowserHistory, 用于现代浏览器由 html5 history api 提供基础
  • HashHistory 对应的创建函数 createHashHistory 用于旧款浏览器

MemoryHistory

MemoryHistory 实际开发过程中使用的会很少

比 History 对象多了索引

createBrowserHistory/createHashHistory 函数

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

  • getLocation
  • createHref
  • validateLocation
  • options

属性实现不同的 history 对象

function getUrlBasedHistory(
  getLocation: (window: Window, globalHistory: Window["history"]) => Location,
  createHref: (window: Window, to: To) => string,
  validateLocation: ((location: Location, to: To) => void) | null,
  options: UrlHistoryOptions = {}
){ /**/ }

createMemoryHistory

  • 创建 memoery 模式history
export function createMemoryHistory(
  options: MemoryHistoryOptions = {}
): MemoryHistory {
  let { initialEntries = ["/"], initialIndex, v5Compat = false } = options;
  let entries: Location[]; // Declare so we can access from createMemoryLocation
  entries = initialEntries.map((entry, index) =>
    createMemoryLocation(
      entry,
      typeof entry === "string" ? null : entry.state,
      index === 0 ? "default" : undefined
    )
  );
  let index = clampIndex(
    initialIndex == null ? entries.length - 1 : initialIndex
  );
  let action = Action.Pop;
  let listener: Listener | null = null;

  function clampIndex(n: number): number {
    return Math.min(Math.max(n, 0), entries.length - 1);
  }
  function getCurrentLocation(): Location {
    return entries[index];
  }
  function createMemoryLocation(
    to: To,
    state: any = null,
    key?: string
  ): Location {
    let location = createLocation(
      entries ? getCurrentLocation().pathname : "/",
      to,
      state,
      key
    );
    warning(
      location.pathname.charAt(0) === "/",
      `relative pathnames are not supported in memory history: ${JSON.stringify(
        to
      )}`
    );
    return location;
  }

  let history: MemoryHistory = {
    get index() {
      return index;
    },
    get action() {
      return action;
    },
    get location() {
      return getCurrentLocation();
    },
    createHref(to) {
      return typeof to === "string" ? to : createPath(to);
    },
    encodeLocation(to: To) {
      let path = typeof to === "string" ? parsePath(to) : to;
      return {
        pathname: path.pathname || "",
        search: path.search || "",
        hash: path.hash || "",
      };
    },
    push(to, state) {
      action = Action.Push;
      let nextLocation = createMemoryLocation(to, state);
      index += 1;
      entries.splice(index, entries.length, nextLocation);
      if (v5Compat && listener) {
        listener({ action, location: nextLocation });
      }
    },
    replace(to, state) {
      action = Action.Replace;
      let nextLocation = createMemoryLocation(to, state);
      entries[index] = nextLocation;
      if (v5Compat && listener) {
        listener({ action, location: nextLocation });
      }
    },
    go(delta) {
      action = Action.Pop;
      index = clampIndex(index + delta);
      if (listener) {
        listener({ action, location: getCurrentLocation() });
      }
    },
    listen(fn: Listener) {
      listener = fn;
      return () => {
        listener = null;
      };
    },
  };

  return history;
}

不同的 history 如何选择

  • memoryHistory 是内存模式,使用与 react-native 测试当环境
  • browserHistory 适用于现代浏览器
  • hashHistory 老浏览器支持 hash 模式

history 对象属性

action

  • 获取当前 action
get action() {
  return action;
}

location

  • 获取当前 location 对象
get location() {
  return getLocation(window, globalHistory);
}

listen

  • popstate 监听浏览器跳转行为(go/forword/back)
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;
  };
},

createHref

  • 创建地址
createHref(to) {
  return createHref(window, to); // hash 路由与browser路由不一致
},

encodeLocation

  • 以与 window.history 相同的方式对位置进行编码
encodeLocation(to) {
  // Encode a Location the same way window.location would
  let url = createClientSideURL(
    typeof to === "string" ? to : createPath(to)
  );
  return {
    pathname: url.pathname,
    search: url.search,
    hash: url.hash,
  };
},

go

  • 指定跳转地址, go 的封装
go(n) {
  return globalHistory.go(n); // let globalHistory = window.history;
},

push

  • 添加地址 pushState 的封住
function push(to: To, state?: any) {
    action = Action.Push;
    let location = createLocation(history.location, to, state);
    if (validateLocation) validateLocation(location, to);

    let historyState = getHistoryState(location);
    let url = history.createHref(location);

    // try...catch because iOS limits us to 100 pushState calls :/
    try {
      globalHistory.pushState(historyState, "", url);
    } catch (error) {
      // They are going to lose state here, but there is no real
      // way to warn them about it since the page will refresh...
      window.location.assign(url);
    }

    if (v5Compat && listener) {
      listener({ action, location: history.location });
    }
  }

replace

  • 替换当前 replaceState 的封装
function replace(to: To, state?: any) {
    action = Action.Replace;
    let location = createLocation(history.location, to, state);
    if (validateLocation) validateLocation(location, to);

    let historyState = getHistoryState(location);
    let url = history.createHref(location);
    globalHistory.replaceState(historyState, "", url);

    if (v5Compat && listener) {
      listener({ action, location: history.location });
    }
}

index()

  • 当前记忆 history 的索引
get index() {
  return index;
}

// 实现
let index = clampIndex(
    initialIndex == null ? entries.length - 1 : initialIndex
)

function clampIndex(n: number): number {
    return Math.min(Math.max(n, 0), entries.length - 1);
}

学习方法推荐

  • 使用测试工具测试 api, 官方自己的测试用例也写的可以
  • 实际在浏览器中进行调试,测试各种 api,巩固学习

小结

  • history 在 v5 之前使用单独的包, v6 之后再 router 包中单独实现。
  • history 主要由 location api (pathname/serach/hash) + history api(state/key) 构建新 History 对象的创建
  • 提供了三种不同的 history 的类型:browserHistory/hashHistory/memoryHistory,以及对应的创建函数。
  • createBrowserHistory 与 createHashHistory 区别主要在于: 创建 location 方式和创建 hashHref 的 方式有所不同

推荐文章

接下来

  • 由 history 对象,进入 router 对象,并 router 对象分析

help

作者正在参加投票活动,如果文章真的能帮助到您,不妨发财的小手点一点,投一票是对作者最大鼓励丫。

转载自:https://juejin.cn/post/7180075043412508728
评论
请登录