Vue3 与 Vue2 对比学习

403 阅读8分钟

只是做了一些比较常用到的Api的对比总结,如需详细学习,还是以Vue官网为主。

1. 代码区别:

1.1 Vue 2

只能用选项式 API

  • 优点: 简单易上手,容易理解, 代码结构相对规整;
  • 缺点: 组建虽然也可以重用,但是重用性较差,并且会随着组建越来越臃肿,重用性会越来越差。

1.2 Vue 3

可以使用选项式 API ,也可以使用组合式 API

  • 优点:可以把组建充分拆分,大大增强了重用性,并且组合式API 可以精确的绑定监听多层引用数据的某个属性,更加的高效和便利。而 Vue 2在绑定和监听数据的过程是不够精确的,往往在回调中产生副作用。
  • 缺点:上手有一定的难度,需要Vue 2的一些基础,才能更好的理解一些函数的使用。

2. Vue2与Vue3项目对比总结

2.1 main.js

一般使用Vue创建一个单页面项目,都会去创建一个main.js,在这个文件里,可以引入插件,进行一些预先操作以及渲染根页面App.vue

  • Vue 2 main.js
/* 1.引入必要的插件和根页面App*/
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store"; 
import customDirective from "./directive";

/* 2.根据项目需要进行相应的预处理*/
/*引入安装自定义插件*/
Vue.use(customDirective);
/*定义自定义的全局属性(实际更建议写成插件,使用Vue.use()引入项目)*/
Vue.prototype.$cusString = "cusString";

/* 3.处理完成开始渲染 */
new Vue({
    router,
    store,
    render:(h) => h(App)
}).$mount("#app");

  • Vue 3 main.js
/* 1.引入必要的插件和根页面App*/
import { createApp } from 'vue'
import App from "./App.vue";
import router from "./router";
import store from "./store"; 
import customDirective from "./directive";
/* 2.根据项目需要进行相应的预处理*/
const app = createApp(App).use(router).use(store).use(customDirective);
/*定义自定义的全局属性(实际更建议写成插件,使用Vue.use()引入项目)*/
/* 2.1可以在选项式vue页面中直接使用this调用,但不推荐在组合式js中直接获取使用这种方式定义全局属性。*/
/* 因为Vue 3官方并没有直接给出一个相应的钩子函数来导出此处定义的全局变量。*/
/* getCurrentInstance 虽然可以获取内部组建实例,但是不建议在应用中直接使用这种方式来处理业务*/
app.config.globalProperties.$cusString = "cusString";
/* 2.2 如果要使用组合式调用全局定义的属性时,建议使用provide,然后在子组件中使用 inject来调用inject('$cusString')*/
app.provide('$cusString', 'cusString')

/* 3.处理完成开始渲染 */
app.mount('#app');

必要知识点:

2.2 component组建对比

对于整个Vue项目而言,每个.vue文件都可以被当作是一个随身带有css样式事件逻辑虚拟DOM树(组建),然后按照需求,渲染(render)到不同页面的相关位置。

  • Vue 2 component
<template>
    <div>
        ...写html
    </div>
</template>
<script>
export default {
    name: "componentA", //组建命名
    data() {
        return {}  //组建变量
    },
    components: {}, //注册局部子组件
    directives: {}, //注册局部指令
    filters: {}, //注册局部过滤器
    mixins: [], //组建局部混入公共部分
    props: {}, //父组件传过来的变量
    computed: {}, //计算属性,一般是对data,props或者全局变量,进行相关处理后作为响应式的变量使用。
    methods: {},//组建中的方法,
    watch: {},//监听,与computed 有相似之处,区别在于watch是监听变量变化之后的回调函数,computed是变量变化之后,重新形成的数据结果并显示。
    provide() {
        return {} //此组件注入依赖,后代组建可以直接使用inject 调用,无需考虑逻辑关系,
    },
    inject: [], //用来接收祖先组建,provide 注入的数据和变量。用法和props相似。
    beforeCreate() {}, //在实例初始化之后,进行数据侦听data和事件/侦听器watch的配置之前同步调用。
    created() {}, //在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听data、计算属性computed、方法methods、事件/侦听器watch的回调函数。然而,挂载阶段还没开始(dom未被渲染,有关dom的操作不可用)
    beforeMount(){},// 在挂载开始之前被调用
    mounted(){
        this.$nextTick(function () {  
            // 仅在整个视图都被渲染之后才会运行的代码  
        })
    }, //实例被挂载后调用, 如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时vm.$el 也在文档内。mounted不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 使用$nextTick
    beforeUpdate() {}, //在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。
    updated() {}, //在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
    activated() {},//被 keep-alive 缓存的组件激活时调用。
    deactivated() {}, //被 keep-alive 缓存的组件失活时调用。
    beforeDestoryed() {} // 实例销毁之前调用。在这一步,实例仍然完全可用。
    destoryed() {}, //实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
    errorCaptured(err, vm, info) {} //在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 `false` 以阻止该错误继续向上传播。
}
</script>
<style lang="scss" scoped>
    ...写css
</style>
  • Vue 3 component

Vue 3 在操作组建的时候,分为两种方式选项式组合式

  • 选项式: 与Vue 2 非常相似,都是根据生命周期的不同阶段来调用相应的选项式Api提供的钩子函数,区别更多在于相应数据响应式的底层实现原理。
  • 组合式: 是Vue 3新增的一种写法,主要是与Vue 3 实例提供的setup()钩子函数相结合,可以把整个vue component中的代码逻辑抽离出来成js文件,从而极大的提高和增强组建的重用性,让代码更加灵活。setup()函数是组合式Api的入口函数,没有这个函数,组合式开发无从开始。
  • Vue 3 选项式:
<template>
    <div>
        ...写html
    </div>
</template>
<script>
export default {
    //与Vue 2基本一致,有略微区别。与Vue2 一致的就不重复了,就写一些不一样的。
    ...,
    expose: ['publicMethod'], //用于声明当组件实例被父组件通过模板引用访问时暴露的公共属性。当使用 expose 时,只有显式列出的属性将在组件实例上暴露,且仅影响用户定义的属性——它不会过滤掉内置的组件实例属性。
    methods: { 
        publicMethod() {}, 
        privateMethod() {} 
    },
    beforeUnmount() {}, //在一个组件实例被卸载之前调用。==beforeDestoryed
    unmounted() {},// 在一个组件实例被卸载之后调用。==destoryed 可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。 
}
</script>
<style lang="scss" scoped>
    ...写css
</style>
  • Vue 3 组合式:

组合式的实现都是在setup()函数的基础上实现的,而且在这个函数中,可以使用Vue提供的组合式Api的生命周期钩子函数,完成一整个生命周期的过程。

<template>
    <div>
        ...写html
    </div>
</template>
<script>
import { ref, 
        reactive, 
        computed, 
        readonly, 
        watch,
        watchEffect,
        toRef,
        toRefs,
        onMounted,
        onUpdated,
        onUnmounted,
        onBeforeMount,
        onBeforeUpdate,
        onBeforeUnmount,
        onErrorCaptured,
        onActivated,
        onDeactivated,
        provide,
        inject,
        } from 'vue'
export default {
    setup(props, { attrs, slots, emit, expose }) {
        console.log(props) //父组件传过来的变量  
        console.log(attrs) // 透传 Attributes
        console.log(slots) //插槽
        console.log(emit) // 触发事件(函数,等价于 $emit)
        console.log(expose)// 暴露公共属性(函数)
        const dataNum = ref(0) //接受一个内部值,返回一个响应式的、可更改的 ref 对象。此对象只有一个指向其内部值的属性 .value
        const dataObj = reactive({ count: 0 }) //接收一个引用对象,返回一个对象的响应式代理。
        // ref(),reactive() 与 data 一致,定义一个具有响应式的变量
        const copy = readonly(dataObj) // 得到的copy,仅可读的一个响应式对象
        const computedObj = computed(() => dataNum.value + 1) // 计算属性,返回一个返回一个只读的响应式ref对象,自带响应式;也可以传入一个带有set和get函数的对象,是变量具有读写属性。
        watch(dataObj, (new,old,onCleanup) => {}, {flush: 'post'}) // 监听
        watchEffect((onCleanup) => {},{flush: 'post'}) // 监听
        const foo = toRef(props,'foo'); // 让解构后失去响应式的数据重新恢复响应式。此时foo.value的数据与props中传入的foo数据是一致的,
        const {foo} = toRefs(props); // 同上,不过是对于一个对象进行的。
        onMounted(()=>{}) //注册一个回调函数,在组件挂载完成后执行。
        onUpdated(() => {}) //注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用
        onUnmounted(() => {})//注册一个回调函数,在组件实例被卸载之后调用。在这里取消一些定时任务等。
        onBeforeMount(() => {}) //注册一个钩子,在组件被挂载之前被调用。
        onBeforeUpdate(() => {})//注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。
        onBeforeUnmount(() => {})//注册一个钩子,在组件实例被卸载之前调用。
        onErrorCaptured(() => {})//注册一个钩子,在捕获了后代组件传递的错误时调用。
        onActivated(() => {})//注册一个回调函数,若组件实例是 <KeepAlive> 缓存树的一部分,当组件被插入到 DOM 中时调用。
        onDeactivated(() => {})//注册一个回调函数,若组件实例是<KeepAlive>缓存树的一部分,当组件从 DOM 中被移除时调用。
        provide('count', dataNum); //提供一个值,可以被后代组件注入。
        const count = inject('count') 
        // 注入一个由祖先组件或整个应用 (通过 `app.provide()`) 提供的值。 (在组合式Api用这种方法替代全局属性)
        
        return { count } // 返回值会暴露给模板和其他的选项式 API 钩子
    },
    // 除了setup() 入口函数之外,依然可以使用选项式Api,选项式使用的Api在这里就省略了。
}
</script>
<style lang="scss" scoped>
    ...写css
</style>

既然已经使用了setup()来使用组合式Api并且也能进行相关组建的全生命周期的回调,但是上面的方式以我的理解还是没有摆脱选项式Api,为了更好的使用组合式Api增强重复性功能。那就要使用<script setup>语法塘。这个语法塘可以把这个标签内容当作setup()函数的作用域,并且<script setup>中的代码会在每次组件实例被创建的时候执行,就像选项式Api一样,并且任何在语法塘内声明的顶层的绑定(包括变量,函数声明,响应式变量以及 import 导入的内容) 都能在模板中直接使用

<template> 
    <div v-my-directive @click="log">{{ msg }}</div> 
    <button @click="count++">{{ count }}</button>
    <MyComponent />
</template>
<script setup>
    //...上面再setup() 中的使用方法是一致的,只不过是少了变量的导出
    import { ref } from 'vue'
    import MyComponent from './MyComponent.vue' // 无需注册直接使用
    import { myDirective as vMyDirective } from './MyDirective.js' // 外部引入的指令也要重命名,以遵守规范
    const props = defineProps({ foo: String }) // props
    const emit = defineEmits(['change', 'delete']) // $emit()
    // 变量 
    const msg = 'Hello!'  // 不具有响应式
    // 函数 无需使用method
    function log() { 
        console.log(msg) 
    }
    const count = ref(0)  // 响应式变量
    const vMyDirective = { 
        beforeMount: (el) => { 
            // 在元素上做些操作 
        } 
    } // 本地的自定义指令,命名必须遵循 vNameOfDirective 这样的命名规范
    defineExpose({ count, log }) // 规定了,父组件$parent使用本组件是,可以显示的自定义声明的属性。(这个模式下默认全部不可见)
        // 可以直接使用await,代码会被编译成 async setup() [这种模式下的语法塘相当于 代替了之前按的 created()]
    const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

必要知识点:

2.3 生命周期对比

Vue 2.0Vue 3.0选项式(setup())Vue 3.0组合式(<script setup></script>)
setup()setup作用域内容
beforeCreatebeforeCreatesetup作用域内容
createdcreatedsetup作用域内容
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestoryedbeforeUnmountonBeforeUnmount
destoryedunmountedonUnmounted

2.4 组合式Api和选项式Api总结

使用组合式Api解决了选项式Api一些固有的问题:

  1. 组建越写越臃肿难以拆分导致后期难以维护;
  2. 可以更好的碎片化开发可以减少了大量重复代码;
  3. 更好的兼容TS和单元测试有利于掌控代码质量等等。

但是选项式Api,也并不是一无是处的,因为再选项式Api中可以直接使用this,因为this可以代表当前组建实例,因此当使用一些定义在全局的变量和方法时(vue.prototype.$cusFun等),就会显得非常的便利的,可以直接使用。所以当Vue 3 提供了组合式Api时,并没有放弃选项式Api,两者混合使用会更高效

2.5 Vue2 与 Vue3 的响应式原理的理解:

  • Vue 2响应式:

使用Object.defineProperty()gettersetter方法劫持对象的属性, 然后结合Dom的模板渲染方法,当setter方法发生改变,带动Dom的数据的变化,实现响应式。缺点:虽然使用递归的方式调用Object.defineProperty()方法,可以很好的监听整个对象初始化属性,但是无法响应式的监听这个对象的新增的属性

  • Vue 3:

使用new Proxy()代理整个对象的方式,使用Proxy()代理中的settergetter方法为基础,然后结合Dom的模板渲染方法,实现数据的响应式。缺点:虽然可以整体监听整个对象的属性的增删改查,但是需要支持ES6,也就是说,无法支持老版本浏览器(ie)。

3. 必要知识点对比具体讲解:

3.1 app.use(plugin(obj|fun),options) 与 Vue.use(plugin(obj|fun),options)

2.0的 Vue.use() 一致的,都是为了安装插件使用的方法。是可以传递两个参数,第一个是插件本身(要么是带有install属性的对象,要么是被当作install使用的函数实例),第二个可选参数,是传入install函数的参数。

  • main.js
app.use(customDirective,options);
  • customDirective.js
export default {
    install(app, options) {
        //app 是Vue的全局实例,可以在这里进行相应的设置一些全局方法,属性,组建以及指令等等。
        //options 就是app.use 传递进来的options
    }
}

3.2 setup(props, { attrs, slots, emit, expose })

  • props 与 上下文
export default { 
    setup(props, { attrs, slots, emit, expose }) { 
        // props 是响应式的,并且会在传入新的 props 时同步更新
        // 透传 Attributes(非响应式的对象,等价于 $attrs) console.log(attrs) 
        // 插槽(非响应式的对象,等价于 $slots) console.log(slots) 
        // 触发事件(函数,等价于 $emit) console.log(emit) 
        // 暴露公共属性(函数) console.log(expose) 
    } 
}
  1. setup() 函数中返回的对象会暴露给模板和组件实例。其他的选项也可以通过组件实例来获取 setup() 暴露的属性,即可以直接使用this调用。
  2. setup() 自身并不含对组件实例的访问权,即在 setup() 中访问 this 会是 undefined。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。
  3. setup()函数自身就是一个普通函数,并不能让函数作用域内的局部变量转化成响应式的变量,需要使用ref(),reactive(),computed() 把有个普通变量转化成为一个响应式变量。

3.3 ref()与reactive()的区别

区别项ref()reactive()
持有数据可以持有任意类型的数据只能持有引用类型,Object,Array,Map,Set
响应式深层响应包括嵌套对象和数组深层响应包括嵌套对象和数组
使用被包裹了一层,必须使用.value调用可以当作对象直接使用
解构普通数据类型不存在解构的问题解构之后会失去响应式

3.4 watch()与watchEffect()的区别

区别项watch()watchEffect()
加载懒加载立即加载
触发根据监听对象精准触发会在触发函数里寻找全部的响应式对象,然后根据响应式对象的变化触发,不够精准

3.5 setup()与<script setup></script>的区别

区别setup()<script setup></script>
代码书写比较复杂,需要导出数据比较美观,可以直接再模板中使用,比较方便
重用重用性还是比较不好,只能处理个别数据可以直接使用js文件书写业务逻辑,可以让业务逻辑更加的碎片化可重兴大大提升,也有利于TS类型控制和UT单元测试的对代码质量更好的把控
可读性与选项式Api并没有很大的提升提升巨大
功能性不够完整功能更完整,更强大,比如使用directives,注册components
变量其他选项能访问return的变量其他选项无法访问创建的变量