学习笔记(十二)Vue基础及Vue-Router原理实现

最近工作太忙,课程落下了不少,好长时间没更新了,后续还得抓紧把进度赶上来

Vue基础

详见Vue官网文档介绍https://cn.vuejs.org/v2/guide/

Vue-Router原理实现

Vue-Router基本使用

  • 创建路由相关的组件

  • 使用Vue.use注册Vue-Router插件

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    
  • 创建VueRouter对象实例,并配置路由规则

    const router = new VueRouter({
      routes // 路由规则数组
    })
    
  • 在创建Vue实例时,配置router对象

    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app')
    
  • 使用<router-view/>进行占位,当路由匹配时,会使用相应的组件进行替换

    <template>
      <div id="app">
        <router-view/>
      </div>
    </template>
    
  • 使用<router-link>创建路由链接

动态路由

在路由规则配置的path中,使用冒号+名称占位,可以进行动态路由传参,例如path: '/detail/:id'

  • 在组件中获取动态路由传递参数的两种方式
    • 使用$route.params.id方式
      • 这种方式的缺点是对路由$route强依赖,这样组件无法通过非路由的方式进行使用
    • 使用props方式
      • 在路由规则中配置props: true选项,可以将动态路由参数通过props传递到组件中,这种方式组件不依赖于$route,更便于复用

嵌套路由

对于页面相同可复用的内容,可以提取到一个公共的组件中,并通过嵌套路由的方式,来替换独立部分的组件

  • 在配置路由规则时,配置公共部分的组件,例如layout,并在组件中提供<router-view/>占位
  • 将独立部分组件的路由规则,配置为children,path可以使用相对路径,也可以使用绝对路径

编程式导航

  • $router.push()
    • 可以接收路由字符串作为参数
    • 可以接收一个对象作为参数,需要指定路由的name,路由name在路由规则的name属性中进行配置,同时可以通过params属性传递参数
  • $router.replace()
    • 参数类似$router.push(),区别是不记录历史
  • $router.back()
    • 返回上一个路由
  • $router.go()
    • 可以接收负数,-1表示返回上一个路由,-2表示返回上上个路由,以此类推

Hash模式与History模式

这两种模式都是前端路由的实现方式,当路由发生变化时,不会向服务端发送请求

通过前端JS监视路由变化,并根据路由配置渲染不同的组件内容

  • Hash模式
    • URL中携带井号#,井号后面是路由地址,可以使用?携带URL参数
    • 实现原理
      • 基于锚点 + onhashchange事件
    • 缺点
      • 无法使用URL中的#来实现锚点
  • History模式
    • 与普通URL相同,但需要服务端配置支持(处理页面刷新向服务端请求资源导致丢失前端路由的问题)
    • 实现原理
      • 基于HTML5中的History API,即history.pushState()与history.replaceState()
      • 使用history.push()来改变路由时,会向服务端发送请求,而使用history.pushState()时,只会改变路由,并记录路由历史,但不会向服务端发起请求
      • 通过监听popstate事件,可以获取改变后的路由地址,并完成相应的渲染,history.pushState()与history.replaceState()不能触发popstate事件,history.back()、histroy.forward()、以及浏览器的前进、后退按钮可以触发popstate事件
    • 缺点:
    • 需要HTML5支持,即不兼容IE10以下版本

服务端配置处理History模式

  • Node.js - 以使用express搭建node server举例

    • 使用connect-history-api-fallback模块,并在express的app实例中通过use注册处理history的中间件

      const history = require('connect-history-api-fallback')
      const express = require('express')
      const app = express()
      app.use(history())
      
    • 处理history中间件的工作原理是,当客户端向server请求的路由资源不存在时,会将指定资源根路径的index页面返回,前台单页应用在加载index页面后,再通过history路由访问指定的页面模块

  • nginx

    • 在相应的location配置下,增加try_files $uri $uri/ /index.html
    • 当客户端向nginx请求路由资源时,将会依次尝试try_files中配置的文件路径,$uri代表路由指向的资源,如果$uri对应的资源不存在,则尝试将路由指向的内容作为一个目录$uri/进行访问,如果仍不能找到对应的资源,则访问根路径的index页面

VueRouter模拟实现

  • VueRouter的核心使用代码

    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    const routes = [] // 路由配置数组
    const router = new Router({
      // mode: 'history', // require service support
      routes
    })
    new Vue({
      el: '#app',
      router,
      render: h => h(App)
    })
    
  • 基于使用代码分析

    • Vue.use(Router) VueRouter是一个Vue插件,需要实现一个install方法
    • const router = new Router({ routes }) VueRouter是一个类,构造函数接收routes、mode等参数组成的对象作为配置选项
    • VueRouter的实例将传入Vue的构造函数,以供后续使用
  • VueRouter的类视图

    image-20201104183346495
    • VueRouter类包含的属性
      • options
        • 配置选项
      • data
        • 保存当前路由相关数据的响应式对象
      • routeMap
        • 维护路由与组件映射关系的map
    • VueRouter类包含的方法
      • Constructor(Options)
        • 构造函数,接收options配置项参数
      • install
        • 静态方法,用于Vue插件注册
      • init
        • 初始化,调用initEvent、createRouteMap、initComponents方法
      • initEvent
        • 注册popstate事件,监听浏览器路由变化
      • createRouteMap
        • 创建路由与组件映射
      • initComponents
        • 创建router-link与router-view组件
  • 简单模拟实现代码

    let _Vue = null
    
    export default class VueRouter {
        constructor (options) {
            this.options = options
            this.routerMap = {}
            this.data = _Vue.observable({
                current: '/'
            })
        }
        static install (Vue) {
            // 1. 判断插件是否被安装过
            if (VueRouter.install.installed) {
                return 
            }
            VueRouter.install.installed = true
    
            // 2. 将Vue构造函数记录到全局变量
            _Vue = Vue
    
            // 3. 把创建Vue实例时传入的router对象注入到Vue实例上
            // 通过混入mixin
    
            _Vue.mixin({
                beforeCreate() {
                    if (this.$options.router) {
                        _Vue.prototype.$router = this.$options.router
                        this.$options.router.init()
                    }
                }
            })
        }
        init () {
            this.createRouterMap()
            this.initComponents(_Vue)
            this.intiEvent()
        }
        intiEvent () {
            window.addEventListener('popstate', () => {
                this.data.current = window.location.pathname
            })
        }
        createRouterMap () {
            // 遍历所有路由规则options.routes
            // 将路由规则解析成键值对形式,存储到routerMap中
    
            this.options.routes.map(route => {
                this.routerMap[route.path] = route.component
            })
        }
        initComponents (Vue) {
            const _this = this
            Vue.component('router-link', {
                props: {
                    to: String
                },
                render(h) {
                    return h('a', {
                        attrs: {
                            href: `${this.to}`,
                        },
                        on: {
                            click: this.onClick
                        }
                    }, [this.$slots.default])
                },
                // template: '<a :href="to"><slot></slot></a>'
                methods: {
                    onClick (e) {
                        history.pushState({}, '', this.to)
                        _this.data.current = this.to
                        e.preventDefault()
                    }
                }
            })
            Vue.component('router-view', {            
                render(h) {
                    // 获取当前路由地址对应的组件
                    const component = _this.routerMap[_this.data.current]
                    return h(component)
                }
            })
        }
    }
    

Vue的构建版本 - 运行时版本与完整版

  • 运行时版
    • 不支持template模板,需要打包的时候提前编译
    • 需要通过手动实现render函数的方式代替
  • 完整版
    • 包含运行时和编译器,体积比运行时版大10K左右,程序运行的时候把模板转换成render函数
  • Vue Cli创建的项目,默认使用Vue运行时版本
    • 通过在项目根目录下创建vue.config.js配置文件,并指定runtimeCompiler选项为true,则可以切换使用完整版
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容