1.Vue.js基础回顾
1.1Vue基础结构
<div id="app">
{{ message }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
//el选项
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
//$mount选项
<script>
new Vue({
data: {
message: 'Hello Vue!'
},
render(h){
return h('div',[
h('p',this.message)
])
}
}).$mount('#app')
</script>
- render函数接受一个参数, 这个参数是h函数,h函数的作用是创建虚拟dom, render函数是把h函数创建的虚拟dom返回。
- $mount方法的作用是把虚拟dom转换为真实dom,渲染到浏览器。
- 使用el选项和render选项在运行时有什么区别?
1.2Vue的生命周期
| 生命周期 | 发生的事情 |
|---|---|
| new Vue() | 初始化事件&生命周期 |
| beforeCreate() | 初始化注入&校验,会将props, data, methods等成员注入到vue实例上。 |
| created() | 可以访问到props,data,methods等成员,核心事情就是把模版编译成render函数 |
| beforeMount() | 无法获取新元素的内容,将新的结构渲染到页面 |
| mounted() | 可以访问到新的dom结构的内容 |
| beforeUpdate() | 当data被修改时, 新旧虚拟dom的对比,然后把差异渲染到浏览器中。访问不到新的内容 |
| updated() | 可以访问修改后的新的内容 |
| beforeDestroy() | 解除绑定销毁子组建以及事件监听器。 |
| destroyed() | 销毁完毕 |
Vue始终推荐提前编译模版,提高性能,不需要在运行期间编译模版。
1.3Vue语法和概念
- 差值表达式:通过{{}}将data中的成员显示在模版中的任何位置。如果内容中有html字符串, 差值表达式会将内容解析为普通文本, html内容被转译。如果想作为html输出, 可以使用v-html指令。
- 指令:内置14个指令,可以创建自定义指令。
- 计算属性和侦听器:计算属性的结果被缓存,从缓存中获取结果,提高性能。监听数据的变化作复杂操作,可使用侦听器。
- class和style绑定:分别可以绑定数组或者对象
- 条件渲染和列表渲染: v-if/v-show,v-for,用key跟踪每一个节点的身份,让每一项尽可能重用
- 表单输入绑定: v-model双向绑定
- 组件: 无限次被重用
- 插槽: 在自定义组件时挖坑, 在使用组件时填坑, 让组件更灵活
- 插件: vue-router, vueX
- 混入mixin
- 深入响应式原理
- 不同构件版本的vue
2. Vue Router实现原理
2.1 Vue Router 基础回顾
路由使用步骤及动态路由
- 路由组件:Blog.vue, Index.vue, Photo.vue
- 路由模块:index.js文件夹
import Vue from 'vue'
import Vue Router from 'vue-router'
import Index from '../views/index.vue'
// step1: 注册路由插件
// Vue.use用来注册插件,他会调用传入对象的install方法
Vue.use(VueRouter)
//路由规则
const routes = [
{
path:'/',
name:'Index',
component:Index
},{
path:'/blog',
name:'Blog',
component: () => import(/* webpackChunkName: "blog" */ '../views/index.vue')
},{
path:'/photo',
name:'Photo',
component:() => import(/* webpackChunkName: "photo" */ '../views/photo.vue')
},{
//动态路由, 在组件中有2中方式获取id,
// 1, $route.params.id,强依赖路由。
// 2, 当props的值设置为true时,会把url的参数传递给组件,只需要通过props接收就OK了, 同父子组件传值。
path:'/detail/:id',
name:'Detail',
props:true,
component:() => import(/* webpackChunkName: "detail" */ '../views/detail.vue') //路由懒加载, 访问路由地址时才会加载组件,提供性能。
},{
path:'*',
name:'404',
component:() => import('../views/404.vue')
}
]
// step2: 创建路由对象
const router = new VueRouter({
routes
})
export default router
- 文件入口:main.js
import Vue from 'vue'
import router from './router'
new Vue({
//step3: 生成vue实例时,注册router对象,
router,
render:h=>h(App)
}).$mount('#app')
打印vue实例对象:
//存贮路由规则
$route{
fullPath:'/blog',
hash:'',
matched:[{}],
meta:{},
name:'Blog',
params:{},
path:'/blog',
query:{}
}
//VueRouter实例, 路由对象
$router
相关属性:mode:'hash', currentRoute:[]
相关方法:go(), back(), push(), replace(), forward(), addRoutes(), beforeEach(), afterEach()等等
- App.vue文件
<template>
<div id="app">
//创建链接
<div id="nav">
<router-link to="/">Index</router-link>
<router-link to="/blog">Blog</router-link>
<router-link to="/photo">Photo</router-link>
</div>
//step4: 创建路由组建的占位,组件加载进来会替换掉router-view.
<router-view></router-view>
</div>
</template>
嵌套路由
- 当首页和详情页公用一个头部和尾部的时候 layout.vue
<template>
<div>
<div>header</div>
<router-view></router-view>
<div>footer</div>
</div>
</template>
router>index.js
cosnt route = [
//嵌套路由
{
path:'/',
component:Layout,
children:[
{
name:Index,
path:'',
component:Index
},{
name:Detail,
path:'detail/:id',
props:true,
component:() => import(/* webpackChunkName: "detail" */ '../views/detail.vue')
}
]
}
]
编程式导航
//参数可以是字符串和对象。
this.$router.push('/detail')
this.$router.push({name:'Detail',params:{id:'123'}})
this.$router.replace()
this.$router.go(-2)
this.$router.back()
2.2 hash和History模式
表现形式&原理的区别
形式:
hash模式
https://music.163.com/#/playList?id=3102961863
history模式
https://music.163.com/playList/id=3102961863
原理:
hash模式是基于锚点, 以及onhashchange事件
-- 通过 location.hash = 'foo' 这样的语法来改变,路径就会由 baidu.com 变更为 baidu.com/#foo。
-- 通过 window.addEventListener('hashchange') 这个事件,就可以监听到 hash 值的变化。
history是基于html5中的historyAPI
history.pushState():路径变化,向服务器发送请求
history.replaceState(): 不会发生服务器请求
history模式的使用, 需要服务器的支持
- 为什么刷新后会 404:本质上是因为刷新以后是带着 baidu.com/foo 这个页面去请求服务端资源的,但是服务端并没有对这个路径进行任何的映射处理,当然会返回 404
- 解决方法:处理方式是让服务端对于"不认识"的页面,返回 index.html,这样这个包含了前端路由相关js代码的首页,就会加载你的前端路由配置表,并且此时虽然服务端给你的文件是首页文件,但是你的 url 上是 baidu.com/foo,前端路由就会加载 /foo 这个路径相对应的视图,完美的解决了 404 问题.
2.3模拟实现自己的Vue Router
- Vue.use(VueRouter) // 注册插件,调用VueRouter的install静态方法
- 创建路由对象,传入一个对象, 对象里面包含路由规则
const router = new VueRouter({
routes:[
{path:'',name:'Index',component:homePage}
]
})
- 创建vue实例, 注册router对象
new Vue({
router,
render:h => h(App)
}).$mount('#app')
- VueRouter类图
2.3.1 install方法
let _Vue = null
export defualt class VueRouter{
static install(vue){
//1. 判断插件是否已经安装
if(VueRouter.install.installed){
return
}
VueRouter.install.installed = true //表示插件被安装了
//2. 把Vue构造函数记录到全局变量
_Vue = vue
//3. 把创建Vue实例的时候传入的router对象注入到Vue实例上
// 混入, 给所有的vue实例设置一个选项, 所有的组件会执行我们混入的beforecreat()
_Vue.mixin({
beforeCreate(){
// 直接拿到Vue实例,
// 组件不执行(不然会执行很多次), 实例的时候执行一次即可
// 组件时不会传入options, 实例的时候会传入options
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
}
2.3.2构造函数
constructor(options){
this.$options = options
this.routeMap = {} //键:路由地址, 值:路由地址对应的路由组件, 根据路由地址找到路由组建,渲染到页面上
//data里面是响应式的路由地址,这一点特别*重要*
this.data = _Vue.observable({
current:'/'
})
}
2.3.3 init方法
init(){
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
2.3.4 createRouteMap方法
- 作用: 遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap中
createRouteMap(){
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
2.3.5 initComponents方法
initComponents(Vue){
Vue.component('router-link', {
props:{
to:String
},
// template:'<a :href="to"><slot></slot></a>'
render(h){
return h('a', {
attrs:{
href: this.to
},
on:{
click:this.clickHandler
}
},[this.$slots.default])
},
methods:{
clickHandler(e){
history.pushState({},'网页标题',this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
Vue.component('router-view', {
render (h) {
// 拿到当前路由地址,注意this的指向
const component = self.routeMap[self.data.current]
//url变化, 浏览器会刷新, 不希望向服务器发送请求, history的pushState(),仅仅改变地址栏的地址, 不会发送请求
return h(component)
}
})
}
template:'<a :href="to"><slot></slot></a>'
此时运行会报错,原因如下:
- Vue的构建版本:(默认使用运行时版本)
- 运行时版:不支持template模版,需要打包的时候提前编译
- 完整版:包含运行时和编译器,体积比运行时版本大10k左右,程序运行时把模版转换成render函数,性能不如运行时版本。 如何切换到完整版?
// vue.config.js
module.exports = {
runtimeCompiler:true
}
//重启终端
2.3.6 initEvent方法
- 点击浏览器的前进后退时, 没有重新加载路由对应的组件
- popstate(),历史发生变化时被出发
initEvent(){
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}