React router 原理与实现

1,046 阅读3分钟

React router 中的实现

react-router的基础——history.js

history是一个第三方js库,借鉴HTML5 history对象的理念,在其基础上又扩展了一些功能,用来管理历史记录,可以兼容不同的浏览器和不同的环境,根据不同的环境提供了三种不同的API。

HashHistory:针对老版本的浏览器,主要通过Hash实现。 BrowserHistory:针对较高版本的浏览器,主要通过H5的History实现 MemoryHistory:主要通过内存中的历史记录实现,主要用于Node环境。

History支持发布/订阅功能,当history发生改变的时候,可以自动触发订阅的函数。 history库可以说是对原生history对象的扩展,使功能更加丰富

// 内部的抽象实现
function createHistory(options={}) {
  ...
  return {
    listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
    listen, // location发生改变时触发回调
    transitionTo, // 执行location的改变
    push, // 改变location
    replace,
    go,
    goBack,
    goForward,
    createKey, // 创建location的key,用于唯一标示该location,是随机生成的
    createPath,
    createHref,
    createLocation, // 创建location
  }
}

重点在于createHistory的实现

image.png 设计思想:

  1. 对History进行了一次封装,使能够识别将url的变化与componet渲染进行匹配
  2. 根据BrowserRouter等不同的API针对H5的history的重构
  3. 结构的构建,同时对history属性进行注册。
  4. 在Router的componentWillMount中注册history的事件回调。
  5. 在Redirect中进行路径的计算,调用 history.push/history.replace 等更新history信息。
  6. Route中根据计算的匹配结果,进行页面首次渲染/页面更新渲染处理。

为了维护state的状态,可以将其存储在sessionStorage里面:

// createBrowserHistory/createHashHistory中state的存储
function saveState(key, state) {
  ...
  window.sessionStorage.setItem(createKey(key), JSON.stringify(state));
}
function readState(key) {
  ...
  json = window.sessionStorage.getItem(createKey(key));
  return JSON.parse(json);
}
// createMemoryHistory仅仅在内存中,所以操作比较简单
const storage = createStateStorage(entries); // storage = {entry.key: entry.state}

function saveState(key, state) {
  storage[key] = state
}
function readState(key) {
  return storage[key]
}

react-router-dom 组件结构

image.png

react-router-dom 提供了BrowserRouter、Route、Link等api,可以通过 dom 操作触发事件控制路由。

  • Link组件,会渲染一个a标签;BrowserRouter和HashRouter组件,前者使用pushState和popState事件构建路由,后者使用hash和 hashchange事件构建路由。
render() {
    const { replace, to, innerRef, ...props } = this.props; // eslint-disable-line no-unused-vars

    invariant(
      this.context.router,
      "You should not use <Link> outside a <Router>"
    );

    invariant(to !== undefined, 'You must specify the "to" property');

    const { history } = this.context.router;
    const location =
      typeof to === "string"
        ? createLocation(to, null, null, history.location)
        : to;

    const href = history.createHref(location);
    return (
      <a {...props} onClick={this.handleClick} href={href} ref={innerRef} />
    );
  }
  • react-router-dom在react-router的基础上扩展了可操作dom的api。
  • Swtich 和 Route 都是从react-router中导入了相应的组件并重新导出,没做什么特殊处理。 Route 实现
 render() {
    const { match } = this.state; // 布尔值,表示 location 是否匹配当前 Route 的 path
    const { children, component, render } = this.props; // Route 提供的三种可选的渲染方式
    const { history, route, staticContext } = this.context.router; // Router 传入的 context
    const location = this.props.location || route.location;
    const props = { match, location, history, staticContext };

    if (component) return match ? React.createElement(component, props) : null; // Component 创建

    if (render) return match ? render(props) : null; // render 创建

    if (typeof children === "function") return children(props); // 回调 children 创建

    if (children && !isEmptyChildren(children)) // 普通 children 创建
      return React.Children.only(children);

    return null;
  }

switch 实现

  render() {
    const { route } = this.context.router;
    const { children } = this.props;
    const location = this.props.location || route.location;

    let match, child;
    React.Children.forEach(children, element => {
      if (match == null && React.isValidElement(element)) {
        const {
          path: pathProp,
          exact,
          strict,
          sensitive,
          from
        } = element.props;
        const path = pathProp || from;

        child = element;
        match = matchPath(
          location.pathname,
          { path, exact, strict, sensitive },
          route.match
        );
      }
    });

    return match
      ? React.cloneElement(child, { location, computedMatch: match })
      : null;
  }

可直接 npm 安装 react-router-dom,使用其api。

参考链接:

react-router的实现原理