一、创建Vue3.0工程

1.使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。

  • 优势如下:

    • 开发环境中,无需打包操作,可快速的冷启动。

    • 轻量快速的热重载(HMR)。

    • 真正的按需编译,不再等待整个应用编译完成。

  • 传统构建 与 vite构建对比图

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

分析工程结构

1 //引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
2 import { createApp } from 'vue'
3 import App from './App.vue'
4 
5 //创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
6 const app = createApp(App)
7 //挂载
8 app.mount('#app')
main.js
 1 <template>
 2   <!-- Vue3组件中的模板结构可以没有根标签 -->
 3   <img alt="Vue logo" src="./assets/logo.png">
 4   <HelloWorld msg="Welcome to Your Vue.js App"/>
 5 </template>
 6 
 7 <script>
 8 import HelloWorld from './components/HelloWorld.vue'
 9 
10 export default {
11   name: 'App',
12   components: {
13     HelloWorld
14   }
15 }
16 </script>
17 
18 <style>
19 #app {
20   font-family: Avenir, Helvetica, Arial, sans-serif;
21   -webkit-font-smoothing: antialiased;
22   -moz-osx-font-smoothing: grayscale;
23   text-align: center;
24   color: #2c3e50;
25   margin-top: 60px;
26 }
27 </style>
App.vue

二、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

1.拉开序幕的setup

1. 理解:Vue3.0中一个新的配置项,值为一个函数。
2. setup是所有<strong style="color:#DD5145">Composition API(组合API)</strong><i style="color:gray;font-weight:bold">“ 表演的舞台 ”</i>。
3. 组件中所用到的:数据、方法等等,均要配置在setup中。
4. setup函数的两种返回值:
   1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
   2. <span style="color:#aad">若返回一个渲染函数:则可以自定义渲染内容。(了解)</span>
5. 注意点:
   1. 尽量不要与Vue2.x配置混用
      - Vue2.x配置(data、methos、computed...)中<strong style="color:#DD5145">可以访问到</strong>setup中的属性、方法。
      - 但在setup中<strong style="color:#DD5145">不能访问到</strong>Vue2.x配置(data、methos、computed...)。
      - 如果有重名, setup优先。
   2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

初识setup

1 import { createApp } from 'vue'
2 import App from './App.vue'
3 
4 const app = createApp(App)
5 app.mount('#app')
main.js
 1 <template>
 2   <h1>一个人的信息</h1>
 3   <h2>姓名:{{name}}</h2>
 4   <h2>年龄:{{age}}</h2>
 5   <button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
 6 </template>
 7 
 8 <script>
 9 
10 export default {
11   name: 'App',
12   setup(){
13     //数据
14     let name = '张三'
15     let age = 18
16 
17     //方法
18     function sayHello() {
19       alert(`我的名字是${name},我${age}岁了!`)
20     }
21 
22     return {
23       name,
24       age,
25       sayHello
26     }
27   }
28 }
29 </script>
30 
31 <style>
32 
33 </style>
App.vue

2.ref函数

- 作用: 定义一个响应式的数据
- 语法: const xxx = ref(initValue)
  - 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
  - JS中操作数据: xxx.value
  - 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
- 备注:
  - 接收的数据可以是:基本类型、也可以是对象类型。
  - 基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
  - 对象类型的数据:内部求助了Vue3.0中的一个新函数—— reactive函数。
 1 <template>
 2   <h1>一个人的信息</h1>
 3   <h2>姓名:{{name}}</h2>
 4   <h2>年龄:{{age}}</h2>
 5   <h3>工作种类:{{job.type}}</h3>
 6   <h3>工作薪水:{{job.salary}}</h3>
 7   <button @click="changeInfo">修改人的信息</button>
 8 </template>
 9 
10 <script>
11 import {ref} from 'vue'
12 export default {
13   name: 'App',
14   setup(){
15     //数据
16     let name = ref('张三')
17     let age = ref(18)
18     let job = ref({
19       type:'前端工程师',
20       salary:'30K'
21     })
22 
23     //方法
24     function changeInfo() {
25       name.value = '李四'
26       age.value = 20
27       job.value.type = 'UI设计师'
28       job.value.salary = '60K'
29     }
30 
31     return {
32       name,
33       age,
34       changeInfo,
35       job
36     }
37   }
38 }
39 </script>
40 
41 <style>
42 
43 </style>
App.vue

3.reactive函数

- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
- 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
- reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
 1 <template>
 2   <h1>一个人的信息</h1>
 3   <h2>姓名:{{person.name}}</h2>
 4   <h2>年龄:{{person.age}}</h2>
 5   <h3>工作种类:{{person.job.type}}</h3>
 6   <h3>工作薪水:{{person.job.salary}}</h3>
 7   <h3>爱好:{{person.hobby}}</h3>
 8   <h3>测试的数据c:{{person.job.a.b.c}}</h3>
 9   <button @click="changeInfo">修改人的信息</button>
10 </template>
11 
12 <script>
13 import {reactive} from 'vue'
14 export default {
15   name: 'App',
16   setup(){
17     //数据
18     let person = reactive({
19       name:'张三',
20       age:18,
21       job:{
22         type:'前端工程师',
23         salary:'30K',
24         a:{
25           b:{
26             c:666
27           }
28         }
29       },
30       hobby:['抽烟','喝酒','烫头']
31     })
32 
33     //方法
34     function changeInfo() {
35       person.name = '李四'
36       person.age = 20
37       person.job.type = 'UI设计师'
38       person.job.salary = '60K'
39     }
40 
41     return {
42       person,
43       changeInfo
44     }
45   }
46 }
47 </script>
48 
49 <style>
50 
51 </style>
App.vue

4.Vue3.0中的响应式原理

vue2.x的响应式

- 实现原理:
  - 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
  - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
    Object.defineProperty(data, 'count', {
        get () {}, 
        set () {}
    })
- 存在问题:
  - 新增属性、删除属性, 界面不会更新。
  - 直接通过下标修改数组, 界面不会自动更新。

Vue3.0的响应式

实现原理: 
- 通过Proxy(代理):  拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射):  对源对象的属性进行操作。
- MDN文档中描述的Proxy与Reflect:
  - Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
  - Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

new Proxy(data, {
    // 拦截读取属性值
    get (target, prop) {
        return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
        return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
        return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom' 
  1 <!DOCTYPE html>
  2 <html>
  3     <head>
  4         <meta charset="UTF-8" />
  5         <title>Document</title>
  6     </head>
  7     <body>
  8         <script type="text/javascript" >
  9             //源数据
 10             let person = {
 11                 name:'张三',
 12                 age:18
 13             }
 14 
 15             //模拟Vue2中实现响应式
 16             //#region 
 17             /* let p = {}
 18             Object.defineProperty(p,'name',{
 19                 configurable:true,
 20                 get(){ //有人读取name时调用
 21                     return person.name
 22                 },
 23                 set(value){ //有人修改name时调用
 24                     console.log('有人修改了name属性,我发现了,我要去更新界面!')
 25                     person.name = value
 26                 }
 27             })
 28             Object.defineProperty(p,'age',{
 29                 get(){ //有人读取age时调用
 30                     return person.age
 31                 },
 32                 set(value){ //有人修改age时调用
 33                     console.log('有人修改了age属性,我发现了,我要去更新界面!')
 34                     person.age = value
 35                 }
 36             }) */
 37             //#endregion
 38             
 39             //模拟Vue3中实现响应式 (代理+反射)
 40             //#region 
 41             const p = new Proxy(person,{
 42                 //有人读取p的某个属性时调用
 43                 get(target,propName){
 44                     console.log(`有人读取了p身上的${propName}属性`)
 45                     return Reflect.get(target,propName)
 46                 },
 47                 //有人修改p的某个属性、或给p追加某个属性时调用
 48                 set(target,propName,value){
 49                     console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
 50                     Reflect.set(target,propName,value)
 51                 },
 52                 //有人删除p的某个属性时调用
 53                 deleteProperty(target,propName){
 54                     console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
 55                     return Reflect.deleteProperty(target,propName)
 56                 }
 57             })
 58             //#endregion
 59 
 60             let obj = {a:1,b:2}
 61             //通过Object.defineProperty去操作
 62             //#region 
 63             /* try {
 64                 Object.defineProperty(obj,'c',{
 65                     get(){
 66                         return 3
 67                     }
 68                 })
 69                 Object.defineProperty(obj,'c',{
 70                     get(){
 71                         return 4
 72                     }
 73                 })
 74             } catch (error) {
 75                 console.log(error)
 76             } */
 77             //#endregion
 78 
 79             //通过Reflect.defineProperty去操作
 80             //#region 
 81             /* const x1 = Reflect.defineProperty(obj,'c',{
 82                 get(){
 83                     return 3
 84                 }
 85             })
 86             console.log(x1)
 87             
 88             const x2 = Reflect.defineProperty(obj,'c',{
 89                 get(){
 90                     return 4
 91                 }
 92             }) 
 93             if(x2){
 94                 console.log('某某某操作成功了!')
 95             }else{
 96                 console.log('某某某操作失败了!')
 97             } */
 98             //#endregion
 99 
100             // console.log('@@@')
101 
102         </script>
103     </body>
104 </html>
Vue3的响应式原理

5.reactive对比ref

-  从定义数据角度对比:
   -  ref用来定义:基本类型数据。
   -  reactive用来定义:对象(或数组)类型数据。
   -  备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
-  从原理角度对比:
   -  ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
   -  reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
-  从使用角度对比:
   -  ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。
   -  reactive定义的数据:操作数据与读取数据:均不需要.value。

6.setup的两个注意点

- setup执行的时机
  - 在beforeCreate之前执行一次,this是undefined。
- setup的参数
  - props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
  - context:上下文对象
    - attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于this.$attrs。
    - slots: 收到的插槽内容, 相当于 this.$slots。
    - emit: 分发自定义事件的函数, 相当于 this.$emit。
 1 <template>
 2   <Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
 3     <template v-slot:qwe>
 4       <span>京东</span>
 5     </template>
 6     <template v-slot:asd>
 7       <span>京东</span>
 8     </template>
 9   </Demo>
10 </template>
11 
12 <script>
13 import Demo from "@/components/Demo";
14 
15 export default {
16   name: 'App',
17   components:{
18     Demo
19   },
20   setup(){
21     function showHelloMsg(value){
22       alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
23     }
24     return {
25       showHelloMsg
26     }
27   }
28 }
29 </script>
30 
31 <style>
32 
33 </style>
App.vue
 1 <template>
 2   <h1>一个人的信息</h1>
 3   <h2>姓名:{{ person.name }}</h2>
 4   <h2>年龄:{{ person.age }}</h2>
 5   <button @click="test">测试触发一下Demo组件的Hello事件</button>
 6 </template>
 7 
 8 <script>
 9 import {reactive} from 'vue'
10 
11 export default {
12   name: "Demo",
13   props:['msg','school'],
14   emits:['hello'],
15   setup(props,context){
16     // console.log('---setup---',props)
17     // console.log('---setup---',context)
18     // console.log('---setup---',context.attrs) //相当于Vue2中的$attrs
19     // console.log('---setup---',context.emit) //触发自定义事件的。
20     // console.log('---setup---',context.slots) //插槽
21 
22     let person = reactive({
23       name:'张三',
24       age:18
25     })
26 
27     function test() {
28       context.emit('hello',666)
29     }
30 
31     return {
32       person,
33       test
34     }
35   }
36 }
37 </script>
38 
39 <style scoped>
40 
41 </style>
Demo.vue

7.计算属性与监视

1.computed函数

- 与Vue2.x中computed配置功能一致
- 写法
import {computed} from 'vue'

setup(){
    ...
    //计算属性——简写
    let fullName = computed(()=>{
        return person.firstName + '-' + person.lastName
    })
    //计算属性——完整
    let fullName = computed({
        get(){
            return person.firstName + '-' + person.lastName
        },
        set(value){
            const nameArr = value.split('-')
            person.firstName = nameArr[0]
            person.lastName = nameArr[1]
        }
    })
}
 1 <template>
 2   <Demo></Demo>
 3 </template>
 4 
 5 <script>
 6 import Demo from "@/components/Demo";
 7 
 8 export default {
 9   name: 'App',
10   components:{
11     Demo
12   }
13 }
14 </script>
15 
16 <style>
17 
18 </style>
App.vue
 1 <template>
 2   <h1>一个人的信息</h1>
 3   姓:<input type="text" v-model="person.firstName">
 4   <br>
 5   名:<input type="text" v-model="person.lastName">
 6   <br>
 7   <span>全名:{{person.fullName}}</span>
 8   <br>
 9   全名:<input type="text" v-model="person.fullName">
10 </template>
11 
12 <script>
13 import {reactive,computed} from 'vue'
14 
15 export default {
16   name: "Demo",
17   setup(){
18     let person = reactive({
19       firstName:'',
20       lastName:'',
21     })
22     //计算属性——简写(没有考虑计算属性被修改的情况)
23     // person.fullName = computed(()=>{
24     //   return person.firstName + '-' + person.lastName
25     // })
26 
27     //计算属性——完整写法(考虑读和写)
28     person.fullName = computed({
29       get(){
30         return person.firstName + '-' + person.lastName
31       },
32       set(value){
33         const nameArr = value.split('-')
34         person.firstName = nameArr[0]
35         person.lastName = nameArr[1]
36       }
37     })
38 
39     return {
40       person,
41     }
42   }
43 }
44 </script>
45 
46 <style scoped>
47 
48 </style>
Demo.vue

2.watch函数

- 与Vue2.x中watch配置功能一致
- 两个小“坑”:
  - 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
  - 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
},{immediate:true})

//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
    console.log('sum或msg变化了',newValue,oldValue)
}) 

/* 情况三:监视reactive定义的响应式数据
            若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
            若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
*/
watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效

//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}) 

//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})

//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
 1 <template>
 2   <Demo></Demo>
 3 </template>
 4 
 5 <script>
 6 import Demo from "@/components/Demo";
 7 
 8 export default {
 9   name: 'App',
10   components:{
11     Demo
12   }
13 }
14 </script>
15 
16 <style>
17 
18 </style>
App.vue
 1 <template>
 2   <h2>当前求和为:{{sum}}</h2>
 3   <button @click="sum++">点我+1</button>
 4   <hr>
 5   <h2>当前的信息为:{{msg}}</h2>
 6   <button @click="msg+='!'">修改信息</button>
 7   <hr>
 8   <h2>姓名:{{person.name}}</h2>
 9   <h2>年龄:{{person.age}}</h2>
10   <h2>薪资:{{person.job.j1.salary}}K</h2>
11   <button @click="person.name+='~'">修改姓名</button>
12   <button @click="person.age++">增长年龄</button>
13   <button @click="person.job.j1.salary++">涨薪</button>
14 </template>
15 
16 <script>
17 import {reactive,ref,watch} from 'vue'
18 
19 export default {
20   name: "Demo",
21   setup(){
22     let sum = ref(0)
23     let msg = ref('你好啊')
24     let person = reactive({
25       name:'张三',
26       age:18,
27       job:{
28         j1:{
29           salary:20
30         }
31       }
32     })
33 
34     //情况一:监视ref所定义的一个响应式数据
35     // watch(sum,(newValue,oldValue)=>{
36     //   console.log('sum变了',newValue,oldValue)
37     // },{immediate:true})
38 
39     //情况二:监视ref所定义的多个响应式数据
40     // watch([sum,msg],(newValue,oldValue)=>{
41     //   console.log('sum或msg变了',newValue,oldValue)
42     // },{immediate:true})
43 
44     /*
45     情况三:监视reactive所定义的一个响应式数据的全部属性
46         1.注意:此处无法正确的获取oldValue(对象类型不行)
47         2.注意:强制开启了深度监视(deep配置无效)
48     */
49     // watch(person,(newValue,oldValue)=>{
50     //   console.log('person变化了',newValue,oldValue)
51     // },{deep:false}) //此处的deep配置无效
52 
53     //情况四:监视reactive所定义的一个响应式数据中的某个属性
54     // watch(()=>person.name,(newValue,oldValue)=>{
55     //   console.log('person变化了',newValue,oldValue)
56     // })
57 
58     //情况五:监视reactive所定义的一个响应式数据中的某些属性
59     // watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
60     //   console.log('person变化了',newValue,oldValue)
61     // })
62 
63     //特殊情况
64     watch(()=>person.job,(newValue,oldValue)=>{
65       console.log('person的job变化了',newValue,oldValue)
66     },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
67 
68     return {
69       sum,
70       msg,
71       person
72     }
73   }
74 }
75 </script>
76 
77 <style scoped>
78 
79 </style>
Demo.vue

watch监视ref数据的说明

 1 <template>
 2   <h2>当前求和为:{{sum}}</h2>
 3   <button @click="sum++">点我+1</button>
 4   <hr>
 5   <h2>当前的信息为:{{msg}}</h2>
 6   <button @click="msg+='!'">修改信息</button>
 7   <hr>
 8   <h2>姓名:{{person.name}}</h2>
 9   <h2>年龄:{{person.age}}</h2>
10   <h2>薪资:{{person.job.j1.salary}}K</h2>
11   <button @click="person.name+='~'">修改姓名</button>
12   <button @click="person.age++">增长年龄</button>
13   <button @click="person.job.j1.salary++">涨薪</button>
14 </template>
15 
16 <script>
17 import {reactive,ref,watch} from 'vue'
18 
19 export default {
20   name: "Demo",
21   setup(){
22     let sum = ref(0)
23     let msg = ref('你好啊')
24     let person = ref({
25       name:'张三',
26       age:18,
27       job:{
28         j1:{
29           salary:20
30         }
31       }
32     })
33 
34     watch(sum,(newValue,oldValue)=>{
35       console.log('sum变了',newValue,oldValue)
36     })
37 
38     // watch(person,(newValue,oldValue)=>{
39     //   console.log('person变了',newValue,oldValue)
40     // })
41 
42     // watch(person.value,(newValue,oldValue)=>{
43     //   console.log('person变了',newValue,oldValue)
44     // })
45 
46     watch(person,(newValue,oldValue)=>{
47       console.log('person变了',newValue,oldValue)
48     },{deep:true})
49 
50 
51     return {
52       sum,
53       msg,
54       person
55     }
56   }
57 }
58 </script>
59 
60 <style scoped>
61 
62 </style>
Demo.vue

3.watchEffect函数

- watch的套路是:既要指明监视的属性,也要指明监视的回调。
- watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
  - 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
  - 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
})
 1 <template>
 2   <h2>当前求和为:{{sum}}</h2>
 3   <button @click="sum++">点我+1</button>
 4   <hr>
 5   <h2>当前的信息为:{{msg}}</h2>
 6   <button @click="msg+='!'">修改信息</button>
 7   <hr>
 8   <h2>姓名:{{person.name}}</h2>
 9   <h2>年龄:{{person.age}}</h2>
10   <h2>薪资:{{person.job.j1.salary}}K</h2>
11   <button @click="person.name+='~'">修改姓名</button>
12   <button @click="person.age++">增长年龄</button>
13   <button @click="person.job.j1.salary++">涨薪</button>
14 </template>
15 
16 <script>
17 import {reactive,ref,watch,watchEffect} from 'vue'
18 
19 export default {
20   name: "Demo",
21   setup(){
22     let sum = ref(0)
23     let msg = ref('你好啊')
24     let person = reactive({
25       name:'张三',
26       age:18,
27       job:{
28         j1:{
29           salary:20
30         }
31       }
32     })
33 
34     // watch(sum,(newValue,oldValue)=>{
35     //   console.log('sum变了',newValue,oldValue)
36     // })
37 
38     watchEffect(()=>{
39       const x1 = sum.value
40       const x2 = person.job.j1.salary
41       console.log('watchEffect所指定的回调执行了')
42     })
43     
44     return {
45       sum,
46       msg,
47       person
48     }
49   }
50 }
51 </script>
52 
53 <style scoped>
54 
55 </style>
Demo.vue

8.生命周期

- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
  - beforeDestroy改名为 beforeUnmount
  - destroyed改名为 unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
  - `beforeCreate`===>`setup()`
  - `created`=======>`setup()`
  - `beforeMount` ===>`onBeforeMount`
  - `mounted`=======>`onMounted`
  - `beforeUpdate`===>`onBeforeUpdate`
  - `updated` =======>`onUpdated`
  - `beforeUnmount` ==>`onBeforeUnmount`
  - `unmounted` =====>`onUnmounted`
 1 <template>
 2   <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
 3   <Demo v-if="isShowDemo"/>
 4 </template>
 5 
 6 <script>
 7 import {ref} from 'vue'
 8 import Demo from './components/Demo'
 9 export default {
10   name: 'App',
11   components:{Demo},
12   setup() {
13     let isShowDemo = ref(true)
14     return {isShowDemo}
15   }
16 }
17 </script>
App.vue
 1 <template>
 2   <h2>当前求和为:{{ sum }}</h2>
 3   <button @click="sum++">点我+1</button>
 4 </template>
 5 
 6 <script>
 7 import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
 8 
 9 export default {
10   name: 'Demo',
11   setup() {
12     console.log('---setup---')
13     //数据
14     let sum = ref(0)
15 
16     //通过组合式API的形式去使用生命周期钩子
17     onBeforeMount(() => {
18       console.log('---onBeforeMount---')
19     })
20     onMounted(() => {
21       console.log('---onMounted---')
22     })
23     onBeforeUpdate(() => {
24       console.log('---onBeforeUpdate---')
25     })
26     onUpdated(() => {
27       console.log('---onUpdated---')
28     })
29     onBeforeUnmount(() => {
30       console.log('---onBeforeUnmount---')
31     })
32     onUnmounted(() => {
33       console.log('---onUnmounted---')
34     })
35 
36     //返回一个对象(常用)
37     return {sum}
38   },
39 
40   //通过配置项的形式使用生命周期钩子
41   // beforeCreate() {
42   //   console.log('---beforeCreate---')
43   // },
44   // created() {
45   //   console.log('---created---')
46   // },
47   // beforeMount() {
48   //   console.log('---beforeMount---')
49   // },
50   // mounted() {
51   //   console.log('---mounted---')
52   // },
53   // beforeUpdate() {
54   //   console.log('---beforeUpdate---')
55   // },
56   // updated() {
57   //   console.log('---updated---')
58   // },
59   // beforeUnmount() {
60   //   console.log('---beforeUnmount---')
61   // },
62   // unmounted() {
63   //   console.log('---unmounted---')
64   // }
65 }
66 </script>
Demo.vue

9.自定义hook函数

- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于vue2.x中的mixin。
- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
 1 <template>
 2   <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
 3   <Demo v-if="isShowDemo"/>
 4   <hr>
 5   <Test></Test>
 6 </template>
 7 
 8 <script>
 9 import {ref} from 'vue'
10 import Demo from './components/Demo'
11 import Test from "@/components/Test";
12 
13 export default {
14   name: 'App',
15   components:{Demo,Test},
16   setup() {
17     let isShowDemo = ref(true)
18     return {isShowDemo}
19   }
20 }
21 </script>
App.vue
 1 import {onBeforeMount, onMounted,reactive} from 'vue'
 2 
 3 export default function () {
 4     let point = reactive({
 5         x:0,
 6         y:0
 7     })
 8 
 9     function savePoint(event){
10         point.x = event.pageX
11         point.y = event.pageY
12     }
13 
14     onMounted(() => {
15         window.addEventListener('click',savePoint)
16     })
17 
18     onBeforeMount(()=>{
19         window.removeEventListener('click',savePoint)
20     })
21 
22     return point
23 }
usePoint.js
 1 <template>
 2   <h2>当前求和为:{{ sum }}</h2>
 3   <button @click="sum++">点我+1</button>
 4   <hr>
 5   <h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}</h2>
 6 </template>
 7 
 8 <script>
 9 import {ref} from 'vue'
10 import usePoint from "@/hooks/usePoint";
11 
12 export default {
13   name: 'Demo',
14   setup() {
15     //数据
16     let sum = ref(0)
17 
18     let point = usePoint()
19 
20     return {sum,point}
21   }
22 }
23 </script>
Demo.vue
 1 <template>
 2   <h2>我是Test组件</h2>
 3   <h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}</h2>
 4 </template>
 5 
 6 <script>
 7 import usePoint from '../hooks/usePoint'
 8 export default {
 9   name:'Test',
10   setup(){
11     const point = usePoint()
12     return {point}
13   }
14 }
15 </script>
Test.vue

10.toRef

- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 语法:const name = toRef(person,'name')
- 应用:   要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:toRefs 与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
 1 <template>
 2   <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button>
 3   <Demo v-if="isShowDemo"/>
 4 </template>
 5 
 6 <script>
 7 import {ref} from 'vue'
 8 import Demo from './components/Demo'
 9 
10 export default {
11   name: 'App',
12   components:{Demo},
13   setup() {
14     let isShowDemo = ref(true)
15     return {isShowDemo}
16   }
17 }
18 </script>
App.vue
 1 <template>
 2   <h4>{{person}}</h4>
 3   <h2>姓名:{{name}}</h2>
 4   <h2>年龄:{{age}}</h2>
 5   <h2>薪资:{{job.j1.salary}}K</h2>
 6   <button @click="name+='~'">修改姓名</button>
 7   <button @click="age++">增长年龄</button>
 8   <button @click="job.j1.salary++">涨薪</button>
 9 </template>
10 
11 <script>
12 import {toRef,toRefs,reactive} from 'vue'
13 
14 export default {
15   name: 'Demo',
16   setup() {
17     let person = reactive({
18       name:'张三',
19       age:18,
20       job:{
21         j1:{
22           salary:20
23         }
24       }
25     })
26 
27     const x = toRefs(person)
28     console.log('******',x)
29 
30     return {
31       person,
32       // name:toRef(person,'name'),
33       // age:toRef(person,'age'),
34       // salary:toRef(person.job.j1,'salary'),
35       ...toRefs(person)
36     }
37   }
38 }
39 </script>
Demo.vue

三、其它 Composition API

1.shallowReactive 与 shallowRef

- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?
  -  如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
  -  如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
 1 <template>
 2   <h4>当前的x.y值是:{{x.y}}</h4>
 3   <button @click="x={y:888}">点我替换x</button>
 4   <button @click="x.y++">点我x.y++</button>
 5   <hr>
 6   <h4>{{person}}</h4>
 7   <h2>姓名:{{name}}</h2>
 8   <h2>年龄:{{age}}</h2>
 9   <h2>薪资:{{job.j1.salary}}K</h2>
10   <button @click="name+='~'">修改姓名</button>
11   <button @click="age++">增长年龄</button>
12   <button @click="job.j1.salary++">涨薪</button>
13 </template>
14 
15 <script>
16 import {toRefs,reactive,shallowReactive,shallowRef} from 'vue'
17 
18 export default {
19   name: 'Demo',
20   setup() {
21     // let person = shallowReactive({  //只考虑第一层数据的响应式
22     let person = reactive({
23       name:'张三',
24       age:18,
25       job:{
26         j1:{
27           salary:20
28         }
29       }
30     })
31 
32     let x = shallowRef({
33       y:0
34     })
35     console.log('******',x)
36 
37     return {
38       x,
39       person,
40       ...toRefs(person)
41     }
42   }
43 }
44 </script>
Demo.vue

2.readonly 与 shallowReadonly

- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
 1 <template>
 2   <h4>当前求和为:{{sum}}</h4>
 3   <button @click="sum++">点我++</button>
 4   <hr>
 5   <h2>姓名:{{name}}</h2>
 6   <h2>年龄:{{age}}</h2>
 7   <h2>薪资:{{job.j1.salary}}K</h2>
 8   <button @click="name+='~'">修改姓名</button>
 9   <button @click="age++">增长年龄</button>
10   <button @click="job.j1.salary++">涨薪</button>
11 </template>
12 
13 <script>
14 import {ref,toRefs,reactive,readonly,shallowReadonly} from 'vue'
15 
16 export default {
17   name: 'Demo',
18   setup() {
19     let sum = ref(0)
20     let person = reactive({
21       name:'张三',
22       age:18,
23       job:{
24         j1:{
25           salary:20
26         }
27       }
28     })
29 
30     // person = readonly(person)
31     // person = shallowReadonly(person)
32 
33     // sum = readonly(sum)
34     // sum = shallowReadonly(sum)
35 
36 
37     return {
38       ...toRefs(person),
39       sum
40     }
41   }
42 }
43 </script>
Demo.vue

3.toRaw 与 markRaw

- toRaw:
  - 作用:将一个由reactive生成的响应式对象转为普通对象。
  - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- markRaw:
  - 作用:标记一个对象,使其永远不会再成为响应式对象。
  - 应用场景:
    1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
    2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
 1 <template>
 2   <h4>当前求和为:{{sum}}</h4>
 3   <button @click="sum++">点我++</button>
 4   <hr>
 5   <h2>姓名:{{name}}</h2>
 6   <h2>年龄:{{age}}</h2>
 7   <h2>薪资:{{job.j1.salary}}K</h2>
 8   <h3 v-show="person.car">座驾信息:{{person.car}}</h3>
 9   <button @click="name+='~'">修改姓名</button>
10   <button @click="age++">增长年龄</button>
11   <button @click="job.j1.salary++">涨薪</button>
12   <button @click="showRawPerson">输出最原始的person</button>
13   <button @click="addCar">给人添加一台车</button>
14   <button @click="person.car.name+='!'">换车名</button>
15   <button @click="changePrice">换价格</button>
16 </template>
17 
18 <script>
19 import {ref,toRefs,reactive,toRaw,markRaw} from 'vue'
20 
21 export default {
22   name: 'Demo',
23   setup() {
24     let sum = ref(0)
25     let person = reactive({
26       name:'张三',
27       age:18,
28       job:{
29         j1:{
30           salary:20
31         }
32       }
33     })
34 
35     function showRawPerson(){
36       const p = toRaw(person)
37       p.age++
38       console.log(p)
39     }
40 
41     function addCar(){
42       let car = {name:'奔驰',price:40}
43       // person.car = car
44       person.car = markRaw(car)
45     }
46 
47     function changePrice(){
48       person.car.price++
49       console.log(person.car.price)
50     }
51 
52     return {
53       person,
54       ...toRefs(person),
55       sum,
56       showRawPerson,
57       addCar,
58       changePrice
59     }
60   }
61 }
62 </script>
Demo.vue

4.customRef

- 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
- 实现防抖效果:
<template>
    <input type="text" v-model="keyword">
    <h3>{{keyword}}</h3>
</template>

<script>
    import {ref,customRef} from 'vue'
    export default {
        name:'Demo',
        setup(){
            // let keyword = ref('hello') //使用Vue准备好的内置ref
            //自定义一个myRef
            function myRef(value,delay){
                let timer
                //通过customRef去实现自定义
                return customRef((track,trigger)=>{
                    return{
                        get(){
                            track() //告诉Vue这个value值是需要被“追踪”的
                            return value
                        },
                        set(newValue){
                            clearTimeout(timer)
                            timer = setTimeout(()=>{
                                value = newValue
                                trigger() //告诉Vue去更新界面
                            },delay)
                        }
                    }
                })
            }
            let keyword = myRef('hello',500) //使用程序员自定义的ref
            return {
                keyword
            }
        }
    }
</script>
 1 <template>
 2   <input type="text" v-model="keyWord">
 3   <h3>{{keyWord}}</h3>
 4 </template>
 5 
 6 <script>
 7 import {ref,customRef} from 'vue'
 8 
 9 export default {
10   name: 'App',
11   components:{},
12   setup() {
13     //防抖效果
14     function myRef(value,delay) {
15       let timer
16       return customRef((track, trigger)=>{
17         return {
18           get(){
19             console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
20             track()  //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
21             return value
22           },
23           set(newValue){
24             console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
25             clearTimeout(timer)
26             timer = setTimeout(()=>{
27               value = newValue
28               trigger() //通知Vue去重新解析模板
29             },delay)
30           }
31         }
32       })
33     }
34 
35     // let keyWord = ref('hello') //使用Vue提供的ref
36     let keyWord = myRef('hello',500)  //使用程序员自定义的ref
37 
38     return {
39       keyWord
40     }
41   }
42 }
43 </script>
App.vue

5.provide 与 inject

- 作用:实现祖与后代组件间通信
- 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据
- 具体写法:
  1. 祖组件中:
setup(){
    ......
    let car = reactive({name:'奔驰',price:'40万'})
    provide('car',car)
    ......
}
  2. 后代组件中:
setup(props,context){
    ......
    const car = inject('car')
    return {car}
    ......
}
 1 <template>
 2   <div class="app">
 3     <h3>我是App组件(祖),{{name}}--{{price}}</h3>
 4     <Child/>
 5   </div>
 6 </template>
 7 
 8 <script>
 9 import { reactive,toRefs,provide } from 'vue'
10 import Child from './components/Child.vue'
11 export default {
12   name:'App',
13   components:{Child},
14   setup(){
15     let car = reactive({name:'奔驰',price:'40W'})
16     provide('car',car) //给自己的后代组件传递数据
17     return {...toRefs(car)}
18   }
19 }
20 </script>
21 
22 <style>
23 .app{
24   background-color: gray;
25   padding: 10px;
26 }
27 </style>
App.vue
 1 <template>
 2   <div class="child">
 3     <h3>我是Child组件(子)</h3>
 4     <Son/>
 5   </div>
 6 </template>
 7 
 8 <script>
 9 import {inject} from 'vue'
10 import Son from './Son.vue'
11 export default {
12   name:'Child',
13   components:{Son},
14   /* setup(){
15     let x = inject('car')
16     console.log(x,'Child-----')
17   } */
18 }
19 </script>
20 
21 <style>
22 .child{
23   background-color: skyblue;
24   padding: 10px;
25 }
26 </style>
Child.vue
 1 <template>
 2   <div class="son">
 3     <h3>我是Son组件(孙),{{car.name}}--{{car.price}}</h3>
 4   </div>
 5 </template>
 6 
 7 <script>
 8 import {inject} from 'vue'
 9 export default {
10   name:'Son',
11   setup(){
12     let car = inject('car')
13     return {car}
14   }
15 }
16 </script>
17 
18 <style>
19 .son{
20   background-color: orange;
21   padding: 10px;
22 }
23 </style>
Son.vue

6.响应式数据的判断

- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理
- isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理
- isProxy: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理
 1 <template>
 2   <h3>我是App组件</h3>
 3 </template>
 4 
 5 <script>
 6 import {ref, reactive,toRefs,readonly,isRef,isReactive,isReadonly,isProxy } from 'vue'
 7 export default {
 8   name:'App',
 9   setup(){
10     let car = reactive({name:'奔驰',price:'40W'})
11     let sum = ref(0)
12     let car2 = readonly(car)
13 
14     console.log(isRef(sum))
15     console.log(isReactive(car))
16     console.log(isReadonly(car2))
17     console.log(isProxy(car))
18     console.log(isProxy(sum))
19 
20 
21     return {...toRefs(car)}
22   }
23 }
24 </script>
25 
26 <style>
27 .app{
28   background-color: gray;
29   padding: 10px;
30 }
31 </style>
App.vue

四、Composition API 的优势

1.Options API 存在的问题

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

2.Composition API 的优势

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

五、新的组件

1.Fragment

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

2.Teleport

什么是Teleport?—— `Teleport` 是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="移动位置">
    <div v-if="isShow" class="mask">
        <div class="dialog">
            <h3>我是一个弹窗</h3>
            <button @click="isShow = false">关闭弹窗</button>
        </div>
    </div>
</teleport>
 1 <template>
 2   <div class="app">
 3     <h3>我是App组件(祖)</h3>
 4     <Child/>
 5   </div>
 6 </template>
 7 
 8 <script>
 9 import Child from './components/Child.vue'
10 export default {
11   name:'App',
12   components:{Child},
13 }
14 </script>
15 
16 <style>
17 .app{
18   background-color: gray;
19   padding: 10px;
20 }
21 </style>
App.vue
 1 <template>
 2     <div class="child">
 3         <h3>我是Child组件</h3>
 4         <Son/>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     import Son from './Son'
10     export default {
11         name:'Child',
12         components:{Son},
13     }
14 </script>
15 
16 <style>
17     .child{
18         background-color: skyblue;
19         padding: 10px;
20     }
21 </style>
Child.vue
 1 <template>
 2     <div class="son">
 3         <h3>我是Son组件</h3>
 4         <Dialog/>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     import Dialog from './Dialog.vue'
10     export default {
11         name:'Son',
12         components:{Dialog}
13     }
14 </script>
15 
16 <style>
17     .son{
18         background-color: orange;
19         padding: 10px;
20     }
21 </style>
Son.vue
 1 <template>
 2     <div>
 3         <button @click="isShow = true">点我弹个窗</button>
 4         <teleport to="body">
 5             <div v-if="isShow" class="mask">
 6                 <div class="dialog">
 7                     <h3>我是一个弹窗</h3>
 8                     <h4>一些内容</h4>
 9                     <h4>一些内容</h4>
10                     <h4>一些内容</h4>
11                     <button @click="isShow = false">关闭弹窗</button>
12                 </div>
13             </div>
14         </teleport>
15     </div>
16 </template>
17 
18 <script>
19     import {ref} from 'vue'
20     export default {
21         name:'Dialog',
22         setup(){
23             let isShow = ref(false)
24             return {isShow}
25         }
26     }
27 </script>
28 
29 <style>
30     .mask{
31         position: absolute;
32         top: 0;bottom: 0;left: 0;right: 0;
33         background-color: rgba(0, 0, 0, 0.5);
34     }
35     .dialog{
36         position: absolute;
37         top: 50%;
38         left: 50%;
39         transform: translate(-50%,-50%);
40         text-align: center;
41         width: 300px;
42         height: 300px;
43         background-color: green;
44     }
45 </style>
Dialog.vue

3.Suspense

- 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
- 使用步骤:
  - 异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
  - 使用Suspense包裹组件,并配置好default与fallback
<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
            <template v-slot:default>
                <Child/>
            </template>
            <template v-slot:fallback>
                <h3>加载中.....</h3>
            </template>
        </Suspense>
    </div>
</template>
 1 <template>
 2     <div class="app">
 3         <h3>我是App组件</h3>
 4         <Suspense>
 5             <template v-slot:default>
 6                 <Child/>
 7             </template>
 8             <template v-slot:fallback>
 9                 <h3>稍等,加载中...</h3>
10             </template>
11         </Suspense>
12     </div>
13 </template>
14 
15 <script>
16     // import Child from './components/Child'//静态引入
17     import {defineAsyncComponent} from 'vue' 
18     const Child = defineAsyncComponent(()=>import('./components/Child')) //异步引入
19     export default {
20         name:'App',
21         components:{Child},
22     }
23 </script>
24 
25 <style>
26     .app{
27         background-color: gray;
28         padding: 10px;
29     }
30 </style>
App.vue
 1 <template>
 2     <div class="child">
 3         <h3>我是Child组件</h3>
 4         {{sum}}
 5     </div>
 6 </template>
 7 
 8 <script>
 9     import {ref} from 'vue'
10     export default {
11         name:'Child',
12         async setup(){
13             let sum = ref(0)
14             let p = new Promise((resolve,reject)=>{
15                 setTimeout(()=>{
16                     resolve({sum})
17                 },3000)
18             })
19             return await p
20         }
21     }
22 </script>
23 
24 <style>
25     .child{
26         background-color: skyblue;
27         padding: 10px;
28     }
29 </style>
Child.vue

六、其他

1.全局API的转移

Vue 2.x 有许多全局 API 和配置。
- 例如:注册全局组件、注册全局指令等。
//注册全局组件
Vue.component('MyButton', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

//注册全局指令
Vue.directive('focus', {
  inserted: el => el.focus()
}

Vue3.0中对这些API做出了调整:
- 将全局的API,即:Vue.xxx调整到应用实例(app)上
2.x 全局 API(Vue)                   3.x 实例 API (`app`)
Vue.config.xxxx                         app.config.xxxx
Vue.config.productionTip            移除
Vue.component                         app.component
Vue.directive                             app.directive
Vue.mixin                                 app.mixin
Vue.use                                    app.use
Vue.prototype                           app.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” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

 

posted on 2023-07-13 18:29  晨曦生辉耀匕尖  阅读(14)  评论(0编辑  收藏  举报