灵感来自此博客

主要模拟了Router的实现原理

客供大家参考

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document - JoyNopRouter</title>
  </head>
  <body>
    <div id="root"></div>
    <button class="t1">JoyNop 2hahaha</button>
    <button class="t2">home</button>
    <button class="t3">about</button>
    <template id="home">
      <h1>home</h1>
    </template>
    <template id="about">
      <h1>about</h1>
    </template>
    <template id="joynop">
      <h1>joynop</h1>
    </template>
    <template id="notFound">
      <h1>404 not found</h1>
    </template>
    <!-- <script src="./aja-router.js"></script> -->
    <script type="module">
      import { JoyNopRouter } from "./joynop-router.js";
      const router = new JoyNopRouter();
      router.forRoot([
        {
          path: "",
          redirectTo: "home"
        },
        {
          path: "home",
          render(host) {
            const t = document.querySelector("#home");
            host.append(document.importNode(t.content, true));
          }
        },
        {
          path: "about",
          render(host) {
            const t = document.querySelector("#about");
            host.append(document.importNode(t.content, true));
          }
        },
        {
          path: "joynop/:id",
          render(host, route) {
            const t = document.querySelector("#joynop");
            const joynop = document.importNode(t.content, true);
            joynop.querySelector(
              "h1"
            ).innerHTML = `joynop, id is ${route.params.id.value}`;
            host.append(joynop);
          }
        },
        {
          path: "**",
          render(host) {
            const t = document.querySelector("#notFound");
            host.append(document.importNode(t.content, true));
          }
        }
      ]);

      document.querySelector(".t1").addEventListener("click", (e) => {
        router.push("joynop/2");
      });
      document.querySelector(".t2").addEventListener("click", (e) => {
        router.push("home");
      });
      document.querySelector(".t3").addEventListener("click", (e) => {
        router.push("about");
      });
    </script>
  </body>
</html>

joynop-router.js

const _textIsDynamicRouteExp = /\/?:[a-zA-Z]+/;
export class JoyNopRouter {
  _host = document.querySelector("#root");
  _routes = [];

  constructor(host) {
    if (host) this._host = host;
    this._setup();
  }

  _setup() {
    window.addEventListener("load", (e) => {
      this._render();
    });

    window.addEventListener("popstate", (e) => {
      this._render();
    });

    // window.addEventListener("hashchange", e => {
    //   console.log("hash ");
    // });
  }

  forRoot(routes = []) {
    this._routes = routes.map((route) => {
      const { path } = route;
      const pathSplit = path.split("/");
      console.log(pathSplit);

      if (path && path.match(_textIsDynamicRouteExp)) {
        route.isDynamic = true;
        // 动态路由
        const params = {};
        let exp = "";
        for (var i = 0; i < pathSplit.length; i++) {
          const item = pathSplit[i];
          let expItem = "/" + item;
          if (item.startsWith(":")) {
            params[item.replace(/^:/, "")] = { index: i };
            expItem = `/(?<${item.replace(/^:/, "")}>[^/]+)`;
          }
          exp += expItem;
        }
        if (exp && exp.trim() !== "") {
          exp = exp.replace(/\//, "");
        }
        route.exp = new RegExp(exp);
        route.params = params;
      }

      return route;
    });
  }

  _findHashRoute(path) {
    const hash = path ?? document.location.hash.replace(/#\/?/, "");
    return this._match(hash);
  }

  /**
   * 使用path在routes中寻找路由
   */
  _match(path) {
    // 1, 先找普通路由
    let route = this._routes.find((i) => i.path === path);
    if (route) {
      return route;
    }

    // 2, 找动态路由
    route = this._routes
      .filter((i) => i.isDynamic)
      .find((i) => {
        const pattern = "/";
        const routeNameSplit = path.split(pattern);
        const dynamicRouteNameSplit = i.path.split(pattern);
        const equalRouteLength =
          routeNameSplit.length === dynamicRouteNameSplit.length;
        const match = path.match(i.exp);
        if (match && match.groups) {
          for (const k in match.groups) {
            const param = i.params[k];
            param.value = match.groups[k];
          }
        }

        return equalRouteLength && match;
      });

    if (route) {
      return route;
    }

    // 3, 都没找到,默认返回404路由
    return this._find404Route();
  }

  _find404Route() {
    return this._routes.find((i) => i.path === "**");
  }

  _render(path) {
    const matchRoute = this._findHashRoute(path);
    if (matchRoute) {
      this._host.innerHTML = "";
      if (matchRoute.redirectTo) {
        this._render(matchRoute.redirectTo);
      } else {
        matchRoute.render(this._host, matchRoute);
      }
    }
  }

  push(path) {
    try {
      this._render(path);
      window.history.pushState({}, document.title, `#/${path}`);
    } catch (error) {}
  }
}

示例如下

https://codesandbox.io/s/vigilant-violet-55q4t