最近工作太忙,课程落下了不少,好长时间没更新了,后续还得抓紧把进度赶上来
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页面
- 在相应的location配置下,增加
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的类视图
- VueRouter类包含的属性
- options
- 配置选项
- data
- 保存当前路由相关数据的响应式对象
- routeMap
- 维护路由与组件映射关系的map
- options
- VueRouter类包含的方法
- Constructor(Options)
- 构造函数,接收options配置项参数
- install
- 静态方法,用于Vue插件注册
- init
- 初始化,调用initEvent、createRouteMap、initComponents方法
- initEvent
- 注册popstate事件,监听浏览器路由变化
- createRouteMap
- 创建路由与组件映射
- initComponents
- 创建router-link与router-view组件
- Constructor(Options)
- VueRouter类包含的属性
-
简单模拟实现代码
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,则可以切换使用完整版