第四章、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')




// 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
                }
            })
        }
    }
}