前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Angular路由实现原理

Angular路由实现原理

原创
作者头像
4cos90
发布2023-05-31 23:17:09
7410
发布2023-05-31 23:17:09
举报
文章被收录于专栏:随想随想

路由实现原理基本上每个人都能说出一点。最近也是被问到了回答的不是很好,所以准备好好整理一下。

SPA路由实现基本原理

前端单页应用实现路由的方式有两种。一种是基于hash,一种是基于History API

基于hash

通过将一个URL path部分用 # (Hash符号) 拆分。浏览器将 # 后面的部分视作虚拟片段。

早期的前端路由实现是基于 location.hash来实现的。他有如下特性:

  • URL 中hash值的改变不会被触发页面的重载。
  • 页面发送请求时, hash 部分不会被发送。
  • hash 值的改变,会记录在浏览器的历史记录,可使用浏览器的“后退”,“前进”触发页面跳转。
  • 可以利用 hashchange 事件来监听 hash 的变化。

触发hash变化的方式

  • 通过a标签的 href 属性,用户点击后,URL 就会发生改变,进而触发 hashchange 事件
  • 直接对 location.hash 赋值,从而改变 URL, 触发hashchange 事件。

下面是一个简易实现。设定了一个路由数组,有一个方法locationHandler,根据hash,通过路由数组,找到对应页面的内容。

监听hashchange事件,当hash改变时触发。并且在页面打开时也同样触发一次。

代码语言:javascript
复制
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>spa route</title>
</head>

<body>
    <nav>
        <a href="#">Home</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
        <a href="#other">Other</a>
    </nav>
    <div id="content"></div>
</body>

<script>
    const routes = {
        404: {
            content: "404 Not Found",
            title: "404",
        },
        "/": {
            content: "Home Page",
            title: "Home",
        },
        "about": {
            content: "This is a route demo",
            title: "About Us",
        },
        "contact": {
            content: "This is a contact",
            title: "Contact Us",
        }
    };

    const locationHandler = async() => {
        var location = window.location.hash.replace("#", "");
        if (location.length == 0) {
            location = "/";
        }
        const route = routes[location] || routes["404"];
        const html = route.content;
        document.getElementById("content").innerHTML = html;
        document.title = route.title;
    };

    window.addEventListener("hashchange", locationHandler);
    locationHandler();
</script>

</html>

基于History API

普通的URL path (无 # 拆分) ,服务器需要拦截路径请求返回入口index.html文件。

代码语言:javascript
复制
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>spa route</title>
</head>

<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
        <a href="/other">Other</a>
    </nav>
    <div id="content"></div>
</body>

<script>
    const routes = {
        404: {
            content: "404 Not Found",
            title: "404",
        },
        "/": {
            content: "Home Page",
            title: "Home",
        },
        "/about": {
            content: "This is a route demo",
            title: "About Us",
        },
        "/contact": {
            content: "This is a contact",
            title: "Contact Us",
        }
    };


    const route = (event) => {
        event = event || window.event;
        event.preventDefault();
        window.history.pushState({}, "", event.target.href);
        locationHandler();
    };

    document.addEventListener("click", (e) => {
        const {
            target
        } = e;
        if (!target.matches("nav a")) {
            return;
        }
        e.preventDefault();
        route();
    });

    const locationHandler = async() => {
        var location = window.location.pathname.;
        if (location.length == 0) {
            location = "/";
        }
        const route = routes[location] || routes["404"];
        const html = route.content;
        document.getElementById("content").innerHTML = html;
        document.title = route.title;
    };
    window.onpopstate = locationHandler;
    window.route = route;
    locationHandler();
</script>

</html>

基础实现对比

对比两种实现,其实代码逻辑基本上是一致的

基于location.hash的实现比较简单,直接通过监听hashchange来改变页面内容。

基于History API 的实现,主要是利用了 h5 提供的 pushState, replaceState方法。去改变当前页面的 URL, 同时,利用点击事件 结合 window.onpopstate监听事件触发页面的更新渲染逻辑。

此外History API的实现服务器通常需要做一些配置。

因为由于单页应用路由的实现是前端实现的, 可以理解为是 “伪路由”, 路由的跳转逻辑都是前端代码完成的,这样就存在一个问题, 例如上面的实现中, http://127.0.0.1:5500/about 这个页面用户点击了页面刷新,就会找不到页面。 因为浏览器会向服务器 “http://127.0.0.1:5500/about” 这个地址发送 GET 请求, 希望请求到一个单独的 index.html 文件, 而实际上这个文件我们服务器上是不存在的。 我们需要将其处理为:

http://127.0.0.1:5500/ server 返回首页

http://127.0.0.1:5500/about server 返回首页, 然后前端路由跳转到 about 页

http://127.0.0.1:5500/contact server 返回首页, 然后前端路由跳转到 contact 页

为了做到这点,所以我们需要对服务器做一些转发处理。

总结

基于Hash

优势:

  1. 浏览器不会将 URL.path 中 # hash 后面的部分视作一个分页,因此默认的就不会触发页面的重载。
  2. 在前端定义带有 hash 的链接总是安全的,因为它不会触发页面的重载。
  3. 服务端不需要额外配置。
  4. 实现起来更加简单。

劣势:

  1. SEO 并不友好
  2. 用户体验不好

基于History API

优势:

  1. URL 看起来和普通的url 一样, 更加美观简洁。
  2. 在 SEO 方面, 普通 url 会有更多的优势。
  3. 现代框架通常默认支持该模式。

劣势:

  1. 客户端刷新时,会把 SPA 的路由误当作 资源请求链接,所以需要配置 web 服务器以处理这些 “路由形式的URL” 以统一放回入口 index.html 文件。
  2. 通常为了让服务器区分这些 “路由形式的URL”, 所以通常需要用一些前缀以区分和普通 请求的区别,如 /api/*
  3. 通过这种方式实现时,定义路由的时候需要特别注意, 因为不当的链接跳转可能会导致全页面重载。

Angular路由实现

已经了解了基本原理,那么Angular的路由又是怎么实现的呢。

我到github上下载了angular路由实现的源码。

https://github.com/angular/angular/tree/main/packages/router

我们直接在router目录下搜索路由跳转的方法navigate。

commands是命令数组,比较常见的用法是在里面填写要导航到的路由,extras里设置路由的参数,以及其他扩展属性,第一步是校验数组里的成员是否均合法。

不是null即是合法。

值得注意的是Navigation这个类里,触发方式有三种,imperative即通过router.navigate触发,popstate event即history api,hashchange就是hash改变。

下一步构建UrlTree,queryParams即路由参数,会根据路由方式选择是否和原路由的参数合并。

最终返回是一个构建完成的Url。通过构建的url和扩展参数开始导航。值得一提的是这个NgZone。之前做过一个前端获取ip的需求,封装的getUserIP方法入参是一个回调函数,我在回调函数里调用navigate调用失败,后面也是通过设置ngZone.run()来解决的,这下原理终于搞清楚了,原来是执行上下文的问题。

后面实际处理路由请求时,还会对路由进行合并,路由守卫校验,设置活动路由等操作。这些都是angular提供的进阶的路由能力。基本的路由功能的实现看起来还是非常简单清晰的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SPA路由实现基本原理
    • 基于hash
      • 触发hash变化的方式
    • 基于History API
      • 基础实现对比
        • 总结
          • 基于Hash
          • 基于History API
      • Angular路由实现
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档