Vue学习笔记(二)

179 阅读14分钟

七、Vue3

1、创建Vue3工程

(1)使用vue-cli创建

输入命令vue create vue3_test即可创建名为vue3_test的项目

(2)使用vite创建

输入命令npm init vite-app vue3_test_vite即可创建名为vue3_test_vite的项目

注意: 使用vite创建的项目没有安装依赖,需要后续手动安装。

2、Vue3工程结构

(1)main.js文件
 import { createApp } from 'vue'
 import App from './App.vue'
 ​
 createApp(App).mount('#app')

在Vue3中不再引入Vue构造函数,而是引入一个名为createApp的工厂函数

通过create(App)会创建应用实例对象,类似于Vue2中的vm,但是会比vm更轻,其内部结构如下所示:

image-20220115123650234

(2)App.vue文件

在Vue3的模板中不再需要将结构写在一个根标签中了

 <template>
   <img alt="Vue logo" src="./assets/logo.png">
   <HelloWorld msg="Welcome to Your Vue.js App"/>
 </template>

3、常用的Composition API

(1)setup

setup是Vue3中的一个新的配置项,值为一个函数。为所有Composition API提供了基础。组件中用到的数据、方法等,都要配置在setup中。

setup函数的两种返回值

  • 返回对象

    如果返回一个对象,则对象中的属性和方法在模板中都可以直接使用

     <template>
       <h2>姓名:{{name}}</h2>
       <h2>年龄:{{age}}</h2>
       <button @click="sayHello">点我说话</button>
     </template>
     ​
     <script>
     ​
     export default {
       name: 'App',
       setup() {
         let name = "dexter";
         let age = 18;
         function sayHello() {
           alert(`我叫${name},我${age}岁了`)
         }
         return {
           name,
           age,
           sayHello
         }
       }
     }
     </script>
    
  • 返回渲染函数

    如果返回一个渲染函数,则可以自定义渲染的内容

     <template>
       <h2>姓名:{{name}}</h2>
       <h2>年龄:{{age}}</h2>
     </template>
     ​
     <script>
       import { h } from "vue";
     ​
       export default {
         name: 'App',
         setup() {
           let name = "dexter";
           let age = 18;
           return () => h("h2", "hello")
         }
       }
     </script>
    

    备注: 如果返回了一个渲染函数,则渲染函数中的内容会替换掉模板中的内容。

注意:

  • 尽量不要与Vue2.x配置混用。如果混用,则Vue2.x的配置(data、methods、computed...)中可以访问到setup中的属性和方法,但是setup中不能访问到Vue2.x中的配置;如果两者在命名上出现了冲突,会优先使用setup中的配置。
  • setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性了。(后期也可以返回一个Promise实例,但是需要Suspense和异步组件的配合)
(2)ref函数

ref函数的作用是定义一个响应式的数据。

语法const xxx = ref(initValue),创建一个包含响应式的引用对象,使用js操作数据时要使用xxx.value来获取,但是在模板中读取数据不需要.value,直接使用原始数据即可。

备注: ref接收的数据可以是基本类型、也可以是对象类型,基本数据类型的响应式依然是靠Object.defineProperty()getset实现的,所以在获取数据时需要使用.value;而对象类型的数据内部求助了Vue3.0中的一个新的函数reactive,所以在使用时只需要写一次.value即可获取到对应的数据,对于对象内部的数据,不需要层层.value获取。

 <template>
   <h1>hello</h1>
   <h2>姓名:{{name}}</h2>
   <h2>年龄:{{age}}</h2>
   <h2>工作:{{job.type}},薪资{{job.salary}}</h2>
   <button @click="changeInfo">修改人的信息</button>
 </template>
 ​
 <script>
   import { ref } from "vue";
 ​
   export default {
     name: 'App',
     setup() {
       let name = ref("dexter");
       let age = ref(18);
       let job = ref({ type: "front end", salary: "30K" });
       function changeInfo() {
         name.value = "emma";
         job.value.type = "java";
         console.log(name, age, job);
       }
       return { name, age, job, changeInfo }
     }
   }
 </script>

image-20220115175814720

(3)reactive函数

reactive函数的作用是定义一个对象类型的响应式数据(基本数据类型不能使用reactive,要使用ref),语法const 代理对象 = reactive(源对象)reactive函数接收一个对象或数组,返回一个代理对象(proxy对象)。

reactive定义的响应式数据是深层次的,内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据。

 let job = reactive({
   type: "front end",
   salary: "30K"
 });
 function changeInfo() {
   name.value = "emma";
   job.type = "java";
 }

changeInfo中输出job可以看到其就是一个Proxy对象

image-20220115175814720

(4)Vue3的响应式原理

Vue2中存在的问题是:新增属性、删除属性,界面不会更新;直接通过下标修改数组时,界面不会更新。必须通过Vue提供的$set$delete等方法才能够操作数据。

Vue3中通过reactive函数解决了上述问题,将对象写在该函数中,后续的任意操作都可以正常显示。

原生Proxy的使用

 let person = {
   name: "dexter",
   age: 18
 };
 let p = new Proxy(person, {
   //当有人读取了p身上的某个属性时调用
   get(target, propName) {
     console.log(`有人读取了p身上的${propName}属性`);
     return target[propName]
   },
   //当有人修改p的某个属性、或者给p追加某个属性时调用
   set(target, propName, value) {
     console.log(`有人修改了p身上的${propName}属性`);
     target[propName] = value;
   },
   //当有人删除了p身上的某个属性时调用
   deleteProperty(target, propName) {
     console.log(`有人删除了p身上的${propName}属性`);
     return delete target[propName]
   }
 });

通过Proxy代理,拦截对象中任意属性变化,包括属性的读写,属性的添加和删除等。

reflect是一个js内置的对象,它提供拦截js操作的方法。静态方法Reflect.defineProperty()基本等同于 Object.defineProperty() 方法,Object.defineProperty方法如果成功则返回一个对象,否则抛出一个 TypeError 。另外,当定义一个属性时,你也可以使用 try...catch去捕获其中任何的错误。而因为 Reflect.defineProperty返回 Boolean 值作为成功的标识,所以只能使用 if...else

 let person = {
   name: "dexter",
   age: 18
 };
 const x = Reflect.defineProperty(person, "sex", {
   value: "male"
 });
 if(x) {
   console.log("添加成功");
 }else {
   console.log("添加失败");
 }

vue3中的响应式如下所示:

 let person = {
   name: "dexter",
   age: 18
 };
 let p = new Proxy(person, {
   //当有人读取了p身上的某个属性时调用
   get(target, propName) {
     console.log(`有人读取了p身上的${propName}属性`);
     return Reflect.get(target, propName)
   },
   //当有人修改p的某个属性、或者给p追加某个属性时调用
   set(target, propName, value) {
     console.log(`有人修改了p身上的${propName}属性`);
     return Reflect.set(target, propName, value)
   },
   //当有人删除了p身上的某个属性时调用
   deleteProperty(target, propName) {
     console.log(`有人删除了p身上的${propName}属性`);
     return Reflect.deleteProperty(target, propName)
   }
 });

在控制台中的操作展示

image-20220116000247631

(5)reactive与ref的对比
  • 从定义数据角度对比
    • ref用来定义基本数据类型(ref也可以用来定义对象或数组类型数据,但是它内部会自动通过reactive转化为代理对象)
    • reactive用来定义对象或数组类型数据
  • 从原理角度对比
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)
    • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
  • 从使用角度对比
    • ref定义的数据操作时需要使用.value,读取数据时模板中可以直接读取
    • reactive定义的数据操作与读取数据都不需要.value
(6)setup的两个注意点
  • 执行时机

    setup会在beforeCreate生命周期执行之前执行一次;setupthisundefined

  • 参数
    • props

      值为对象,包含组件外部传递过来且组件内部声明接收了的属性

      image-20220116004023574

    • context

      上下文对象

      image-20220116004134687

      • attrs的值为对象,包含组件传递过来但是没有在props配置中声明的属性,相当于vue2中的this.$attrs
      • slots表示收到的插槽内容,相当于vue2中的this.$slots
      • emit是分发自定义事件的函数,相当于vue2中的this.$emit

父组件编码:

 <template>
   <Demo @hello="showHelloMsg" msg="hello" school="hebeu">
     <template v-slot:nihao>
       <span>hello</span>
     </template>
   </Demo>
 </template>
 ​
 <script>
   import Demo from "./components/Demo.vue";
 ​
   export default {
     name: 'App',
     components: {Demo},
     setup() {
       function showHelloMsg(value) {
         console.log(`收到了参数${value}`);
       }
       return {showHelloMsg}
     }
   }
 </script>

子组件编码

 <template>
   <h2>姓名:{{person.name}}</h2>
   <h2>年龄:{{person.age}}</h2>
   <button @click="test">触发自定义事件</button>
 </template>
 ​
 <script>
   import { reactive } from "vue";
 ​
   export default {
     name: "Demo",
     props: ["msg", "school"],
     emits: ["hello"],
     setup(props, context) {
       let person = reactive({
         name: "dexter",
         age: 18
       });
       function test() {
         context.emit("hello", "how are you")
       }
       return { person, test }
     }
   }
 </script>
(7)计算属性
  • computed函数

    与vue2中的computed函数的配置功能一致,但是需要提前引入

       import { reactive, computed } from "vue";
     ​
       export default {
         name: "Demo",
         setup() {
           let person = reactive({
             firstName: "dexter",
             lastName: "jun"
           });
           person.fullName = computed({
             get() {
               return person.firstName + "-" + person.lastName;
             },
             set(value) {
               person.firstName = value.split("-")[0];
               person.lastName = value.split("-")[1];
             }
           });
           return { person }
         }
       }
    

    如果只需要读取数据,可以使用简写形式

     person.fullName = computed(() => {
       return person.firstName + "-" + person.lastName;
     });
    
  • watch函数

    setup中定义了如下数据

     let sum = ref(0);
     let msg = ref("hello");
     let person = reactive({
       name: "dexter",
       age: 18,
       job: {
         j1: {
           salary: 20
         }
       }
     });
    
    • 情况一:监视ref所定义的一个响应式数据

       watch(sum, (newValue, oldValue) => {
         console.log("sum的值发生变化了", newValue, oldValue);
       })
      

      数据改变时控制台输出:

      image-20220116121153125

    • 情况二:监视ref所定义的多个响应式数据

       watch([sum, msg], (newValue, oldValue) => {
         console.log("sum或msg发生变化了", newValue, oldValue);
       }, {immediate: true})
      

      数据改变时控制台输出:

      image-20220116121520195

    • 情况三:监视reactive所定义的一个响应式数据

       watch(person, (newValue, oldValue) => {
         console.log("person发生变化了", newValue, oldValue);
       })
      

      当person中的数据发生改变时watch会监测到,但是值得注意的是这里只能监测到newValue的值,并不能监测到oldValue的值;且默认强制开启了深度监视,即使配置deep: false也没用,数据变化时控制台输出:

      image-20220116122256061

    • 情况四:监视reactive所定义的一个响应式数据中的某个属性

       watch(() => person.job.j1.salary, (newValue, oldValue) => {
         console.log("薪资变化了", newValue, oldValue);
       })
      

      数据变化时控制台输出:

      image-20220116122520023

    • 情况五:监视reactive所定义的一个响应式数据中的多个属性

       watch([() => person.name, () => person.job.j1.salary], (newValue, oldValue) => {
         console.log("薪资变化了", newValue, oldValue);
       })
      

      数据变化时控制台输出:

      image-20220116122745771

    • 特殊情况

       watch(() => person.job, (newValue, oldValue) => {
         console.log("job变化了", newValue, oldValue);
       }, {deep: true})
      

      此处由于监视的是reactive所定义的对象中的某个属性,所以deep配置有效,数据改变时控制台输出:

      image-20220116123043512

    • 关于.value的问题

       setup() {
         let sum = ref(0);
         let msg = ref("hello");
         let person = ref({
           name: "dexter",
           age: 18,
           job: {
             j1: {
               salary: 20
             }
           }
         });
         console.log(sum);
         console.log(person);
         watch(sum, (newValue, oldValue) => {
           console.log("sum发生变化了", newValue, oldValue);
         })
         watch(person.value, (newValue, oldValue) => {
           console.log("person发生变化了", newValue, oldValue);
         })
       }
      

      从下图中可以看到通过ref所定义的数据会被包装成一个RefImpl对象,基本数据类型会直接作为value属性的值存储,引用数据类型会被reactive包装成一个Proxy对象再交给value保存。因此在使用watch函数监视ref绑定的基本数据类型时不需要使用.value,而监视引用数据类型时需要.value

      image-20220116140505277

      备注: 如果不想使用.value可以不写,但是要为watch配置深度监视。

  • watchEffect函数

    在编写watch函数时,既要指明监视的属性,又要指明监视的回调;而在编写watchEffect时,不用指明监视哪个属性,监视的回调中用到哪个属性,就会自动监视哪个属性,写法如下:

     watchEffect(() => {
       msg.value = "emma";
       let x = person.name;
       console.log("watchEffect执行了");
     })
    

    备注: watchEffect会在组件挂载后执行一次。

    watchEffectcomputed的比较:

    相同点:二者都是根据依赖数据的变化而执行回调函数的

    不同点:computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值;而watchEffect更注重的是过程(回调函数的主体),所以不用写返回值。

(8)Vue3生命周期

vue3中可以继续使用vue2中的生命周期钩子,但是有两个被更名:beforeDestroy更改为beforeUnmountdestroyed更改为unmounted

Vue3中也提供了Componsition API形式的生命周期钩子,与Vue2中钩子的对应如下:

  • beforeCreate()======>setup()
  • created()======>setup()
  • beforeMount()======>onBeforeMount()
  • mounted()======>onMounted()
  • beforeUpdate()======>onBeforeUpdate()
  • updated()======>onUpdated()
  • beforeUnmount() ==>onBeforeUnmount()
  • unmounted() =====>onUnmounted()
 import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from "vue";
 ​
 setup() {
   console.log("setup");
   let sum = ref(0);
   onBeforeMount(() => {
     console.log("onBeforeMount");
   })
   onMounted(() => {
     console.log("onMounted");
   })
   onBeforeUpdate(() => {
     console.log("onBeforeUpdate");
   })
   onUpdated(() => {
     console.log("onUpdated");
   })
   onBeforeUnmount(() => {
     console.log("onBeforeUnmount");
   })
   onUnmounted(() => {
     console.log("onUnmounted");
   })
   return {sum}
 },
(9)自定义hook函数

自定义hook的本质是一个函数,函数中把setup函数中的composition API进行了封装,类似于vue2中的mixin。自定义hook的优势是复用代码,让setup中的逻辑更加清晰。

一般将hook函数定义在src/hooks文件夹下,定义usePoint.js文件如下:

 import { onBeforeUnmount, onMounted, reactive } from "vue";
 ​
 export default function() {
   let point = reactive({
     x: 0,
     y: 0
   });
   function savePoint(event) {
     point.x = event.pageX;
     point.y = event.pageY;
     console.log(point.x, point.y);
   }
   onMounted(() => {
     window.addEventListener("click", savePoint)
   })
   onBeforeUnmount(() => {
     window.removeEventListener("click", savePoint)
   })
   return point;
 }

在组件中使用自定义hook

 <template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button><hr>
   <h2>当前鼠标点击的坐标为:x:{{point.x}},y:{{point.y}}</h2>
 </template>
 ​
 <script>
   import { ref } from "vue";
   import usePoint from "../hooks/usePoint";
 ​
   export default {
     name: "Demo",
     setup() {
       let sum = ref(0);
       let point = usePoint();
       
       return { sum, point }
     }
   }
 </script>
(10)toRef

通过调用toRef来创建一个对象,该对象的value值指向另一个对象中的某个属性

 setup() {
   let person = reactive({
     name: "dexter",
     age: 18,
     job: {
       j1: {
         salary: 20
       }
     }
   });
   const x = toRef(person, "name");
   console.log("x=",x);
 }

可以看到roRefperson中的name的值也包装成一个对象,如同一个桥梁,联通了操作对象与原对象:

image-20220116172549275

可以利用toRef将定义的对象数据内部的属性取出来,方便在结构中使用

 <template>
   <h2>姓名:{{name}}</h2>
   <h2>年龄:{{age}}</h2>
   <h2>薪资:{{salary}}</h2>
   <button @click="name+='~'">修改姓名</button>
   <button @click="age++">增长年龄</button>
   <button @click="salary++">涨薪</button>
 </template>
 ​
 <script>
   import { reactive, toRef } from "vue";
 ​
   export default {
     name: "Demo",
     setup() {
       let person = reactive({
         name: "dexter",
         age: 18,
         job: {
           j1: {
             salary: 20
           }
         }
       });
       return {
         name: toRef(person, "name"),
         age: toRef(person, "age"),
         salary: toRef(person.job.j1, "salary")
       }
     }
   }
 </script>

备注: toRefstoRef的功能一致,但是可以批量创建多个ref对象,如:const x1 = toRefs(person)

image-20220116173111087

这样可以简化return的语法:return { ...toRefs(person) }。但是toRefs只能展开第一层数据,深层的数据在结构中依然要通过.来获取。

4、其他Composition API

(1)shallowReactive和shallowRef
  • shallowReactive

    reactive相似,但是shallowReactive只处理对象最外层的响应式(浅响应式)

  • shallowRef

    ref相似,但是shallowRef只处理基本数据类型的响应式,不进行对象的响应式处理

  • 使用场景

    • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化可以使用shallowReactive
    • 如果有一个对象数据,后续功能不会修改对象中的属性,而是用新的对象来替换可以使用shallowRef
(2)readonly和shallowReadonly
  • readonly

    让一个响应式数据变为只读的(深只读)

  • shallowReadonly

    让一个响应式数据变为只读的(浅只读)

 let person = reactive({
   name: "dexter",
   age: 18,
   job: {
     j1: {
       salary: 20
     }
   }
 });
 //person = readonly(person);
 person = shallowReadonly(person);

让一个数据变为只读之后不可以修改,如果强制修改控制台会有警告,只读属性的机制是vue内部也不会修改数据,而不是修改了不显示。

(3)toRaw和markRaw
  • toRaw

    作用是将一个由reactive生成的响应式对象转换为普通对象。

    使用场景,用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起页面的更新。

  • markRaw

    作用是标记一个对象,使其永远不会再成为响应式对象

    应用场景,有些值不应被设置为响应式,例如复杂的第三方库等,当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

    注意: 使用markRaw标记成的非响应式数据在vue后台是会检测到修改的,只是不会在页面上显示。

 import { markRaw, reactive, toRaw, toRefs } from "vue";
 ​
 export default {
   name: "Demo",
   setup() {
     let person = reactive({
       name: "dexter",
       age: 18,
       job: {
         j1: {
           salary: 20
         }
       }
     });
     function showRawPerson() {
       const p = toRaw(person);
       p.age++;
     }
     function addCar() {
       let car = {name: "benzi", price: 40};
       person.car = markRaw(car);
     }
     function changePrice() {
       person.car.price++;
     }
 ​
     return { person, ...toRefs(person), showRawPerson, addCar, changePrice }
   }
 }
(4)customRef

作用是创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制

customRef需要一个函数作为参数,该函数接收两个参数tracktrigger,在获取数据前需要调用track通知Vue追踪value的变化,在更新了数据之后要调用trigger通知Vue重新解析模板。

自定义ref实现输入防抖效果

 <template>
   <input type="text" v-model="keyword">
   <h1>{{keyword}}</h1>
 </template>
 ​
 <script>
   import { customRef } from 'vue';
 ​
   export default {
     name: 'App',
     setup() {
       function myRef(value, delay) {
         let timer;
         return customRef((track, trigger) => {
           return {
             get() {
               console.log(`有人从myRef中读取走了${value}`);
               track()
               return value;
             },
             set(newValue) {
               console.log(`有人将myRef中的数据修改为${newValue}`);
               clearTimeout(timer)
               timer = setTimeout(() => {
                 value = newValue;
                 trigger()
               }, delay)
             }
           }
         })
       }
       let keyword = myRef("hello", 1000);
       return {keyword}
     }
   }
 </script>
(5)provide与inject

image-20220116192341811

实现祖孙组件之间的通信,父组件通过provide选项来提供数据,后代组件通过inject选项来使用这些数据

父组件中写法

 setup() {
   let car = reactive({name: "benzi", price: "40W"});
   provide("car", car)
   return {...toRefs(car)}
 }

后代组件的写法

 setup() {
   let car = inject("car");
   return {car}
 }
(6)响应式数据的判断
  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
   setup() {
     let car = reactive({name: "benzi", price: "40W"});
     let sum = ref(0);
     let car2 = readonly(car);
     console.log(isRef(sum));//true
     console.log(isProxy(car));//true
     console.log(isReactive(sum));//false
     console.log(isReadonly(car2));//true
     return {...toRefs(car)}
   }

5、Composition API的优势

(1)Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

(2)Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

6、新的组件

(1)Fragment
  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用
(2)Teleport

Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

 <teleport to='body'>
   <div v-if="isShow" class="mask">
     <div class="dialog">
       <h2>这是一个弹窗</h2>
       <h4>内容</h4>
       <button @click="isShow=false">关闭</button>
     </div>
   </div>
 </teleport>

将需要传送的内容包裹在teleport标签内,通过to属性来指定所要传送的位置,属性值可以是html标签,也可以是css选择器。之后指定的结构就会在目标区域显示了。

(3)Suspense

等待异步组件时渲染一些额外的内容,让应用有更好的用户体验

使用步骤:

  • 异步引入组件

     import { defineAsyncComponent } from "vue";
     const Child = defineAsyncComponent(() => import("./components/Child.vue"));
    
  • 使用Suspense包裹组件,并配置好defaultfallback

     <Suspense>
       <template v-slot:default>
         <Child/>
       </template>
       <template v-slot:fallback>
         <h3>加载中,稍等!</h3>
       </template>
     </Suspense>
    

    当child组件还没有加载成功时会显示加载中,加载成功时就显示child组件。原理是Vue在背后配置了两个插槽,我们需要把适当的内容放在对应的插槽中即可。

7、其他

(1)全局API的转移

Vue 2.x 有许多全局 API 和配置,例如:注册全局组件、注册全局指令等,Vue3.0中对这些API做出了调整:将全局的API,即:Vue.xxx调整到应用实例(app)上

2.x 全局 API(Vue3.x 实例 API (app)
Vue.config.xxxxapp.config.xxxx
Vue.config.productionTip移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties
(2)其他改变
  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    Vue2.x写法

     .v-enter,
     .v-leave-to {
       opacity: 0;
     }
     .v-leave,
     .v-enter-to {
       opacity: 1;
     }
    

    Vue3.x写法

     .v-enter-from,
     .v-leave-to {
       opacity: 0;
     }
     ​
     .v-leave-from,
     .v-enter-to {
       opacity: 1;
     }
    
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    父组件中绑定事件

     <my-component
       v-on:close="handleComponentEvent"
       v-on:click="handleNativeClickEvent"
     />
    

    子组件中声明自定义事件

     <script>
       export default {
         emits: ['close']
       }
     </script>
    
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。