全面总结 Vue 3.0 的新特性 Vue 3.0 新特性及使用方法

作者 : admin 本文共15422个字,预计阅读时间需要39分钟 发布时间: 2021-10-27 共1人阅读

全面总结 Vue 3.0 的新特性 Vue 3.0 新特性及使用方法

Vue3.0从20年九月发布第一个One Piece版本,到现在一直在更新优化;中文版的官方文档也已经放出;那么作为终端用户的我们来看下Vue3新增了哪些功能和特性。

尤大大在B站直播时分享了Vue3.0的几个亮点:

  • Performance:性能优化
  • Tree-shaking support:支持摇树优化
  • Composition API:组合API
  • Fragment,Teleport,Suspense:新增的组件
  • Better TypeScript support:更好的TypeScript支持
  • Custom Renderer API:自定义渲染器

在性能方面,对比Vue2.x,性能提升了1.3~2倍左右;打包后的体积也更小了,如果单单写一个HelloWorld进行打包,只有13.5kb;加上所有运行时特性,也不过22.5kb。

那么作为终端用户的我们,在开发时,和Vue2.x有什么不同呢?Talk is cheap,我们还是来看代码。

Tree-shaking

Vue3最重要的变化之一就是引入了Tree-Shaking,Tree-Shaking带来的bundle体积更小是显而易见的。在2.x版本中,很多函数都挂载在全局Vue对象上,比如nextTick、nextTick、nextTick、set等函数,因此虽然我们可能用不到,但打包时只要引入了vue这些全局函数仍然会打包进bundle中。

而在Vue3中,所有的API都通过ES6模块化的方式引入,这样就能让webpack或rollup等打包工具在打包时对没有用到API进行剔除,最小化bundle体积;我们在main.js中就能发现这样的变化:

  1. //src/main.js
  2. import { createApp } from “vue”;
  3. import App from “./App.vue”;
  4. import router from “./router”;
  5. const app = createApp(App);
  6. app.use(router).mount(“#app”);

 

创建app实例方式从原来的new Vue()变为通过createApp函数进行创建;不过一些核心的功能比如virtualDOM更新算法和响应式系统无论如何都是会被打包的;这样带来的变化就是以前在全局配置的组件(Vue.component)、指令(Vue.directive)、混入(Vue.mixin)和插件(Vue.use)等变为直接挂载在实例上的方法;我们通过创建的实例来调用,带来的好处就是一个应用可以有多个Vue实例,不同实例之间的配置也不会相互影响:

  1. const app = createApp(App)
  2. app.use(/* … */)
  3. app.mixin(/* … */)
  4. app.component(/* … */)
  5. app.directive(/* … */)

 

因此Vue2.x的以下全局API也需要改为ES6模块化引入:

  • Vue.nextTick
  • Vue.observable不再支持,改为reactive
  • Vue.version
  • Vue.compile (仅全构建)
  • Vue.set (仅兼容构建)
  • Vue.delete (仅兼容构建)

除此之外,vuex和vue-router也都使用了Tree-Shaking进行了改进,不过api的语法改动不大:

  1. //src/store/index.js
  2. import { createStore } from “vuex”;
  3. export default createStore({
  4.   state: {},
  5.   mutations: {},
  6.   actions: {},
  7.   modules: {},
  8. });
  9. //src/router/index.js
  10. import { createRouter, createWebHistory } from “vue-router”;
  11. const router = createRouter({
  12.   historycreateWebHistory(process.env.BASE_URL),
  13.   routes,
  14. });

 

生命周期函数

我们都知道,在Vue2.x中有8个生命周期函数:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

在vue3中,新增了一个setup生命周期函数,setup执行的时机是在beforeCreate生命函数之前执行,因此在这个函数中是不能通过this来获取实例的;同时为了命名的统一,将beforeDestroy改名为beforeUnmountdestroyed改名为unmounted,因此vue3有以下生命周期函数:

  • beforeCreate(建议使用setup代替)
  • created(建议使用setup代替)
  • setup
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

同时,vue3新增了生命周期钩子,我们可以通过在生命周期函数前加on来访问组件的生命周期,我们可以使用以下生命周期钩子:

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • onErrorCaptured
  • onRenderTracked
  • onRenderTriggered

那么这些钩子函数如何来进行调用呢?我们在setup中挂载生命周期钩子,当执行到对应的生命周期时,就调用对应的钩子函数:

  1. import { onBeforeMount, onMounted } from “vue”;
  2. export default {
  3.   setup() {
  4.     console.log(“—-setup—-“);
  5.     onBeforeMount(() => {
  6.       // beforeMount代码执行
  7.     });
  8.     onMounted(() => {
  9.       // mounted代码执行
  10.     });
  11.   },
  12. }

 

新增的功能

说完生命周期,下面就是我们期待的Vue3新增加的那些功能。

响应式API

我们可以使用reactive来为JS对象创建响应式状态:

  1. import { reactive, toRefs } from “vue”;
  2. const user = reactive({
  3.   name‘Vue2’,
  4.   age18,
  5. });
  6. user.name = ‘Vue3’

 

reactive相当于Vue2.x中的Vue.observable

reactive函数只接收object和array等复杂数据类型。

对于一些基本数据类型,比如字符串和数值等,我们想要让它变成响应式,我们当然也可以通过reactive函数创建对象的方式,但是Vue3提供了另一个函数ref

  1. import { ref } from “vue”;
  2. const num = ref(0);
  3. const str = ref(“”);
  4. const male = ref(true);
  5. num.value++;
  6. console.log(num.value);
  7. str.value = “new val”;
  8. console.log(str.value);
  9. male.value = false;
  10. console.log(male.value);

 

ref返回的响应式对象是只包含一个名为value参数的RefImpl对象,在js中获取和修改都是通过它的value属性;但是在模板中被渲染时,自动展开内部的值,因此不需要在模板中追加.value

  1. <template>
  2. <div>
  3. <span>{{ count }}</span>
  4. <button @click=“count ++”>Increment count</button>
  5. </div>
  6. </template>
  7. <script>
  8. import { ref } from ‘vue’
  9. export default {
  10. setup() {
  11. const count = ref(0)
  12. return {
  13. count
  14. }
  15. }
  16. }
  17. </script>

 

reactive主要负责复杂数据结构,而ref主要处理基本数据结构;但是很多童鞋就会误解ref只能处理基本数据,ref本身也是能处理对象和数组的:

  1. import { ref } from “vue”;
  2. const obj = ref({
  3.   name: “qwe”,
  4.   age: 1,
  5. });
  6. setTimeout(() => {
  7.   obj.value.name = “asd”;
  8. }, 1000);
  9. const list = ref([12346]);
  10. setTimeout(() => {
  11.   list.value.push(7);
  12. }, 2000);

 

当我们处理一些大型响应式对象的property时,我们很希望使用ES6的解构来获取我们想要的值:

  1. let book = reactive({
  2.   name‘Learn Vue’,
  3.   year2020,
  4.   title‘Chapter one’
  5. })
  6. let {
  7.   name,
  8. } = book
  9. name = ‘new Learn’
  10. // Learn Vue
  11. console.log(book.name);

 

但是很遗憾,这样会消除它的响应式;对于这种情况,我们可以将响应式对象转换为一组ref,这些ref将保留与源对象的响应式关联:

  1. let book = reactive({
  2.   name‘Learn Vue’,
  3.   year2020,
  4.   title‘Chapter one’
  5. })
  6. let {
  7.   name,
  8. } = toRefs(book)
  9. // 注意这里解构出来的name是ref对象
  10. // 需要通过value来取值赋值
  11. name.value = ‘new Learn’
  12. // new Learn
  13. console.log(book.name);

 

对于一些只读数据,我们希望防止它发生任何改变,可以通过readonly来创建一个只读的对象:

  1. import { reactive, readonly } from “vue”;
  2. let book = reactive({
  3.   name‘Learn Vue’,
  4.   year2020,
  5.   title‘Chapter one’
  6. })
  7. const copy = readonly(book);
  8. //Set operation on key “name” failed: target is readonly.
  9. copy.name = “new copy”;

 

有时我们需要的值依赖于其他值的状态,在vue2.x中我们使用computed函数来进行计算属性,在vue3中将computed功能进行了抽离,它接受一个getter函数,并为getter返回的值创建了一个不可变的响应式ref对象:

  1. const num = ref(0);
  2. const double = computed(() => num.value * 2);
  3. num.value++;
  4. // 2
  5. console.log(double.value);
  6. // Warning: computed value is readonly
  7. double.value = 4

 

或者我们也可以使用get和set函数创建一个可读写的ref对象:

  1. const num = ref(0);
  2. const double = computed({
  3.   get: () => num.value * 2,
  4.   set: (val) => (num.value = val / 2),
  5. });
  6. num.value++;
  7. // 2
  8. console.log(double.value);
  9. double.value = 8
  10. // 4
  11. console.log(num.value);

 

响应式侦听

和computed相对应的就是watch,computed是多对一的关系,而watch则是一对多的关系;vue3也提供了两个函数来侦听数据源的变化:watch和watchEffect。

我们先来看下watch,它的用法和组件的watch选项用法完全相同,它需要监听某个数据源,然后执行具体的回调函数,我们首先看下它监听单个数据源的用法:

  1. import { reactive, ref, watch } from “vue”;
  2. const state = reactive({
  3.   count0,
  4. });
  5. //侦听时返回值得getter函数
  6. watch(
  7.   () => state.count,
  8.   (count, prevCount) => {
  9.     // 1 0
  10.     console.log(count, prevCount);
  11.   }
  12. );
  13. state.count++;
  14. const count = ref(0);
  15. //直接侦听ref
  16. watch(count, (count, prevCount) => {
  17.   // 2 0
  18.   console.log(count, prevCount, “watch”);
  19. });
  20. count.value = 2;

 

我们也可以把多个值放在一个数组中进行侦听,最后的值也以数组形式返回:

  1. const state = reactive({
  2.   count1,
  3. });
  4. const count = ref(2);
  5. watch([() => state.count, count], (newVal, oldVal) => {
  6.   //[3, 2]  [1, 2]
  7.   //[3, 4]  [3, 2]
  8.   console.log(newVal, oldVal);
  9. });
  10. state.count = 3;
  11. count.value = 4;

 

如果我们来侦听一个深度嵌套的对象属性变化时,需要设置deep:true

  1. const deepObj = reactive({
  2.   a: {
  3.     b: {
  4.       c: “hello”,
  5.     },
  6.   },
  7. });
  8. watch(
  9.   () => deepObj,
  10.   (val, old) => {
  11.     // new hello new hello
  12.     console.log(val.a.b.c, old.a.b.c);
  13.   },
  14.   { deep: true }
  15. );
  16. deepObj.a.b.c = “new hello”;

 

最后的打印结果可以发现都是改变后的值,这是因为侦听一个响应式对象始终返回该对象的引用,因此我们需要对值进行深拷贝:

  1. import _ from “lodash”;
  2. const deepObj = reactive({
  3.   a: {
  4.     b: {
  5.       c: “hello”,
  6.     },
  7.   },
  8. });
  9. watch(
  10.   () => _.cloneDeep(deepObj),
  11.   (val, old) => {
  12.     // new hello hello
  13.     console.log(val.a.b.c, old.a.b.c);
  14.   },
  15.   { deep: true }
  16. );
  17. deepObj.a.b.c = “new hello”;

 

一般侦听都会在组件销毁时自动停止,但是有时候我们想在组件销毁前手动的方式进行停止,可以调用watch返回的stop函数进行停止:

  1. const count = ref(0);
  2. const stop = watch(count, (count, prevCount) => {
  3.   // 不执行
  4.   console.log(count, prevCount);
  5. });
  6. setTimeout(()=>{
  7.   count.value = 2;
  8. }, 1000);
  9. // 停止watch
  10. stop();

 

还有一个函数watchEffect也可以用来进行侦听,但是都已经有watch了,这个watchEffect和watch有什么区别呢?他们的用法主要有以下几点不同:

  1. watchEffect不需要手动传入依赖
    
  2. 每次初始化时watchEffect都会执行一次回调函数来自动获取依赖
    
  3. watchEffect无法获取到原值,只能得到变化后的值
    
  1. import { reactive, ref, watch, watchEffect } from “vue”;
  2. const count = ref(0);
  3. const state = reactive({
  4.   year2021,
  5. });
  6. watchEffect(() => {
  7.   console.log(count.value);
  8.   console.log(state.year);
  9. });
  10. setInterval(() => {
  11.   count.value++;
  12.   state.year++;
  13. }, 1000);

 

watchEffect会在页面加载时自动执行一次,追踪响应式依赖;在加载后定时器每隔1s执行时,watchEffect都会监听到数据的变化自动执行,每次执行都是获取到变化后的值。

组合API

Composition API(组合API)也是Vue3中最重要的一个功能了,之前的2.x版本采用的是Options API(选项API),即官方定义好了写法:data、computed、methods,需要在哪里写就在哪里写,这样带来的问题就是随着功能增加,代码也越来复杂,我们看代码需要上下反复横跳:

图片

Composition API对比

上图中,一种颜色代表一个功能,我们可以看到Options API的功能代码比较分散;Composition API则可以将同一个功能的逻辑,组织在一个函数内部,利于维护。

我们首先来看下之前Options API的写法:

  1. export default {
  2.   components: {},
  3.   data() {},
  4.   computed: {},
  5.   watch: {},
  6.   mounted() {},
  7. }

 

Options API就是将同一类型的东西放在同一个选项中,当我们的数据比较少的时候,这样的组织方式是比较清晰的;但是随着数据增多,我们维护的功能点会涉及到多个data和methods,但是我们无法感知哪些data和methods是需要涉及到的,经常需要来回切换查找,甚至是需要理解其他功能的逻辑,这也导致了组件难以理解和阅读。

Composition API做的就是把同一功能的代码放到一起维护,这样我们需要维护一个功能点的时候,不用去关心其他的逻辑,只关注当前的功能;Composition API通过setup选项来组织代码:

  1. export default {
  2.   setup(props, context) {}
  3. };

 

我们看到这里它接收了两个参数props和context,props就是父组件传入的一些数据,context是一个上下文对象,是从2.x暴露出来的一些属性:

  • attrs
  • slots
  • emit

注:props的数据也需要通过toRefs解构,否则响应式数据会失效。

我们通过一个Button按钮来看下setup具体的用法:

图片

举个栗子

  1. <template>
  2. <div>{{ state.count }} * 2 = {{ double }}</div>
  3. <div>{{ num }}</div>
  4. <div @click=“add”>Add</div>
  5. </template>
  6. <script>
  7. import { reactive, computed, ref } from “vue”;
  8. export default {
  9. name: “Button”,
  10. setup() {
  11. const state = reactive({
  12. count: 1,
  13. });
  14. const num = ref(2);
  15. function add() {
  16. state.count++;
  17. num.value += 10;
  18. }
  19. const double = computed(() => state.count * 2);
  20. return {
  21. state,
  22. double,
  23. num,
  24. add,
  25. };
  26. },
  27. };
  28. </script>

 

很多童鞋可能就有疑惑了,这跟我在data和methods中写没什么区别么,不就是把他们放到一起么?我们可以将setup中的功能进行提取分割成一个一个独立函数,每个函数还可以在不同的组件中进行逻辑复用:

  1. export default {
  2.   setup() {
  3.     const { networkState } = useNetworkState();
  4.     const { user } = userDeatil();
  5.     const { list } = tableData();
  6.     return {
  7.       networkState,
  8.       user,
  9.       list,
  10.     };
  11.   },
  12. };
  13. function useNetworkState() {}
  14. function userDeatil() {}
  15. function tableData() {}

 

Fragment

所谓的Fragment,就是片段;在vue2.x中,要求每个模板必须有一个根节点,所以我们代码要这样写:

  1. <template>
  2.   <div>
  3.     <span></span>
  4.     <span></span>
  5.   </div>
  6. </template>

 

或者在Vue2.x中还可以引入vue-fragments库,用一个虚拟的fragment代替div;在React中,解决方法是通过的一个React.Fragment标签创建一个虚拟元素;在Vue3中我们可以直接不需要根节点:

  1. <template>
  2.     <span>hello</span>
  3.     <span>world</span>
  4. </template>

 

这样就少了很多没有意义的div元素。

Teleport

Teleport翻译过来就是传送、远距离传送的意思;顾名思义,它可以将插槽中的元素或者组件传送到页面的其他位置:

图片

传送门游戏

在React中可以通过createPortal函数来创建需要传送的节点;本来尤大大想起名叫Portal,但是H5原生的Portal标签也在计划中,虽然有一些安全问题,但是为了避免重名,因此改成Teleport

Teleport一个常见的使用场景,就是在一些嵌套比较深的组件来转移模态框的位置。虽然在逻辑上模态框是属于该组件的,但是在样式和DOM结构上,嵌套层级后较深后不利于进行维护(z-index等问题);因此我们需要将其进行剥离出来:

  1. <template>
  2. <button @click=“showDialog = true”>打开模态框</button>
  3. <teleport to=“body”>
  4. <div class=“modal” v-if=“showDialog” style=“position: fixed”>
  5. 我是一个模态框
  6. <button @click=“showDialog = false”>关闭</button>
  7. <child-component :msg=“msg”></child-component>
  8. </div>
  9. </teleport>
  10. </template>
  11. <script>
  12. export default {
  13. data() {
  14. return {
  15. showDialog: false,
  16. msg: “hello”
  17. };
  18. },
  19. };
  20. </script>

 

这里的Teleport中的modal div就被传送到了body的底部;虽然在不同的地方进行渲染,但是Teleport中的元素和组件还是属于父组件的逻辑子组件,还是可以和父组件进行数据通信。Teleport接收两个参数todisabled

  • to – string:必须是有效的查询选择器或 HTMLElement,可以id或者class选择器等。
  • disabled – boolean:如果是true表示禁用teleport的功能,其插槽内容将不会移动到任何位置,默认false不禁用。

Suspense

Suspense是Vue3推出的一个内置组件,它允许我们的程序在等待异步组件时渲染一些后备的内容,可以让我们创建一个平滑的用户体验;Vue中加载异步组件其实在Vue2.x中已经有了,我们用的vue-router中加载的路由组件其实也是一个异步组件:

  1. export default {
  2.   name: “Home”,
  3.   components: {
  4.     AsyncButton: () => import(“../components/AsyncButton”),
  5.   },
  6. }

 

在Vue3中重新定义,异步组件需要通过defineAsyncComponent来进行显示的定义:

  1. // 全局定义异步组件
  2. //src/main.js
  3. import { defineAsyncComponent } from “vue”;
  4. const AsyncButton = defineAsyncComponent(() =>
  5.   import(“./components/AsyncButton.vue”)
  6. );
  7. app.component(“AsyncButton”, AsyncButton);
  8. // 组件内定义异步组件
  9. // src/views/Home.vue
  10. import { defineAsyncComponent } from “vue”;
  11. export default {
  12.   components: {
  13.     AsyncButton: defineAsyncComponent(() =>
  14.       import(“../components/AsyncButton”)
  15.     ),
  16.   },
  17. };

 

同时对异步组件的可以进行更精细的管理:

  1. export default {
  2.   components: {
  3.     AsyncButton: defineAsyncComponent({
  4.       delay: 100,
  5.       timeout: 3000,
  6.       loader: () => import(“../components/AsyncButton”),
  7.       errorComponent: ErrorComponent,
  8.       onError(error, retry, fail, attempts) {
  9.         if (attempts <= 3) {
  10.           retry();
  11.         } else {
  12.           fail();
  13.         }
  14.       },
  15.     }),
  16.   },
  17. };

 

这样我们对异步组件加载情况就能掌控,在加载失败也能重新加载或者展示异常的状态:

图片

异步组件加载失败

我们回到Suspense,上面说到它主要是在组件加载时渲染一些后备的内容,它提供了两个slot插槽,一个default默认,一个fallback加载中的状态:

  1. <template>
  2. <div>
  3. <button @click=“showButton”>展示异步组件</button>
  4. <template v-if=“isShowButton”>
  5. <Suspense>
  6. <template #default>
  7. <AsyncButton></AsyncButton>
  8. </template>
  9. <template #fallback>
  10. <div>组件加载中…</div>
  11. </template>
  12. </Suspense>
  13. </template>
  14. </div>
  15. </template>
  16. <script>
  17. export default {
  18. setup() {
  19. const isShowButton = ref(false);
  20. function showButton() {
  21. isShowButton.value = true;
  22. }
  23. return {
  24. isShowButton,
  25. showButton,
  26. };
  27. },
  28. }
  29. </script>

 

图片

异步组件加载显示占位

非兼容的功能

非兼容的功能主要是一些和Vue2.x版本改动较大的语法,已经在Vue3上可能存在兼容问题了。

data、mixin和filter

在Vue2.x中,我们可以定义data为object或者function,但是我们知道在组件中如果data是object的话会出现数据互相影响,因为object是引用数据类型;

在Vue3中,data只接受function类型,通过function返回对象;同时Mixin的合并行为也发生了改变,当mixin和基类中data合并时,会执行浅拷贝合并:

  1. const Mixin = {
  2.   data() {
  3.     return {
  4.       user: {
  5.         name‘Jack’,
  6.         id1,
  7.         address: {
  8.           prov2,
  9.           city3,
  10.         },
  11.       }
  12.     }
  13.   }
  14. }
  15. const Component = {
  16.   mixins: [Mixin],
  17.   data() {
  18.     return {
  19.       user: {
  20.         id2,
  21.         address: {
  22.           prov4,
  23.         },
  24.       }
  25.     }
  26.   }
  27. }
  28. // vue2结果:
  29. {
  30.   id2,
  31.   name‘Jack’,
  32.   address: {
  33.     prov4,
  34.     city3
  35.   }
  36. }
  37. // vue3结果:
  38. user: {
  39.   id2,
  40.   address: {
  41.     prov4,
  42.   },
  43. }

 

我们看到最后合并的结果,vue2.x会进行深拷贝,对data中的数据向下深入合并拷贝;而vue3只进行浅层拷贝,对data中数据发现已存在就不合并拷贝。

在vue2.x中,我们还可以通过过滤器filter来处理一些文本内容的展示:

  1. <template>
  2. <div>{{ status | statusText }}</div>
  3. </template>
  4. <script>
  5. export default {
  6. props: {
  7. status: {
  8. type: Number,
  9. default: 1
  10. }
  11. },
  12. filters: {
  13. statusText(value){
  14. if(value === 1){
  15. return ‘订单未下单’
  16. } else if(value === 2){
  17. return ‘订单待支付’
  18. } else if(value === 3){
  19. return ‘订单已完成’
  20. }
  21. }
  22. }
  23. }
  24. </script>

 

最常见的就是处理一些订单的文案展示等;然而在vue3中,过滤器filter已经删除,不再支持了,官方建议使用方法调用或者计算属性computed来进行代替。

v-model

在Vue2.x中,v-model相当于绑定value属性和input事件,它本质也是一个语法糖:

  1. <child-component v-model=“msg”></child-component>
  2. <!– 相当于 –>
  3. <child-component :value=“msg” @input=“msg=$event”></child-component>

 

在某些情况下,我们需要对多个值进行双向绑定,其他的值就需要显示的使用回调函数来改变了:

  1. <child-component
  2.     v-model=“msg”
  3.     :msg1=“msg1”
  4.     @change1=“msg1=$event
  5.     :msg2=“msg2”
  6.     @change2=“msg2=$event>
  7. </child-component>

 

在vue2.3.0+版本引入了.sync修饰符,其本质也是语法糖,是在组件上绑定@update:propName回调,语法更简洁:

  1. <child-component 
  2.     :msg1.sync=“msg1”
  3.     :msg2.sync=“msg2”>
  4. </child-component>
  5. <!– 相当于 –>
  6. <child-component 
  7.     :msg1=“msg1”
  8.     @update:msg1=“msg1=$event”
  9.     :msg2=“msg2”
  10.     @update:msg2=“msg2=$event”>
  11. </child-component>

 

Vue3中将v-model.sync进行了功能的整合,抛弃了.sync,表示:多个双向绑定value值直接用多个v-model传就好了;同时也将v-model默认传的prop名称由value改成了modelValue:

  1. <child-component 
  2.     v-model=“msg”>
  3. </child-component>
  4. <!– 相当于 –>
  5. <child-component 
  6.   :modelValue=“msg”
  7.   @update:modelValue=“msg = $event”>
  8. </child-component>

 

如果我们想通过v-model传递多个值,可以将一个argument传递给v-model:

  1. <child-component 
  2.     v-model.msg1=“msg1”
  3.     v-model.msg2=“msg2”>
  4. </child-component>
  5. <!– 相当于 –>
  6. <child-component 
  7.     :msg1=“msg1”
  8.     @update:msg1=“msg1=$event”
  9.     :msg2=“msg2”
  10.     @update:msg2=“msg2=$event”>
  11. </child-component>

 

v-for和key

在Vue2.x中,我们都知道v-for每次循环都需要给每个子节点一个唯一的key,还不能绑定在template标签上,

  1. <template v-for=“item in list”>
  2.   <div :key=“item.id”></div>
  3.   <span :key=“item.id”></span>
  4. </template>

 

而在Vue3中,key值应该被放置在template标签上,这样我们就不用为每个子节点设一遍:

  1. <template v-for=“item in list” :key=“item.id”>
  2.   <div></div>
  3.   <span></span>
  4. </template>

 

v-bind合并

在vue2.x中,如果一个元素同时定义了v-bind="object"和一个相同的单独的属性,那么这个单独的属性会覆盖object中的绑定:

  1. <div id=“red” v-bind=“{ id: ‘blue’ }”></div>
  2. <div v-bind=“{ id: ‘blue’ }” id=“red”></div>
  3. <!– 最后结果都相同 –>
  4. <div id=“red”></div>

 

然而在vue3中,如果一个元素同时定义了v-bind="object"和一个相同的单独的属性,那么声明绑定的顺序决定了最后的结果(后者覆盖前者):

  1. <!– template –>
  2. <div id=“red” v-bind=“{ id: ‘blue’ }”></div>
  3. <!– result –>
  4. <div id=“blue”></div>
  5. <!– template –>
  6. <div v-bind=“{ id: ‘blue’ }” id=“red”></div>
  7. <!– result –>
  8. <div id=“red”></div>

 

v-for中ref

vue2.x中,在v-for上使用ref属性,通过this.$refs会得到一个数组:

  1. <template
  2. <div v-for=“item in list” :ref=“setItemRef”></div>
  3. </template>
  4. <script>
  5. export default {
  6. data(){
  7. list: [1, 2]
  8. },
  9. mounted () {
  10. // [div, div]
  11. console.log(this.$refs.setItemRef)
  12. }
  13. }
  14. </script>

 

但是这样可能不是我们想要的结果;因此vue3不再自动创建数组,而是将ref的处理方式变为了函数,该函数默认传入该节点:

  1. <template
  2. <div v-for=“item in 3” :ref=“setItemRef”></div>
  3. </template>
  4. <script>
  5. import { reactive, onUpdated } from ‘vue’
  6. export default {
  7. setup() {
  8. let itemRefs = reactive([])
  9. const setItemRef = el => {
  10. itemRefs.push(el)
  11. }
  12. onUpdated(() => {
  13. console.log(itemRefs)
  14. })
  15. return {
  16. itemRefs,
  17. setItemRef
  18. }
  19. }
  20. }
  21. </script>

 

v-for和v-if优先级

在vue2.x中,在一个元素上同时使用v-for和v-if,v-for有更高的优先级,因此在vue2.x中做性能优化,有一个重要的点就是v-for和v-if不能放在同一个元素上。

而在vue3中,v-ifv-for有更高的优先级。因此下面的代码,在vue2.x中能正常运行,但是在vue3中v-if生效时并没有item变量,因此会报错:

  1. <template>
  2. <div v-for=“item in list” v-if=“item % 2 === 0” :key=“item”>{{ item }}</div>
  3. </template>
  4. <script>
  5. export default {
  6. data() {
  7. return {
  8. list: [1, 2, 3, 4, 5],
  9. };
  10. },
  11. };
  12. </script>

 

总结

以上就是Vue3.0作为终端用的我们可能会涉及到的一些新特性和新功能,其实Vue3.0还有很多的改动,这里由于篇幅原因就不一一展开了,大家可以自行查阅官方文档,期待Vue3能带给我们更便利更友好的开发体验。


滴石it网-Java学习中高级和架构师教程_Java企业级开发项目实战下载 » 全面总结 Vue 3.0 的新特性 Vue 3.0 新特性及使用方法

常见问题FAQ

发表评论

开通VIP 享更多特权,建议使用QQ登录