Skip to content
微信公众号

Vue 3.0

Vue 3与 Vue 2的区别

源码组织方式的变化,Vue3的源码全部采用TypeScript方式重写,项目组织的方式也有变化,使用Monorepo的方式组织项目的结构,把独立的功能模块提取到不同的包中,Vue3虽然代码重写了,但是90%以上的api还是兼容2.x,并根据社区的反馈添加了Composition API即组合式api,它是用来解决2.x开发大型项目时遇到超大组件使用options不好拆分和重用的问题,

学习Composition API最好的方式是查看官方的RFC(Request For Comments)Composition API RFC文档

为什么要有Composition API呢?Vue2开发中小型项目很好用,但是开发大型项目需要长期迭代维护会有一些限制,例如会有功能复杂的组件,组件复杂的时候阅读起来比较困难,因为vue2使用的是Options API,Options API指的是包含一个描述组件选项(data、methods、props等)的对象,Options API开发复杂组件,同一个功能逻辑的代码被拆分到不同选项,为了看懂同一个功能需要不同的上下滚动滚动条,例如:

js
export default {
    data(){
        return {
            position:{
                x: 0,
                y: 0
            }
        }
    },
    created(){
        window.addEventListener('mousemove',this.handle)
    }
    destroyed(){
        window.removeEventListener('mousemove',this.handle)
    },
    methods:{
        handle(e){
            this.position.x = e.pageX
            this.position.y = e.pageY
        }
    }
}

而Composition API是vue3新增的一组基于函数的API,可以更灵活的组织组件的逻辑。例如:

js
import { reactive, onMounted, onUnmounted} from 'vue'
function useMousePosition(){
    const position = reactive({
        x: 0,
        y: 0
    })
    const update = (e)=>{
        position.x = e.pageX
        position.y = e.pageY
    }
    onMounted(()=>{
        window.addEventListener('mousemove',update)
    })
    onUnmounted(()=>{
        window.addEventListener('mousemove',update)
    })
    return position
}
export default{
    setup(){
        const position = useMousePosition()
        return {
            position
        }
    }
}

在性能方面Vue3有很大幅度的提升,主要包括响应式系统升级、编译优化、源码体积的优化。

vue2中响应式系统的核心是defineProperty,而Vue3使用proxy对象来重写响应式系统,它可以监听动态新增的属性、监听属性的删除以及监听数组的索引和length属性的操作。

编译优化,vue2中通过标记静态根节点,优化diff的过程,而vue3中标记和提升所有的静态根节点,diff的时候只需要对比动态节点内容。另外vue3中新引入了Fragments特性,模板中不需要再创建唯一的根节点。通过patch flag在diff过程中跳过静态节点,只需要更新动态节点的内容,提升了diff性能。然后缓存事件处理函数,减少了不必要的更新操作。

优化打包体积,vue3中移除了一些不常用的API,例如inline-template、filter等,可以让代码的体积变小。另外vue3对Tree-shaking的支持更好,Tree-shaking依赖ES Module,通过编译时的静态分析,将没有使用的模块,打包过滤掉。

随着vue3的发布,官方提供了开发工具Vite,使用Vite在开发阶段测试的时候不需要打包,可以直接去运行项目,提升了开发的效率。

vite的vue-cli的区别是vite在开发模式下不需要打包可以直接运行,vue-cli开发模式下必须对项目打包才可以运行。vite在生产环境下使用Rollup基于ES Module的方式打包,vue-cli使用Webpack打包。

Composition API

Composition API仅仅是vue3新增多的api,像createApp函数用来创建Vue对象,setup函数是Composition API的入口,reactive函数用来创建响应式对象。

js
import { createApp, reactive } from 'vue'

const app = createApp({
    setup(){
        //第一个参数props
        //第二个参数context,attrs、emit、slots
        const position = reactive({
            x: 0,
            y: 0
        })
        return {
            position
        }
    },
    mounted(){
        this.position.x = 100
    }
})
app.mount('#app')

生命周期钩子函数

在setup中可以使用组件生命周期中的钩子函数,但是需要在生命周期钩子函数前面加上on,然后首字母大写,比如mounted在setup函数中对应onMounted。另外setup是在组件初始化之前执行的,是在beforeCreate和created之间执行的,所以在beforeCreate和created的代码都可以在setup中。

Options APIHook inside inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked

reactive/toRefs/ref

这三个函数都是用来创建响应式数据的。

toRefs可以将响应式对象中的所有属性都转换成响应式的,toRefs要求传入的参数必须是一个代理对象,如果不是代理对象则会报警提示。它内部会创建一个新的对象,然后遍历代理对象的所有属性,把所有属性的值都转换成响应式对象,然后挂载到新创建的对象上并返回。它内部会为代理对象的每一个属性创建一个具有value属性的对象,该对象是响应式的,value属性具有get和set,这一点和ref类似,get返回代理对象中对应属性的值,set中给代理对象的属性赋值,所以返回的每一个属性都是响应式的。

js
import { createApp, ref , toRefs} from 'vue'
createApp({
    setup(){
        const position = {
            x: 0,
            y: 0
        }
        const update = e=>{
            position.x = e.pageX
            position.y = e.pageY
        }
        onMounted(()=>{
            window.addEventListener('mousemove',update)
        })

        onUnmounted(()=>{
            window.removeEventListener('mousemove',update)
        })
        return toRefs(position)
    }
}).mount('#app')

ref的作用是把普通的数据转换成响应式数据,而reactive的作用是把一个对象转换成响应式数据,ref可以把基本类型数据包装成响应式对象。

js
import { createApp, ref } from 'vue'
function useCount(){
    const count = ref(0)
    return {
        count,
        increase:()=>{
            count.value++
        }
    }
}

createApp({
    setup(){
        return {
            ...useCount()
        }
    }
}).mount('#app')

ref如果传入对象,则内部会调用reactive返回代理对象。如果是基本类型的值,则内部会创建有value属性的对象,该对象的value属性具有get和set,在get中收集依赖,在set中触发更新。

Computed

两种用法,第一种是传入获取值得函数,函数的内部依赖响应式的数据,当依赖的数据发生变化后会重新执行该函数获取数据。computed函数返回一个不可变的响应式对象,类似于使用ref创建的对象,只有一个value属性,获取计算属性的值可以通过value来获取。

js
computed(()=>count.value+1)

第二种用法是传入一个对象,对象具有set和get,返回不可变的响应式对象。

js
const count = ref(1)
const plusOne = computed({
    get: () => count.value + 1,
    set: val=>{
        count.value = val - 1
    }
})

Watch

Watch函数有三个参数,第一个参数是要监听的数据,第二个参数是监听到数据变化后执行的函数,这个函数有两个参数分别是新值和旧值,第三个参数是选项对象,deep和immediate。

Watch的返回值是取消监听的函数。

js
import { createApp, ref ,watch} from 'vue'

createApp({
    setup(){
        const question = ref('')
        const answer = ref('')

        watch(question,async (newValue,oldValue)=>{
            const response = await fetch('http://www.xxx.com')
            answer.value = await response.json()
        })

        return {
           question,
           answer
        }
    }
}).mount('#app')

Vue3中提供了WatchEffect,它是watch函数的简化版本,也用来监视数据的变化。它接收一个函数作为参数,监听函数内响应式数据的变化。

js
import { createApp, ref ,watchEffect } from 'vue'

createApp({
    setup(){
        const count = ref(0)

        const stop = watchEffect(()=>{
            console.log(count.value)
        })

        return {
           count,
           stop,
           increase:()=>{
            count.value++
           }
        }
    }
}).mount('#app')

响应式原理

和Vue2相比Vue3响应式系统底层使用Proxy对象实现,在初始化的时候不需要遍历所有的属性再把属性通过defineProperty转换成get和set。另外在有属性嵌套的时候,在访问属性的过程中处理下一级属性。所以Vue3的响应式性能比Vue2好。Vue3可以监听动态添加的属性,监听属性的删除操作,监听数组索引和length属性的修改操作,另外也可以将响应式系统作为单独的模块使用。

先看一下Proxy对象的使用

js
const target = {
    foo: 'xxx',
    bar: 'yyy'
}

// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
const proxy = new Proxy(target,{
    get(target,key,receiver){
        //return target[key]
        return Reflect.get(target,key,receiver)
    },
    set(target,key,value,receiver){
        //target[key]=value
        Reflect.set(target,key,value,receiver)
    },
    deleteProperty(target,key){
        //delete target[key]
        Reflect.deleteProperty(target,key)
    }
})

上面有两个注意的问题,第一个问题是set和deleteProperty中需要返回布尔类型的值,在严格模式下,如果返回false的话会出现TypeError的异常。第二个问题是Proxy和Reflect中使用的receiver,Proxy中receiver是Proxy或者继承Proxy的对象,Reflect中receiver是如果target对象中设置了getter,getter中的this指向receiver。

js
const obj = {
    get foo(){
        console.log(this)
        return this.bar
    }
}

const proxy = new Proxy(obj,{
    get(target,key,receier){
        if(key === 'bar'){
            return 'value - bar'
        }
        return Reflect.get(target,key)
    }
})

reactive的模拟实现

它接收一个参数,判断这参数是否是对象,然后创建拦截器对象,设置get/set/deleteProperty,返回Proxy对象。

js
const isObject = val => val!==null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target):target 
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target,key) => hasOwnProperty.call(target,key)

export function reactive(target){
    if(!isObject(target)) return target
    const handler = {
        get(target,key,receiver){
            //收集依赖
            console.log('get',key)
            const result = Reflect.get(target,key,receiver)
            return convert(result)
        },
        set(target,key,value,receiver){
            const oldValue = Reflect.get(target,key,receiver)
            let result = true
            if(oldValue!==value){
                result = Reflect.set(target,key,value,receiver)
                //触发更新
                console.log('set',key,value)
            }
            return result
        },
        deleteProperty(target,key){
            const hadKey = hasOwn(target,key)
            const result = Reflect.deleteProperty(target,key)
            if(hadKey && result){
                //触发更新
                console.log('delete',key)
            }
        }
    }
    return new Proxy(target,handler)
}

收集依赖

在依赖收集的过程中会创建三个集合,分别是targetMap、depsMap、dep。targetMap的作用是记录目标对象和一个字典,也就是中间的这个map。targetMap使用的类型是WeakMap,key是目标对象,值是depsMap。depsMap是一个字典Map,key是目标对象的属性名称,值是set集合。set集合中元素不会重复,里面存储的是effect函数。通过这种方式可以收集目标对象和目标对象的属性以及Effect函数。

js
const isObject = val => val!==null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target):target 
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target,key) => hasOwnProperty.call(target,key)

export function reactive(target){
    if(!isObject(target)) return target
    const handler = {
        get(target,key,receiver){
            //收集依赖
            console.log('get',key)
            track(target,key)
            const result = Reflect.get(target,key,receiver)
            return convert(result)
        },
        set(target,key,value,receiver){
            const oldValue = Reflect.get(target,key,receiver)
            let result = true
            if(oldValue!==value){
                result = Reflect.set(target,key,value,receiver)
                //触发更新
                console.log('set',key,value)
            }
            return result
        },
        deleteProperty(target,key){
            const hadKey = hasOwn(target,key)
            const result = Reflect.deleteProperty(target,key)
            if(hadKey && result){
                //触发更新
                console.log('delete',key)
            }
        }
    }
    return new Proxy(target,handler)
}

let activeEffect = null
export function effect(callback){
    activeEffect = callback
    callback()  //访问响应式对象属性,去收集依赖
    activeEffect = null
}

let targetMap = new WeakMap()

export function track(target,key){
    if(!activeEffect) return 
    let depsMap = targetMap.get(target)
    if(!depsMap){
        targetMap.set(target,(depsMap=new Map()))
    }
    let dep = depsMap.get(key)
    if(!dep){
        depsMap.set(key,(dep = new Set()))
    }
    dep.add(activeEffect)
}

依赖收集之后就是触发更新trigger

js
const isObject = val => val!==null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target):target 
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target,key) => hasOwnProperty.call(target,key)

export function reactive(target){
    if(!isObject(target)) return target
    const handler = {
        get(target,key,receiver){
            //收集依赖
            console.log('get',key)
            track(target,key)
            const result = Reflect.get(target,key,receiver)
            return convert(result)
        },
        set(target,key,value,receiver){
            const oldValue = Reflect.get(target,key,receiver)
            let result = true
            if(oldValue!==value){
                result = Reflect.set(target,key,value,receiver)
                //触发更新
                trigger(target,key)
                console.log('set',key,value)
            }
            return result
        },
        deleteProperty(target,key){
            const hadKey = hasOwn(target,key)
            const result = Reflect.deleteProperty(target,key)
            if(hadKey && result){
                //触发更新
                trigger(target,key)
                console.log('delete',key)
            }
        }
    }
    return new Proxy(target,handler)
}

let activeEffect = null
export function effect(callback){
    activeEffect = callback
    callback()  //访问响应式对象属性,去收集依赖
    activeEffect = null
}

let targetMap = new WeakMap()

export function track(target,key){
    if(!activeEffect) return 
    let depsMap = targetMap.get(target)
    if(!depsMap){
        targetMap.set(target,(depsMap=new Map()))
    }
    let dep = depsMap.get(key)
    if(!dep){
        depsMap.set(key,(dep = new Set()))
    }
    dep.add(activeEffect)
}

export function trigger(target,key){
    const depsMap = targetMap.get(target)
    if(!depsMap) return
    const dep = depsMap.get(key)
    if(dep){
        dep.forEach(effect=>{
            effect()
        })
    }
}

ref

ref可以把基本数据类型数据,转成响应式对象。ref返回的对象重新赋值成对象也是响应式的。reactive返回的对象,重新赋值丢失响应式,reactive返回的对象不可以解构。

js
export function ref(raw){
    //判断raw是否是ref创建的对象,如果是的话直接返回
    if(isObject(raw) && raw.__v__isRef){
        return
    }
    let value = convert(raw)
    const r = {
        __v__isRef: true,
        get value(){
            track(r,'value')
            return value
        },
        set value(newValue){
            if(newValue!==value){
                raw = newValue
                value = convert(raw)
                trigger(r,'value')
            }
        }
    }

    return r
}

toRefs

js
export function toRefs(proxy){
    const ret = proxy instanceof Array ? new Array(proxy.length) : {}
    for(const key in proxy){
        ret[key] = toProxyRef(proxy,key)
    }

    return ret
}

function toProxyRef(proxy,key){
    const r = {
        __v__isRef: true,
        get value(){
            return proxy[key]
        },
        set value(newValue){
            proxy[key] = newValue
        }
    }
    return r
}

computed

js
export function computed(getter){
    const result = ref()
    effect(()=>(result.value = getter()))
    return result
}

本站总访问量次,本站总访客数人次
Released under the MIT License.