vue-router路由教程⑴-路由原理

333 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第25天,点击查看活动详情

01、什么是前端路由?

前端路由的一个大背景就是当下流行的单页应用SPA,一些主流的前端框架,如vue、react、angular都属于SPA,那什么是SPA呢?

1.1、SPA

SPA(single-page application)单页面应用,就是浏览器只加载了一个URL地址,一个页面,应用的所有功能、交互都在这个页面内进行。而实现单页面应用的基础就是ajax,通过异步请求动态的切换页面内容、实现交互,页面整体没有刷新。这避免了页面URL跳转,用户体验也不会中断,就像原生应用一样,体验比较好。越来越多的系统在使用SPA,尤其是WebApp中使用广泛。

image.png

SPA单页应用对应的就是多页应用MPA,当然两者不是非此即彼的,主要基于业务需求,是可以共存的。

区别单页面应用(SPA)多页面应用(MPA)
页面组成一个主页,包含多个页面片段多个主页面
刷新方式局部刷新整页刷新
url模式hash哈希模式 、history历史模式history历史模式
SEO搜索引擎优化难实现,采用页面静态化方式优化容易实现
数据传递同一应用内,容易通过url、cookie、localStorage等传递,复杂
渲染性能首次加载资源多稍慢,切换快,体验良好切换加载资源,速度慢,用户体验差
转场动画容易实现好像实现不了
维护成本相对容易相对复杂

SPA的主要表现就是更新视图而不重新请求页面,要实现前端的页面的自主路由控制,而不会刷新页面,涉及两种主流的技术:hash模式、history模式,这算是前端路由的核心原理,简单了解一下吧!

1.2、#hash路由原理

hash( /hæʃ/ )是URL地址中#号后面的内容(包括#),原本的作用是用于HTML页面内部定位的描点,描点的变化不会导致页面重新加载。HTTP请求中也不会带#,所以刷新也不影响,这是浏览器端的本地行为。

  • 页面不刷新:hash的变化不会刷新页面,只会触发浏览器定位锚点,这是hash实现前端路由的基本原理。
  • 获取 hashwindow.location.hash
  • hash 变更事件window.hashchange监听hash变化。
  • 不同的hash会进入浏览器历史记录。

www.xxx.cn/#/about
www.xxx.cn/#/pro-info-…

所以,实现过程就比较简单了!

监测hash变化:通过hashchange事件监测hash变化 。

加载资源:根据hash值匹配不同资源进行加载、切换,在Vue中切换的其实就是不同的组件。

image

🌰hash-简易路由示例:codepen

<div id="app2">
    <ul id="nav" v-once>
        <li v-for="item in navs" v-if="item.title"><a v-bind:href="'#/'+item.url">{{item.title}}</a></li>
    </ul>
    <div id="main">
        <keep-alive>
            <component v-bind:is="currentComponent"></component>
        </keep-alive>
    </div>
</div>
<script>
    //components
    const NotFound = { template: '<p>404!Page not found</p>' };
    const Home = { template: '<p>首页<br>Home page</p>' };
    const Product = { template: '<p>产品页面<br>Product page<br><input></p>' };
    const About = { template: '<p>关于我们<br>About page</p>' };
    //导航路由数据
    function Route(title, url, name, component) {
        this.title = title; this.url = url; this.component = component; this.name = name;
    }
    let routes = [
        new Route("首页", "home", 'home', Home), new Route("商品", "protect", 'protect', Product),
        new Route("招聘", "hr", null, null), new Route("关于", "about", 'about', About),
        new Route(null, "not-found", 'not-found', NotFound)];
    let components = {};
    routes.forEach(item => { components[item.name] = item.component });
    //app
    let app2 = new Vue({
        el: "#app2",
        data: { currentRoute: window.location.hash, navs: Object.freeze(routes) },
        computed: {
            currentComponent: function () {
                const com = this.navs.filter(item => '#/' + item.url === this.currentRoute)[0];
                if (com && com.component) {
                    document.title = com.title;
                    return com.name;
                }
                return 'not-found';
            }
        },
        components: components,
        created: function () {
            window.addEventListener("hashchange", () => {
                this.currentRoute = window.location.hash;
            });
        }
    });
</script>

image.png

1.3、history路由原理

history 是历史对象,存放当前文档页面(或框架)的会话历史记录(不是浏览器的所有历史记录)。

history 属性/方法描述
length会话历史列表的记录数量
state表示历史堆栈顶部记录的状态值,可以是任意可序列化JavaScript对象,限制为2MB
pushState(stateObj, title[, url])向当前会话的历史堆栈中添加一条记录
replaceState(stateObj, title[, url])修改 history 对象的当前(栈顶)记录
back()返回到(历史列表中)上一个URL地址。
forward()前进,加载(历史列表中)下一个URL地址
go(number)加载指定相对当前网页索引位置的历史列表URL地址,go(-1)等同于back()

pushStatereplaceState 是HTML5在history上新增的API,用来新增、修改当前文档的历史记录,这两个API就是用来实现SPA单页应用前端路由的关键。他们的参数相同:(stateObj, title[, url])

  • state:一个关联历史会话记录的状态对象,主要作用是在触发 popstate事件时作为参数传递,不需要可以为null,通过history.state可以获取到当前会话的state
  • title:新页面的标题,大部分浏览器都没有管他,可以空着。
  • url:网址,可以相对、绝对地址,但不可跨域。这个url会更新到浏览器地址栏,但并不会加载该url地址,也不检查是否存在,页面也不会刷新!对,要的就是你不刷新。

基于这两个API的特性来实现前端路由。用 pushState还是 replaceState呢?两者作用一样的,唯一的不同就是pushState会产生历史记录,可用于前进、后退。

监测url地址变化

  • popstate 事件:当state变化时触发该事件,在事件中获取当前url地址。pushState、replaceState并不会触发popstate事件,前进、后退、跳转才会触发。
  • 点击事件:绑定导航按钮的click事件,pushState()更新url

加载资源:根据url值匹配不同资源进行加载、切换。

📢注意,页面第一次加载的时候,不会触发 popstate 事件。

history-简易路由示例:codepen

<div id="app3">
    <ul id="nav" v-once>
        <li v-for="item in navs" v-if="item.title">
            <a href="#" v-on:click.prevent="navClick(item)">{{item.title}}</a></li>
    </ul>
    <div id="main">
        <keep-alive>
            <component v-bind:is="currentComponent"></component>
        </keep-alive>
    </div>
</div>
<script>
    //components
    const NotFound = { template: '<p>404!Page not found</p>' };
    const Home = { template: '<p>首页<br>Home page</p>' };
    const Product = { template: '<p>产品页面<br>Product page<br><input></p>' };
    const About = { template: '<p>关于我们<br>About page</p>' };
    //导航路由数据
    function Route(title, url, name, component) {
        this.title = title; this.url = url; this.component = component; this.name = name;
    }
    let routes = [
        new Route("首页", "home", 'home', Home), new Route("商品", "protect", 'protect', Product),
        new Route("招聘", "hr", null, null), new Route("关于", "about", 'about', About),
        new Route(null, "not-found", 'not-found', NotFound)];
    let components = {};
    routes.forEach(item => { components[item.name] = item.component });

    //拦截history.pushState,触发一个事件。不拦截换其他方式也可以,比如点击事件里。
    history.pushState = (function (type) {
        let origin = history[type];  //用闭包来存储原来的方法
        return function () {
            let out = origin.apply(this, arguments);
            let event = new Event(type);   //触发一个自定义事件pushState
            event.arguments = arguments;
            window.dispatchEvent(event);
            return out;
        }
    })('pushState');
    //app
    let app3 = new Vue({
        el: "#app3",
        data: { currentRoute: history.state?.url, navs: Object.freeze(routes) },
        computed: {
            currentComponent: function () {
                const com = this.navs.filter(item => item.url === this.currentRoute)[0];
                if (com && com.component) {
                    document.title = com.title;
                    return com.name;
                }
                return 'not-found';
            }
        },
        components: components,
        methods: {
            navClick: function (route) {
                history.pushState({ url: route.url }, null, route.url);
            }
        },
        created: function () {
            window.addEventListener("popstate", () => {
                this.currentRoute = history.state?.url;  //也可以用location.pathname获取前端url
            });
            window.addEventListener("pushState", () => { //监听自定义事件pushState
                this.currentRoute = history.state?.url;
            });
        }
    })
</script>

📢 刷新页面时会重新加载当前(本地路由的)url地址,可能就404了,这就需要服务端支持,修改下nginx代理也是可以解决的。
history与hash的主要区别,就是不会出现一个#,看上去更加美观?好像也没啥区别吧!


©️版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀