Vue基础语法
初始化
// main.js
import Vue from "Vue"
import App from "App"
new Vue({
render: h => h(App)
}).$mount("#app")
- h创建虚拟dom,$mount函数 将虚拟dom转为真实dom
- Vue Router原理分析与实现
生命周期与主要事件
创建
- new Vue()
- 初始化事件
- 初始化生命周期钩子
- 初始化h函数
- 初始化props
- …
- beforeCreate
- 初始化注入与校验,注入到Vue实例
- props
- data
- methods
- 初始化注入与校验,注入到Vue实例
- created
- Vue实例创建完毕
编译
- 将template编译到render函数中
- 存在 el ,执行vm.$mount(el)
- 指定template,render template
- 未指定template,将el外部HTML作为 template编译
- beforeMount
- 无法获取新dom结构的内容
- 创建vm.$el,替换 el
- mounted
- 能够获取新dom结构的内容
- 修改data时
- 触发beforeUpdate,能获取旧的渲染内容
- 内部完成新旧虚拟dom对比,重新渲染,完成应用更新
- 触发updated,能获取新的渲染内容
销毁
- 销毁阶段,vm.$destroy()
- 解除绑定
- 销毁子组件
- 销毁事件监听器
- destroyed
如果SPA,模板编译工作是在打包或者构建时候完成的,不在运行时处理模板编译工作,就是说,模板编译是提前执行的。
语法
- 插值表达式
- 指令
内置指令共14个 ,Vue支持按需自定义指令。 - 计算属性和侦听器
- Class和style绑定
- 条件渲染与列表渲染
- 表单输入绑定
其它特性
- 组件
- 插槽
- 插件
- 混入mixin
- 深入响应式原理
- 不同构建版本的Vue
VueRouter
基本使用
- 注册
// router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
// Vue.use用来注册插件,会调用传入对象的install方法
// Vue.use用来注册组件,会直接调用传入的函数
Vue.use(VueRouter)
// main.js
import Vue from 'vue'
import App from './app.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render h => h(App)
}).$mount('#app')
- 创建
// router/index.js
// 路由懒加载
const mainPage = () => import(/* webpackChunkName: "MainPage" */ './MainPage.vue')
const routes = [
{ path: '/main', component: mainPage },
{
// 嵌套路由
path: "/",
component: import(/* webpackChunkName: "MainPage" */ './RootPage.vue'),
children: [
{
name: "SubPage1",
// path是相对路径
path: "",
component: import(/* webpackChunkName: "MainPage" */ './SubPage01.vue'),
},
{
name: "SubPage1",
path: "sub2",
component: import(/* webpackChunkName: "MainPage" */ './SubPage01.vue'),
},
]
}
]
const router = new VueRouter()
export default router
// RootPage.vue
<template>
<div id="app">
<div>公共头部</div>
<router-view />
<div>公共尾部</div>
</div>
</template>
- 调用
// app.vue
<template>
<div id="app">
<router-link to="/"> mainPage <router-link>
<router-view />
</div>
</template>
在router注册到new Vue对象中时,注入了 $route 和 $router 对象。 $route存储了当前路由规则信息,包括路径、参数。也可从 $router.currentRoute 获取当前路由信息。 $router是VueRouter的实例对象,提供路由相关的方法。
源码仿写
注意点
// vue.config.js 文件
module.exports = {
// 默认为false,即不带编译器版本的Vue,改为true,启用完整版Vue,即带编译器版本的Vue,可将template模板字符串编译成render函数
// 为什么单文件组件可以正常运行?
// 因为Vue在打包过程中运行了预编译,将template编译成render函数
// 完整版体积比运行时版大10k左右
runtimeCompiler:true
}
编程式导航
- replace
- push
- go
replace 与 push 都可以传递路由参数,跳转到指定路径,replace方法不会记录本次跳转历史。
replace() {
this.$router.replace("/login")
},
push() {
this.$router.push({name: "detail", parmas: {id: 1}})
},
go() {
this.$router.go(-1)
}
路由
- hash
基于锚点和 onhashchange 事件 - history
基于HTML5中的history API- history.pushState() 在IE10以后才支持
- history.replaceState()
- 需要服务器支持
vue-cli 和 react-cli 都在内部做好了history模式兼容。
虚拟DOM
概述
- 通过JS对象来描述真实DOM
- 虚拟DOM只会更新发生变化的节点,通过diff算法寻找新旧节点的差异
- diff的过程是比较新旧节点,最后将比对结果更新到真实DOM上。是对真实DOM打补丁的过程
- 新旧VNode节点是否相同(key与sel相同)
- 不相同,删除旧内容,重新渲染
- 相同,判断VNode是否有text,如果有text,而与oldVNode 的text不同 ,则更新test
- 如果新的VNode有children,判断子节点的差异,仅将差异更新到真实DOM
- 新旧VNode节点是否相同(key与sel相同)
作用
- 渲染生成真实DOM
- SSR (Nuxt.js , Next.js)
- 原生应用 (Weex,RN)
- 小程序(nui-app,mpvue)
视图越复杂,虚拟DOM提升性能效果越明显。简单视图只需要更新特定的节点时,往往不需要虚拟DOM操作。所以,并不是任何时候都需要使用虚拟DOM。
虚拟DOM 开源库
- Snabbdom(vue2.xx 基于此开源库)
- virtual-dom (最早的虚拟DOM开源库)
Snabbdom源码解析
- 主要函数
- h
h函数: 生成虚拟DOM的函数
第一个参数 : 标签 + 选择器
第二个参数 : 标签中的内容/子节点数组 - patch
第一个参数可以是虚拟DOM或者真实DOM,内部会转换成VNode
第二个参数:VNode
返回值:更新后的VNode - init
注册模块
h()函数的第二个参数设置模块需要的数据(对象)
初始化patch函数,参数:数组[模块],不使用模块传 []
- h
- 有自己的生命周期函数
- 所有组件的生命周期函数存储在回调函数数组中
cbs[钩子函数名称] = [对应函数的数组]
虚拟节点 = 虚拟节点 ? (vnode.sel === undefined) 拟节点 : 转换真实dom为虚拟节点
判断相同节点:判断key和sel是相同的
import { h, thunk, init } from 'snabbdom';
// 导入模块
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'
// 注册模块
// h()函数的第二个参数设置模块需要的数据(对象)
// 初始化patch函数,参数:数组[模块],不使用模块传 []
// let patch = init([])
let patch = init([style, eventlisteners])
// h函数: 生成虚拟DOM的函数
// 第一个参数 : 标签 + 选择器
// 第二个参数 : 标签中的内容/子节点数组
// let newVNode = h('div#newElement.className', 'this is a div element')
let newVNode = h('div#newElement.className', [
h('h1', 'this is a h1 element'),
h('div', 'this is a div element'),
])
// 获取占位div元素
const app = document.querySelector('#app')
// 第一个参数可以是虚拟DOM或者真实DOM,内部会转换成VNode
// 第二个参数:VNode
// 返回值:更新后的VNode
let lastVNode = patch(app, newVNode)
// 更新
// newVNode = h('div#newElement.className', 'new value')
newVNode = h('div#newElement.className', [
h('h1', 'change h1 content'),
h('div', 'this is a div element'),
])
lastVNode = patch(lastVNode, newVNode)
const modeVNode = h('div', {
style: {
backgroundColor: 'red'
},
on: {
click: handler
}
}, [h('p', 'this is a p element in div')])
// const modeVNode = h('div',{
// style: {
// backgroundColor : 'red'
// },
// on: {
// click: handler
// }
// }, '我是红色的')
function handler() {
console.log('被点了');
}
setTimeout(() => {
console.log(lastVNode, modeVNode);
patch(lastVNode, modeVNode)
}, 2000);
// clean VNode
setTimeout(() => {
patch(lastVNode, h('!'))
}, 2000);