第四章、Vue3高级
目录
- 十六、vue3高级语法补充
- 1、自定义指令
- 2、指令的生命周期
- 3、指令的修饰符和参数
- 4、案例
- 5、teleport
- 6、Vue插件
- 十七、vue3源码学习
- 1、虚拟dom的优势
- 2、虚拟dom的渲染过程
- 3、vue源码三大核心系统
- 4、实现简单的vue
十六、vue3高级语法补充
1、自定义指令
- 局部指令
- 全局指令
// main.js
import {createApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive("focus", {
mounted(el, bindings, vnode, preVnode) {
el.focus()
}
})
app.mount('#app')
2、指令的生命周期
| vue3 | vue2 | 生命周期 |
|---|---|---|
| created | 新的 | 在绑定元素attribute或事件监听器被应用之前调用 |
| beforeMount | bind | 当指令第一次绑定到元素并且在挂载父组件之前调用 |
| mounted | inserted | 在绑定元素的父组件被挂载后调用 |
| beforeUpdate | 新的 | 在更新包含组件的VNode之前调用 |
| updated | update(移除)、componentUpdated | 在包含组件的VNode及其子组件的VNode更新后调用 |
| beforeUnmount | 新的 | 在卸载绑定元素的父组件之前调用 |
| unmounted | unbind | 当指令与元素解除绑定且父组件已卸载时,只调用一次 |
3、指令的修饰符和参数
4、案例
// src/main.js
import {createApp} from 'vue'
import App from './App.vue'
import registerDirectives from "./directives"
const app = createApp(App)
registerDirectives(app)
app.mount('#app')
{{timestamp}}
// src/directives/index.js
import formatTime from "./format-time"
export default function (app) {
formatTime(app)
}
// src/directives/format-time.js
import dayjs from "dayjs"
export default function (app) {
let formatString = ""
app.directive("format-time", {
created(el, bindings) {
formatString = bindings.value || "YYYY-MM-DD HH:mm:ss"
},
mounted(el) {
console.log("指令")
const textContent = parseInt(el.textContent)
// npm i -S dayjs
el.textContent = dayjs(textContent).format(formatString)
}
})
}
5、teleport
- 概念
* 某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置
* teleport的两个属性
- to:指定将其中的内容移动到的目标元素,可以使用选择器
- disabled:是否禁用teleport的功能
- 基本使用
黄婷婷
孟美岐
姜贞羽
鞠婧祎
6、Vue插件
- 概念
* 通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式
- 对象类型:一个对象,但是必须包含一个install的函数,该函数会在安装插件时执行
- 函数类型:一个function,这个函数会在安装插件时自动执行
* 插件可以完成的功能没有限制,比如下面的几种都是可以的
- 添加全局方法或者property,通过把它们添加到config.globalProperties上实现
- 添加全局资源:指令/过滤器/过渡等
- 通过全局mixin来添加一些组件选项
- 一个库,提供自己的api,同时提供上面提到的一个或多个功能
- 基本使用
// src/main.js
import {createApp} from 'vue'
import App from './App.vue'
import pluginObject from "./plugins/plugins_object"
import pluginFunction from "./plugins/plugins_function"
const app = createApp(App)
app.use(pluginObject)
app.use(pluginFunction)
app.mount('#app')
// src/plugins/plugins_object.js
export default {
install(app) {
// 全局属性命名方式一般以$开头
app.config.globalProperties.$name = "黄婷婷"
}
}
// src/plugins/plugins_function.js
export default function (app) {
app.config.globalProperties.$age = 18
}
十七、vue3源码学习
1、虚拟dom的优势
* 将真实元素抽象成vnode(js对象),操作更加方便
* 避免直接操作dom,减少浏览器回流,提高性能
* 不同的渲染器,可在对应的平台(ios、android)渲染
2、虚拟dom的渲染过程
* template -> render() -> vnode -> patch(n1,n2)(diff算法) -> dom(渲染)
3、vue源码三大核心系统
* compiler模块:编译模板系统
* runtime模块:也可以称之为renderer模块,真正渲染的模块
* reactivity模块:响应式系统
4、实现简单的vue
- 模块划分
* 渲染系统模块
- h函数,用于返回一个vnode对象
- mount函数,用于将vnode挂载到dom上
- patch函数,用于对两个vnode进行对比,决定如何处理新的vnode
* 可响应式系统模块
* 应用程序入口模块
- index.html
vue3
- renderer.js
// 一、渲染系统模块
// a、h函数:创建vnode
const h = (tag, props, children) => {
return {
tag,
props,
children
}
}
// b、mount函数:挂载dom
const mount = (vnode, container) => {
// 1、创建真实元素
const el = vnode.el = document.createElement(vnode.tag)
// 2、处理props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key]
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), value)
} else {
el.setAttribute(key, value)
}
}
}
// 3、处理children
if (vnode.children) {
if (typeof vnode.children === "string") {
el.textContent = vnode.children
} else {
vnode.children.forEach(item => {
mount(item, el)
})
}
}
// 4、递归添加子节点
container.appendChild(el)
}
// c、patch函数:diff算法
const patch = (n1, n2) => {
if (n1.tag !== n2.tag) {
const n1ElParent = n1.el.parentElement
n1ElParent.removeChild(n1.el)
mount(n2, n1ElParent)
} else {
// 1、取出n1的el,并在n2中进行保存
const el = n2.el = n1.el
// 2、处理props
const oldProps = n1.props || {}
const newProps = n2.props || {}
// 2.1、获取所有的newProps添加到el
for (const key in newProps) {
const oldValue = oldProps[key]
const newValue = newProps[key]
if (newValue !== oldValue) {
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue)
}
}
}
// 2.2、删除旧的props
for (const key in oldProps) {
if (key.startsWith("on")) {
const value = oldProps[key]
el.removeEventListener(key.slice(2).toLowerCase(), value)
}
if (!(key in newProps)) {
el.removeAttribute(key)
}
}
// 3、处理children
const oldChildren = n1.children || []
const newChildren = n2.children || []
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.innerHTML = newChildren
}
} else {
if (typeof oldChildren === "string") {
el.innerHTML = ""
newChildren.forEach(item => {
mount(item, el)
})
} else {
/**
* 1、为什么使用key性能更高?
* - key相等的做patch,vnode可以尽量做move,而减少mount和unmount
*/
const commonLength = Math.min(oldChildren.length, newChildren.length)
// 3.1、oldChildren.length === newChildren.length:递归patch(n1, n2)
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i])
}
// 3.2、oldChildren.length < newChildren.length:挂载mount(vnode, container)
if (oldChildren.length < newChildren.length) {
newChildren.slice(oldChildren.length).forEach(item => {
mount(item, el)
})
}
// 3.3、oldChildren.length > newChildren.length:卸载
if (oldChildren.length > newChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => {
el.removeChild(item.el)
})
}
}
}
}
}
- reactive.js
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => {
effect()
})
}
}
let activeEffect = null
function watchEffect(effect) {
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
return dep
}
function reactive(raw) {
// vue2
/*Object.keys(raw).forEach(key => {
const dep = getDep(raw, key)
let value = raw[key]
Object.defineProperty(raw, key, {
get() {
dep.depend()
return value
},
set(newValue) {
if (value !== newValue) {
value = newValue
dep.notify()
}
}
})
})*/
// vue3
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key)
dep.depend()
return target[key]
},
set(target, key, newValue) {
const dep = getDep(target, key)
target[key] = newValue
dep.notify()
}
})
}
- index.js
function createApp(rootComponent) {
return {
mount(selector) {
const container = document.querySelector(selector)
let isMounted = false
let oldVNode = null
watchEffect(function () {
if (!isMounted) {
oldVNode = rootComponent.render()
mount(oldVNode, container)
isMounted = true
} else {
const newVNode = rootComponent.render()
patch(oldVNode, newVNode)
oldVNode = newVNode
}
})
}
}
}