Vue3 -- 基础语法

0. V2 和 V3 的API 对比

1. OptionsAPI 和 CompositionAPI

  1. V2API 设计是 Options (配置) 风格
  2. V3API 设计是 Composition (组合) 风格

2. Options API 的弊端

Options 类型的 API, 其数据、方法、计算属性等, 是分散在 datamethodscomputed 中的, 若想要新增或者修改一个需求, 则需要分别修改 datamethodscomputed, 不便于维护和复用

3. Composition API 的优势

使用传统OptionsAPI中,新增或者修改一个需求,就可能需要分别在data.methods,computed里都修改一遍,代码太分散

使用Composition API 可以更加优雅的组织代码,函数,让相同功能的代码更加有序的组织在一起

1. setup()

0. 概述和注意项

Vue3.0中的一个新的配置项,值为一个函数,setup是所有的 Composition API 表演的舞台,组件中所用到的数据、方法等等,均要配置在setup中

setup的两种返回值

  1. 若返回一个对象,则对象中的属性,方法在模版中均可以直接使用
  2. 若返回一个渲染函数,则可以自定义渲染内容

注意点

  1. 尽量不要与Vue2.x配置混匀
    1. Vue2.x配置的(data,methods,computed..)中可以访问到setup中的属性和方法
    2. 但在setup中不能访问到Vue2.x配置的(data,methods,computed..)
    3. 如果有vue2定义的数据和vue3定义的数据重名,setup优先
  2. setup的异步必须和Suspense和异步组件配合使用,否则会因为加了async函数后返回值不再是return的对象,而是promise,模版看不到return对象中的属性

1. 执行时机

beforeCreate() 前执行一次,此时的 thisundefined

2. 参数

  1. props: 值为对象,包含: 组件外部传递过来,且在组件内使用props:["a","b","c"]声明接收了的属性
  2. context: 上下文对象
    1. context.attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
    2. context.slots: 接收的插槽内容,相当于this.$slots
    3. context.emit: 触发自定义事件的函数,相当于this.$emit

3. 代码示例

1. 返回对象(常用)

App.vue

<template>
    <div class="app">
        <h2>姓名: {{ name }}</h2>
        <h2>年龄: {{ age }}</h2>
        <button @click="showName">展示名字</button>
        <button @click="changeAge">修改年龄</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person',
        setup(){

            // 数据 -- 对应 vue2 中在 data 中定义的数据
            let name = "小明"
            let age = 18
            
            // 方法 -- 对应 vue2 中在 methods 中定义的方法
            function showName(){
                alert(name)
            }

            function changeAge(){
                age += 1   // 由于上面定义的 age 不是响应式, 虽然内存中修改了 age 的值, 但是页面中并没有改变, 需要使用 响应式加工函数 来定义数据
            }

            // 必须返回上面定义的数据和方法, 页面中才能读取到这些数据和方法
            return {
                name,
                age,
                showName
            }
        }
    }
</script>
   
<!-- scoped 表示局部生效 -->
<style scoped>
    .app{
        background-color: aqua;
    }
</style>

2. 返回渲染函数(不常用)

App.vue

<template>
    <button @click="sayHello">点击弹窗</button>
</template>

<script>
    import {h} from 'vue'

    export default {
        name: 'App',
        setup() {
            
            // 返回渲染函数(不常用)
            return () => {
                return h("h1","小明")
            }
        }
    }
</script>

<style>
</style>

4. 语法糖

配置语法糖后, 不再需要手动 return

<template>
    <div class="app">
        <h2>姓名: {{ name }}</h2>
        <h2>年龄: {{ age }}</h2>
        <button @click="showName">展示名字</button>
        <button @click="changeAge">修改年龄</button>
    </div>
</template>

<script lang="ts">
    export default {
        name: 'Person'
    }
</script>

<!-- 新增一个 script 脚本, 并且标记为 setup , lang="ts", 用来配置组合式API, 它会自动 return 脚本中定义好的数据和方法-->
<script setup lang="ts">

    // 数据 -- 对应 vue2 中在 data 中定义的数据
    let name = "小明"
    let age = 18

    // 方法 -- 对应 vue2 中在 methods 中定义的方法
    function showName(){
        alert(name)
    }

    function changeAge(){
        age ++
    }
</script>
    
<style>
    .app{
        background-color: aqua;
    }
</style>

5. 功能插件

如果文件名就是组件名, 可以不写第一个 script 脚本, 并且不下载当前功能插件,

如果需要可以单独配置组件名, 且想将两个 script 脚本合并, 则需要第三方的插件实现

1. 下载插件

npm i vite-plugin-vue-setup-extend -D

2. 配置插件

vite.config.ts

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 1. 引入插件,名字可以随便起
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
  plugins: [
    vue(),
    VueSetupExtend()   // 2. 注册插件
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

3. 合并 script 代码示例

<template>
    <div class="app">
        <h2>姓名: {{ name }}</h2>
        <h2>年龄: {{ age }}</h2>
        <button @click="showName">展示名字</button>
        <button @click="changeAge">修改年龄</button>
    </div>
</template>

<!-- 通过指定 name="Person" 来指定当前组件的名字 -->
<script setup lang="ts" name="Person">

// 数据 -- 对应 vue2 中在 data 中定义的数据
let name = "小明"
let age = 18

// 方法 -- 对应 vue2 中在 methods 中定义的方法
function showName(){
    alert(name)
}

function changeAge(){
    age ++
}

</script>
    
<style>
    .app{
        background-color: aqua;
    }
</style>

2. 响应式加工函数

1. ref( )

用来加工数据为响应式,得到引用对象, ref 所定义的响应式数据需要 .value 来读取和修改, 如果是用 vscode 来编写代码, 可以使用 volar 插件来自动添加.value 左下角的齿轮图标 => 设置 => 扩展 => volar => 勾选 Auto Insert: Dot Value

1. 使用 ref 加工基本类型

<template>
    <div class="app">
        <h2>姓名: {{ name }}</h2>
        <h2>年龄: {{ age }}</h2>   <!-- 5. Vue3在解析模版的时候发现name是ref对象会自动.value取值 -->
        <button @click="showName">展示名字</button>
        <button @click="changeAge">修改年龄</button>
    </div>
</template>


<script setup lang="ts" name="Person">
    // 1. 引入 ref
    import { ref } from 'vue'

    let name = "小明"
    let age = ref(18)    // 2. 使用 ref 定义想要实现响应式的数据

    function showName() {
        alert(name)   
    }

    function changeAge() {
        console.log(age.value)  // 3. 使用 name.value 来读取响应式数据的值
        age.value += 1   	    // 4. 使用 age.value 来修改响应式数据的值
    }
</script>

<style>
.app {
    background-color: aqua;
}
</style>

2. 使用 ref 加工复杂类型

<template>
  <div class="app">
    <!-- 5. 使用 car.brand 来显示响应式的对象数据 -->
    <h2>品牌: {{ car.brand }}</h2>
    <h2>价格: {{ car.price }} 万</h2>
    <button @click="changeBrand">修改品牌</button>
    <button @click="changePrice">修改价格</button>
    <button @click="changeObj">修改对象</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 ref
import {ref} from "vue";

// 2. 使用 ref 定义想要实现响应式的对象数据
let car = ref({
  brand: "奔驰",
  price: 100
})

function changeBrand() {
  car.value.brand = "小米"
}

function changePrice() {
  console.log(car.value.price)  // 3. 使用 car.value.price 来读取响应式数据
  car.value.price += 50   // 4. 使用 car.value.brand 来修改响应式数据
}

function changeObj(){
  car.value = {brand: "奥拓",price: 5}   // 5. 使用 car.value 来重新分配对象, 不会像 reactive() 一样必须使用 Object.assign()
}
</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

2. reactive() --- 只能加工复杂类型数据

只能定义对象类型的响应数据,基本数据类型使用ref(),得到proxy对象

1. 基本使用

<template>
  <div class="app">
    <!-- 5. 使用 car.brand 来显示响应式的对象数据 -->
    <h2>品牌: {{ car.brand }}</h2>
    <h2>价格: {{ car.price }} 万</h2>
    <button @click="changeBrand">修改品牌</button>
    <button @click="changePrice">修改价格</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 reactive
import {reactive} from "vue";

// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
  brand: "奔驰",
  price: 100
})

function changeBrand() {
  car.brand = "小米"   
}

function changePrice() {
  car.price += 50   // 3. 使用 car.brand 来修改响应式数据
  console.log(car.price)  // 4. 使用 car.price 来读取响应式数据
}
</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

2. 多属性对象

简写形式, 将所有的数据类型放在一个 对象 中, 并用 reactive 将其定义为 响应式数据

<template>
    <h3>姓名{{person.name}}</h3>
    <h3>年龄{{person.age}}</h3>
    <h3>工作{{person.job.type}}</h3>
    <h3>薪水{{person.job.salary}}</h3>
    <ul>
        <li v-for="(h,i) in person.hobby" :key="i">{{h}}</li>
    </ul>
    <button @click="changeInfo">点击修改人的信息</button>
</template>

<script>
    // 1. 引入reactive
    import {reactive} from 'vue'

    export default {
        name: 'App',
        setup() {
            // 用 reactive 定义一个对象类型数据的proxy代理整个 person 对象,其中含很多想要的数据,和vue2中的data定义形式类似
            let person = reactive({
                name: "张三",
                age: 18,
                job: {
                    type: "前端工程师",
                    salary: "30k"
                },
                hobby: ["抽烟", "喝酒", "烫头"]
            })


            function changeInfo() {
                person.name = "小明"
                person.age = 20
                person.job.type = "后端工程师"
                person.job.salary = "50k"
                person.hobby[0] = "吃饭"
            }

            // 返回对象
            return {
                person,
                changeInfo
            }
        }
    }
</script>

<style>
</style>

3. reactive() 的坑

当 被reactive定义的响应对象被重新分配新对象时, 会 失去 响应式 (可以使用 Object.assign() 去整体替换)

<template>
  <div class="app">
    <!-- 5. 使用 car.brand 来显示响应式的对象数据 -->
    <h2>品牌: {{ car.brand }}</h2>
    <h2>价格: {{ car.price }} 万</h2>
    <button @click="changeObj">修改对象</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 reactive
import {reactive} from "vue";

// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
  brand: "奔驰",
  price: 100
})

function changeObj(){
  // car = {brand: "奥拓",price: 5}   // 不能直接赋值给响应式数据 car, 这样会导致 car 失去响应式
  Object.assign(car,{brand: "奥拓",price: 5})  // 使用 Object.assign 来整体替换,以保持它的响应式
}
</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

4. toRef()

创建一个 ref 对象,其value值指向另一个对象中的某个属性值, 应用于将响应式对象中的某个属性单独提供给外部使用时

const name = toRef(person,"name")

代码示例

<template>
  <div class="app">
    <!-- 8. 使用 car.brand 来显示响应式的对象数据 -->
    <h2>品牌: {{ car.brand }}</h2>
    <h2>价格: {{ car.price }} 万</h2>
    <button @click="changeBrand">修改品牌</button>
    <button @click="changePrice">修改价格</button>
    <button @click="changeObj">修改对象</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 ref
import {reactive, toRef, toRefs} from "vue";

// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
  brand: "奔驰",
  price: 100
})

// 3. 将由 reactive 定义的每一组响应式数据取出, 使用 toRef 来响应式的代理, 并赋值给 brand, 此时的 brand 也是响应式数据
let brand = toRef(car,"brand")
// 4. 将由 reactive 定义的每一组响应式数据取出, 使用 toRef 来响应式的代理, 并赋值给 price, 此时的 price 也是响应式数据
let price = toRef(car,"price")

function changeBrand() {
  brand.value = "小米"     // 5. 使用 brand.value 来修改响应式数据
}

function changePrice() {
  console.log(price.value)  // 6. 使用 price.value 来读取响应式数据
  price.value += 50         // 7. 使用 price.value 来修改响应式数据
}

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

5. toRefs()

创建一个 ref 对象,其value值指向另一个对象中的某个属性值, 应用于将响应式对象中的某个属性单独提供给外部使用时

// 批量创建多个 ref 对象
const name = toRefs(person,"name")

代码示例

<template>
  <div class="app">
    <!-- 7. 使用 car.brand 来显示响应式的对象数据 -->
    <h2>品牌: {{ car.brand }}</h2>
    <h2>价格: {{ car.price }} 万</h2>
    <button @click="changeBrand">修改品牌</button>
    <button @click="changePrice">修改价格</button>
    <button @click="changeObj">修改对象</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 ref
import {reactive,toRefs} from "vue";

// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
  brand: "奔驰",
  price: 100
})

// 3. 将由 reactive 定义的每一组响应式数据取出, 使用 toRefs 来响应式的代理, 解构赋值取出响应式数据 brand、price
let {brand, price} = toRefs(car)

function changeBrand() {
  brand.value = "小米"     // 4. 使用 brand.value 来修改响应式数据
}

function changePrice() {
  console.log(price.value)  // 5. 使用 price.value 来读取响应式数据
  price.value += 50         // 6. 使用 price.value 来修改响应式数据
}

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

3. 开发中如何使用

  1. 如果想定义一个基本类型的响应式数据, 必须使用 ref
  2. 如果想定义一个对象类型的响应式数据, 并且层级不深, refreactive 都可以
  3. 如果想定义一个对象类型的响应式数据, 并且层级较深, 推荐使用 reactive

4. 响应式原理

1. ref() 响应式原理

1. 基本数据类型

ref函数对于基本数据类型使用的是数据劫持 getter setter 来实现的响应式

2. 复杂数据类型

ref函数对于复杂数据类型,求助了一个新函数--reactive函数,底层使用的是ES6中的 proxy 来实现的响应式

2. V2和V3 响应式对比

1. Vue2.x的响应式原理

  1. 对象类型: 通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
  2. 数组类型: 通过重写push等操作数组的一系列方法来实现拦截(对数组的操作方法进行了重写)
  3. 存在的问题:
    1. 新增属性、删除属性,模板不会自动更新
    2. 直接通过下表修改数组,模板不会自动更新

2. Vue3.x的响应式原理

  1. 实现原理
    1. 通过Proxy(代理): 拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等
    2. 通过Reflect(反射): 对被代理对象的属性进行操作

3. v2和v3的响应式实现对比

1. v2中的响应式

// 模拟vue2中实现响应式
let person = {
    name:"小明",
    age:18
}

let p = {}

Object.defineProperty(p,"name",{
    configurable:true,   // 定义可以删除某个属性
    get(){   // 有人读取person中的name属性时调用
        return person.name
    },
    set(value){  // 有人修改name时调用
        person.name = value
    }
})

Object.defineProperty(p,"age",{
    get(){   // 有人读取person中的age属性时调用
        return person.age
    },
    set(value){  // 有人修改age时调用
        // 这里执行页面更新的逻辑
        person.age = value
    }
})

2. v3中的 Proxy (代理)

// 模拟vue3中实现响应式
let person = {
    name: "小明",
    age: 18
}
const p = new Proxy(person, {
    get(target,propName) {  // 当有人读取person身上的某个属性时调用,target就是person元对象,propName是操作的属性名
        console.log(`有人读取了p身上的${propName}属性`)
        return target[propName]
    },
    set(target,propName,value) { // 当有人修改或追加person身上的某个属性时调用,target就是person元对象,propName是操作的属性名
        console.log(`有人修改了p身上的${propName}属性,值为${value},我要重新渲染页面了`)
        target[propName] = value
    },
    deleteProperty(target, propName) {  // 当有人删除person身上的某个属性时调用
        console.log(`有人修改了p身上的${propName}属性,我要重新渲染页面了`)
        return delete target[propName]
    }
})

3. v3中的 Proxy + Reflect (反射)

// 模拟vue3中实现响应式
let person = {
    name: "小明",
    age: 18
}
const p = new Proxy(person, {
    get(target,propName) {
        console.log(`有人读取了p身上的${propName}属性`)
        return Reflect.get(target,propName)  // 调用反射来真正操作数据的增删改查,不会因为调用错误而直接导致线程的终止
    },
    set(target,propName,value) { 
        console.log(`有人修改了p身上的${propName}属性,值为${value},我要重新渲染页面了`)
        return Reflect.set(target,propName,value)
    },
    deleteProperty(target, propName) {
        console.log(`有人修改了p身上的${propName}属性,我要重新渲染页面了`)
        return Reflect.deleteProperty(target,propName)
    }
})

3. computed() --- 计算属性

1. 只读形式

<template>
  <div class="app">
    <!-- 4. 双向绑定 -->
    <div>姓: <input type="text" v-model="person.firstName"></div>
    <div>名: <input type="text" v-model="person.lastName"></div>
    <br>
    <!-- 5. 模板中使用 fullName 计算属性 -->
    <span>全名: {{ fullName }}</span>

    <button @click="changeFirstName">修改姓</button>
    <button @click="changeLastName">修改名</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 computed, reactive
import {computed, reactive} from "vue";

// 2. 使用 reactive 定义想要实现响应式的对象数据
let person = reactive({
  firstName: "",
  lastName: ""
})

// 3. 定义计算属性 fullName, 但是这样定义的计算属性是只读, 不允许修改的
let fullName = computed(() => {
  return person.firstName + " " + person.lastName
})

function changeFirstName() {
  person.firstName = "王";
}

function changeLastName() {
  person.lastName = "二";
}

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

2. 可读可写

<template>
  <div class="app">
    <!-- 4. 双向绑定 -->
    <div>姓: <input type="text" v-model="person.firstName"></div>
    <div>名: <input type="text" v-model="person.lastName"></div>
    <br>
    <!-- 5. 模板中使用 fullName 计算属性 -->
    <span>全名: {{ fullName }}</span>

    <button @click="changeFirstName">修改姓</button>
    <button @click="changeLastName">修改名</button>
    <button @click="changeFullName">修改全名</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 引入 computed, reactive
import {computed, reactive} from "vue";

// 2. 使用 reactive 定义想要实现响应式的对象数据
let person = reactive({
  firstName: "",
  lastName: ""
})

// 3. 定义计算属性 fullName
let fullName = computed({
  get() {
    return person.firstName + "-" + person.lastName
  },
  set(value) {
    const [firstName, lastName] = value.split("-")
    person.firstName = firstName;
    person.lastName = lastName;
  }
})

function changeFirstName() {
  person.firstName = "王";
}

function changeLastName() {
  person.lastName = "二";
}

function changeFullName(){
  fullName.value = "张-三"   // 4. 使用 fullName.value 来修改 fullName
}

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

4. watch() --- 监视属性

Vue3 中的 watch() 只能监视以下四种数据:

  1. ref 定义的数据
  2. reactive 定义的数据
  3. 如果需要监视的属性值不是对象类型, 需要写成函数形式
  4. 一个包含上述内容的数组

1. 监视 ref 响应式数据

1. 监视一个基本类型

<template>
  <div class="app">
   <h3>{{ sum }}</h3>
    <button @click="changeSum">点我 + 1</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";

// 2. 定义 sum 响应式数据
let sum = ref(0)

function changeSum() {
  sum.value += 1;
}

// 3. 监视 sum 响应式数据的变化
watch(sum, (newValue, oldValue) => {
  console.log(newValue,oldValue)
})

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

2. 监视多个基本类型

<template>
  <div class="app">
    <h3>{{ sum }}</h3>
    <button @click="changeSum">点我 + 1</button>
    <br>
    <h3>当前的信息是: {{ msg }}</h3>
    <button @click="changeMsg">点我 + !</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";

// 2. 定义 sum 和 msg 响应式数据
let sum = ref(0)
let msg = ref("天气真好啊")

function changeSum() {
  sum.value += 1;
}

function changeMsg() {
  msg.value += "!"
}

// 3. 同时监视 sum 和 msg 响应式数据的变化
watch([sum, msg], (newValue, oldValue) => {
  console.log("sum的值变了", newValue[0], oldValue[0])
  console.log("msg的值变了", newValue[1], oldValue[1])
})

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

3. 监视复杂类型对象

<template>
  <div class="app">
    <h2>姓名: {{person.name}}</h2>
    <h2>年龄: {{person.age}}</h2>
    <h2>薪水: {{person.job.salary}} 万元</h2>
    <button @click="changeName">修改姓名</button>
    <button @click="changeAge">增长年龄</button>
    <button @click="changeSalary">涨薪</button>
    <button @click="changePerson">修改人</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";

// 2. 定义 person 响应式数据
let person = ref({
  name: "张三",
  age: 18,
  job:{
    salary:20
  }
})

// 3. 修改整个 person 对象
function changePerson(){
  person.value = {
    name: "李四",
    age: 20,
    job:{
      salary:10
    }
  }
}

// 4. 监视整个 person 对象的变化
watch(person, (newValue, oldValue) => {
  console.log("person的值变了", newValue, oldValue)
})

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

4. 监视复杂类型对象 内部数据

1. 属性值不是对象类型

<template>
    <div class="app">
        <h2>姓名: {{person.name}}</h2>
        <h2>年龄: {{person.age}}</h2>
        <h2>薪水: {{person.job.salary}} 万</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">增长年龄</button>
        <button @click="changePerson">修改人</button>
        <button @click="changeSalary">涨薪</button>
    </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {reactive, watch} from "vue";

// 2. 定义 person 响应式数据
let person = reactive({
  name: "张三",
  age: 18,
  job:{
    salary:20
  }
})

// 3. 修改 person 对象内部的数据
function changeName(){
  person.value.name = "小明"
}

function changeAge() {
  person.value.age += 1
}

function changePerson(){
  person.value = {
    name: "李四",
    age: 20,
  }
}

// 4. 监视 person 对象 及 内部数据的变化, 需要手动开启深度监视
watch(person, (newValue, oldValue) => {
  // 如果修改的是 person 内部的数据, `newValue` 此处和 `oldValue` 是相等的, 都是最新的值, 而监视不到旧的值, 因为它们是同一个对象!
  // 如果修改的是 person 对象, `newValue` 是新的对象  `oldValue` 是旧的对象
  console.log("person的值变了", newValue, oldValue)
},{deep:true})

    
// 5. 监听 person 对象内部的 name 的变化
watch(() => person.name, (newValue, oldValue) => {
  console.log("person的值变了", newValue, oldValue)
})


// 6. 监听 person 对象内部的 obj 中 salary 的变化
watch(()=>person.job.salary, (newValue, oldValue) => {
  console.log("person的值变了", newValue, oldValue)
},{deep:true})
</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

2. 属性值是对象类型

<template>
    <div class="app">
        <h2>姓名: {{person.name}}</h2>
        <h2>年龄: {{person.age}}</h2>
        <h2>薪水: {{person.job.salary}} 万</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">增长年龄</button>
        <button @click="changePerson">修改人</button>
        <button @click="changeSalary">涨薪</button>
        <button @click="changeJob">修改工作</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    // 1. 导入 ref watch
    import {reactive, watch} from "vue";

    // 2. 定义 person 响应式数据
    let person = reactive({
        name: "张三",
        age: 18,
        job: {
            salary: 20
        }
    })

    // 3. 修改 person 对象内部的数据
    function changeName() {
        person.name = "小明"
    }

    function changeAge() {
        person.age += 1
    }

    function changePerson() {
        person.job = {
            salary: 25
        }
    }

    function changeSalary() {
        person.job.salary += 1
    }
    
    function changeJob() {
  	  person.job = {
	    salary: 25
  	  }
	}

    // 4. 监听 [reactive] 定义的 [对象类型] 数据内部的 job 的变化, 默认是开启深度监视的, 且无法被关闭
    watch(() => person.job, (newValue, oldValue) => {
        console.log("person的值变了", newValue, oldValue)
    })
    
    // 5. 监听 [reactive] 定义的 [对象类型] 数据内部的 job 及其内部 的变化, 需要手动再次开启深度监视
    watch(() => person.job, (newValue, oldValue) => {
        console.log("person的值变了", newValue, oldValue)
    },{deep: true})
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

5. 监视多个复杂类型对象

<template>
    <div class="app">
        <h2>姓名: {{person.name}}</h2>
        <h2>年龄: {{person.age}}</h2>
        <h2>薪水: {{person.job.salary}} 万</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">增长年龄</button>
        <button @click="changePerson">修改人</button>
        <button @click="changeSalary">涨薪</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    // 1. 导入 ref watch
    import {reactive, watch} from "vue";

    // 2. 定义 person 响应式数据
    let person = reactive({
        name: "张三",
        age: 18,
        job: {
            salary: 20
        }
    })

    // 3. 修改 person 对象内部的数据
    function changeName() {
        person.name = "小明"
    }

    function changeAge() {
        person.age += 1
    }

    function changePerson() {
        Object.assign(person, {
            name: "李四",
            age: 20,
        })
    }

    function changeSalary() {
        person.job.salary += 1
    }

    // 6. 监听 多个 [reactive] 定义的 [对象类型] 数据内部数据, 默认是开启深度监视的, 且无法被关闭
    watch([() => person.job.salary, () => person.name, () => person.age], (newValue, oldValue) => {
        console.log("person的值变了", newValue, oldValue)
    })
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

6. 总结

如果监视的是对象中的属性值, 直接用函数形式 ( () => person.name ), 如果监视的是嵌套对象, 默认是不监视嵌套对象内部属性值的变化, 如果想见识, 需要手动添加配置开启深度监视 { deep: true}

2. 监视 reactive 响应式数据

<template>
    <div class="app">
        <h2>姓名: {{person.name}}</h2>
        <h2>年龄: {{person.age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">增长年龄</button>
        <button @click="changePerson">修改人</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    // 1. 导入 ref watch
    import {reactive, watch} from "vue";

    // 2. 定义 person 响应式数据
    let person = reactive({
        name: "张三",
        age: 18,
        job: {
            salary: 20
        }
    })

    // 3. 修改 person 对象内部的数据
    function changeName() {
        person.name = "小明"
    }

    function changeAge() {
        person.age += 1
    }

    function changePerson() {
        Object.assign(person, {
            name: "李四",
            age: 20,
        })
    }


    // 4. 监视 [reactive] 定义的 [对象类型] 数据, 默认是开启深度监视的, 且无法被关闭
    watch(person, (newValue, oldValue) => {
        // 如果修改的是 person 内部的数据, `newValue` 此处和 `oldValue` 是相等的, 都是最新的值, 而监视不到旧的值, 因为它们是同一个对象(地址没发生变化)!
        // 如果修改的是 person 对象, `newValue` 是新的对象  `oldValue` 是旧的对象
        console.log("person的值变了", newValue, oldValue)
    }, {deep: true})


    // 5. 监听 [reactive] 定义的 [对象类型] 数据内部的 name 的变化, 默认是开启深度监视的, 且无法被关闭
    watch(() => person.name, (newValue, oldValue) => {
        console.log("person的值变了", newValue, oldValue)
    })
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

3. 深度监视

对于需要监视 ref 定义的 复杂数据类型 及 其内部数据 的变化, 需要手动开启深度监视

<template>
  <div class="app">
    <h2>姓名: {{person.name}}</h2>
    <h2>年龄: {{person.age}}</h2>
    <h2>薪水: {{person.job.salary}} 万元</h2>
    <button @click="changeName">修改姓名</button>
    <button @click="changeAge">增长年龄</button>
    <button @click="changeSalary">涨薪</button>
    <button @click="changePerson">修改人</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";

// 2. 定义 person 响应式数据
let person = ref({
  name: "张三",
  age: 18,
  job:{
    salary:20
  }
})

function changeName(){
  person.value.name = "小明"
}

function changeAge() {
  person.value.age += 1
}

function changeSalary() {
  person.value.job.salary += 1
}

function changePerson(){
  person.value = {
    name: "李四",
    age: 20,
    job:{
      salary:10
    }
  }
}

// 3. 监视 person 对象 及 内部数据的变化, 需要手动开启深度监视
watch(person, (newValue, oldValue) => {
  console.log("person的值变了", newValue, oldValue)
},{deep:true})

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

对于 reactive 定义的 复杂数据类型 及 其内部数据 的变化, 默认开启深度监视, 且无法关闭

<template>
    <div class="app">
        <h2>姓名: {{person.name}}</h2>
        <h2>年龄: {{person.age}}</h2>
        <h2>薪水: {{person.job.salary}} 万</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">增长年龄</button>
        <button @click="changePerson">修改人</button>
        <button @click="changeSalary">涨薪</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    // 1. 导入 ref watch
    import {reactive, watch} from "vue";

    // 2. 定义 person 响应式数据
    let person = reactive({
        name: "张三",
        age: 18,
        job: {
            salary: 20
        }
    })

    // 3. 修改 person 对象内部的数据
    function changeName() {
        person.name = "小明"
    }

    function changeAge() {
        person.age += 1
    }

    function changePerson() {
        Object.assign(person, {
            name: "李四",
            age: 20,
        })
    }
    
    function changeSalary(){
        person.job.salary += 1
    }


    // 4. 监视 [reactive] 定义的 [对象类型] 数据, 默认是开启深度监视的, 且无法被关闭
    watch(person, (newValue, oldValue) => {
        // 如果修改的是 person 内部的数据, `newValue` 此处和 `oldValue` 是相等的, 都是最新的值, 而监视不到旧的值, 因为它们是同一个对象(地址没发生变化)!
        // 如果修改的是 person 对象, `newValue` 是新的对象  `oldValue` 是旧的对象
        console.log("person的值变了", newValue, oldValue)
    }, {deep: true})


    // 5. 监听 [reactive] 定义的 [对象类型] 数据内部的 name 的变化, 默认是开启深度监视的, 且无法被关闭
    watch(() => person.name, (newValue, oldValue) => {
        console.log("person的值变了", newValue, oldValue)
    })
    
    // 6. 监听 [reactive] 定义的 [对象类型] 数据内部的 job 中的 salary 的变化, 默认是开启深度监视的, 且无法被关闭
    watch(() => person.job.salary, (newValue, oldValue) => {
        console.log("person的值变了", newValue, oldValue)
    })
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

3. 首次监视

首次变化是指当页面渲染的时候,触发一次监视, 及其之后的每次变化

<template>
  <div class="app">
    <h2>姓名: {{person.name}}</h2>
    <h2>年龄: {{person.age}}</h2>
    <h2>薪水: {{person.job.salary}} 万元</h2>
    <button @click="changeName">修改姓名</button>
    <button @click="changeAge">增长年龄</button>
    <button @click="changeSalary">涨薪</button>
    <button @click="changePerson">修改人</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";

// 2. 定义 person 响应式数据
let person = ref({
  name: "张三",
  age: 18,
  job:{
    salary:20
  }
})

function changeName(){
  person.value.name = "小明"
}

function changeAge() {
  person.value.age += 1
}

function changeSalary() {
  person.value.job.salary += 1
}

function changePerson(){
  person.value = {
    name: "李四",
    age: 20,
    job:{
      salary:10
    }
  }
}

// 3. 监视整个 person 对象的首次变化及以后得每次变化 
watch(person, (newValue, oldValue) => {
  console.log("person的值变了", newValue, oldValue)
},{immediate:true})

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

4. 停止监视

<template>
  <div class="app">
    <h3>{{ sum }}</h3>
    <button @click="changeSum">点我 + 1</button>
  </div>
</template>

<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";

// 2. 定义 sum 响应式数据
let sum = ref(0)

function changeSum() {
  sum.value += 1;
}

// 3. 监视 sum 响应式数据的变化
const stopWatch = watch(sum, (newValue, oldValue) => {
  console.log(newValue, oldValue)
  if (newValue === 10){  // 当 sum 的值为 10 时, 停止监视
    stopWatch()
  }
})

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

6. watchEffect()

  1. watch的写法是: 既要指明监视的属性,也要指明监视的回调
  2. watchEffect的写法是: 不用指明监视哪个属性, 回调函数中用到哪个属性,监视的就是哪个属性
  3. watchEffect有点像computed的执行流程:
    1. 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
    2. 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

**需求: ** 当 水温达到 6℃ 或者 水位到达 40ml 时, 发送一个请求

<template>
    <div class="app">
        <h2>当前水位: {{wHeight}}ml</h2>
        <h2>当前水温: {{temp}}℃</h2>
        <button @click="changeWheight">水位+10</button>
        <button @click="changeTemp">水温+2</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    import {ref, watch, watchEffect} from 'vue'

    let temp = ref(0)
    let wHeight = ref(0)

    function changeTemp() {
        temp.value += 2
    }

    function changeWheight() {
        wHeight.value += 10
    }

    // watch --- 必须显式的定义, 监视多个值的变化
    watch([temp, wHeight], function (value) {
        let [newTemp, newWheight] = value
        if (newTemp >= 6 || newWheight >= 40) {
            console.log("发送了请求")
        }
    })

    // watchEffect --- 会自动监视逻辑中用到的数据的变化, 并且会首次执行一次
    watchEffect(() => {
        if (temp.value >= 6 || wHeight.value >= 40){
            console.log("发送了请求")
        }
    })

</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

5. ref 属性

1. 加在 HTML 标签上

需求: 如何在组件中取出 DOM 元素

<template>
    <div class="app">
        <!-- 1. 给标签绑定 id 属性 -->
        <h2 id="temp">当前水位</h2>
        <h2>当前水温</h2>
        <button @click="showDom">获取 DOM 元素</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    function showDom(){
        // 2. 获取到 id="temp" 的 DOM 元素
        console.log(document.getElementById('temp'))
    }
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

1. 假如其他页面中也有 id='temp' 的标签, 当页面全部渲染成功后, 就会冲突, 此时可以使用 ref 属性 来标记定义

<template>
    <div class="app">
        <!-- 1. 使用 ref属性, 打标记 -->
        <h2 ref="temp">当前水位</h2>
        <h2>当前水温</h2>
        <button @click="showDom">获取 DOM 元素</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    import {ref} from 'vue'
	// 2. 声明 temp 来存储 ref 所标记的内容
    let temp = ref()
    function showDom(){
        // 3. 使用 temp.value 来获取 DOM 元素
        console.log(temp.value)
    }
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

2. 加在 组件 标签上 ( 子向父传值)

APP.vue

<template>
    <!-- 1. 用 ref 在组件上面做标记, 得到的是 组件实例 -->
    <Person ref="ren"/>
    <button @click="showRefMessage">输出 ref 自定义数据</button>
</template>


<script lang="ts" setup name="App">
    import Person from './components/Person.vue'
    import {ref} from 'vue'

    let ren = ref()

    function showRefMessage() {
        // 得到的是 组件实例
        console.log(ren.value)
        
        // 获取子组件中的数据
        console.log(ren.value.a, ren.value.b, ren.value.c)
    }

</script>

Person.vue

<template>
    <div class="app">
        <h2 ref="temp">当前水位</h2>
        <h2>当前水温</h2>
        <button @click="showDom">获取 DOM 元素</button>
        <button @click="showABC">获取 abc 的值</button>
    </div>
</template>

<script setup lang="ts" name="Person">
    import {defineExpose, ref} from 'vue'   // defineExpose 可以不引入, vue 内部已经缓存了这个函数

    let temp = ref()
    let a = ref(0)
    let b = ref(1)
    let c = ref(2)

    function showDom() {
        console.log(temp.value)
    }

    function showABC() {
        console.log(a.value, b.value, c.value)
    }

    // 指定向父组件暴露的数据
    defineExpose({a, b, c})
</script>

<style scoped>
    .app {
        background-color: aqua;
    }
</style>

6. Vue 中对 TS 的应用

1. 接口

src/types/index.ts

export interface Person {
    id: string
    name: string;
    age: number;
}

src/components/Person.vue

<template>
  <div class="app">
  </div>
</template>

<script setup lang="ts" name="Person">
import {type Person} from '@/types'

// 限制 personList 符合 Person 接口定义的规范
let person: Person = {
  id: "asdfasdgasd",
  name: "小明",
  age: 20
}

// 限制 personList 是一个数组, 并且其中的每一项都符合 Person 接口定义的规范
let personList: Array<Person> = [
  {id: "asdfasdgasd01", name: "小明", age: 20},
  {id: "asdfasdgasd02", name: "小红", age: 15},
  {id: "asdfasdgasd03", name: "小白", age: 10}
]

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

上面数组类型的简写形式

src/type/index.ts

export interface Person {
    id: string
    name: string;
    age: number;
    x?: number;     // x? 表示 x 这个参数不是必填项
}

// 定义符合 Person 接口规范的数组类型
export type PersonList = Array<Person>;   // 写法一
export type PersonList = Person[];           // 写法二

src/components/Person.vue

<template>
  <div class="app">
  </div>
</template>

<script setup lang="ts" name="Person">
import {reactive} from "vue";
import {type PersonList} from '@/types'

// 限制 personList 是一个数组, 并且其中的每一项都符合 Person 接口定义的规范, 并使用 reactive 将其变成响应式数据
let personList = reactive<PersonList>([
  {id: "asdfasdgasd01", name: "小明", age: 20},
  {id: "asdfasdgasd02", name: "小红", age: 15},
  {id: "asdfasdgasd03", name: "小白", age: 10}
])

</script>

<style scoped>
.app {
  background-color: aqua;
}
</style>

7. 生命周期对比

0. 对比图

1. 配置项式 生命周期

App.vue

<template>
    <button @click="isShow = !isShow">显示/隐藏</button>

    <Home v-if="isShow"/>
</template>

<script>
    import {ref} from 'vue'
    import Home from "@/components/Home";

    export default {
        name: 'App',
        // eslint-disable-next-line vue/no-unused-components
        components:{Home},
        setup(){
            let isShow = ref(true)

            return {
                isShow
            }
        }

    }
</script>

<style>
</style>

Home.vue

<template>
    <h3>名字: {{person.name}}</h3>
    <h3>年龄: {{person.age}}</h3>
    <h3>薪水: {{person.job.salary}}</h3>
    <button @click="person.job.salary++">点我涨薪</button>
</template>

<script>
    import {reactive, ref} from "vue";

    export default {
        // eslint-disable-next-line vue/multi-word-component-names
        name: "Home",
        setup() {
            let sum = ref(0)
            let person = reactive({
                name: "小明",
                age: 18,
                job: {
                    salary: 20
                }
            })
            return {
                sum,
                person,
            }
        },

        // 创建前
        beforeCreate() {
            console.log("----beforeCreate----")
        },
        
        // 创建完毕
        created() {
            console.log("----created----")
        },
        
        // 挂载前
        beforeMount() {
            console.log("----beforeMount----")
        },
        
		// 挂载完毕
        mounted() {
            console.log("----mounted----")
        },
        
		// 更新前
        beforeUpdate() {
            console.log("----beforeUpdate----")
        },
        
		// 更新完毕
        updated() {
            console.log("----updated----")
        },
        
		// 卸载前
        beforeUnmount() {
            console.log("----beforeUnmount----")
        },
        
		// 卸载完毕
        unmounted() {
            console.log("----unmounted----")
        },

    }
</script>

<style scoped>

</style>

2. 组合式 生命周期

  1. Vue3中可以继续使用Vue2中以配置项的形式定义生命周期钩子,但有两个钩子更名了:
    1. beforeDestroy 改名为 beforeUnmount
    2. destroyed 改名为 unmounted
  2. Vue3 也提供了 Compositon API 形式的生命周期钩子,与配置项形式的对应关系为:
    1. beforeCreate --- setup()
    2. created ----------- setup()
    3. beforeMount ----- onBeforeMount()
    4. mounted ---------- onMounted()
    5. beforeUpdate --- onBeforeUpdate()
    6. updated ----------- onUpdated()
    7. beforeUnmount -- onBeforeUnmount()
    8. unmounted -------- onUnmounted()
<template>
    <h3>名字: {{person.name}}</h3>
    <h3>年龄: {{person.age}}</h3>
    <h3>薪水: {{person.job.salary}}</h3>
    <button @click="person.job.salary++">点我涨薪</button>
</template>

<script setup lang="ts" name="Person">
import {
    onBeforeMount,
    onBeforeUnmount,
    onBeforeUpdate,
    onMounted,
    onUnmounted,
    onUpdated,
    reactive,
    ref
} from "vue";

let sum = ref(0)
let person = reactive({
    name: "小明",
    age: 18,
    job: {
        salary: 20
    }
})

// 挂载前
onBeforeMount(() => {
    console.log("---- onBeforeMount ----")
})
    
// 挂载完毕
onMounted(() => {
    console.log("---- onMounted ----")
})
    
// 更新前
onBeforeUpdate(() => {
    console.log("---- onBeforeUpdate ----")
})
    
// 更新完毕
onUpdated(() => {
    console.log("---- onUpdated ----")
})
  
// 卸载前
onBeforeUnmount(() => {
    console.log("---- onBeforeUnmount ----")
})
    
// 卸载完毕
onUnmounted(() => {
    console.log("---- onUnmounted ----")
})

</script>

<style scoped>

</style>

3. 嵌套组件 生命周期

子组件的所有生命周期执行完毕, 再执行父组件的所有生命周期

8. hooks ( 必写 )

本质是一个函数,把setup函数中使用到的的一系列的Coposition API 封装成一个函数,然后在页面中使用 类似于vue2中的mixin

自定义hook的优势:

  1. 复用代码,让setup中的逻辑更清楚易懂

src/hooks/usePoint.js 名字一般为 useXxx

import {onBeforeUnmount, onMounted, reactive, computed} from "vue";

// 1. 实现鼠标坐标展示的功能
export default function usePoint() {
    let point = reactive({
        x: 0,
        y: 0
    })

    function savePoint(event) {
        point.x = event.pageX
        point.y = event.pageY
        console.log(event.pageX, event.pageY)
    }
	
    
    // hook 函数中可以定义钩子函数
    onMounted(() => {
        window.addEventListener('click', savePoint)
    })

    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })
    
    // hook 函数中可以定义计算属性

    let zPoint = computed(()=>{
        return point.x * 10
    })
	
    // 2. 必须 return
    return {point, savePoint, zPoint}
}

Home.vue

<template>
    <h2>当前点击时鼠标的坐标</h2>
	<!-- 3. 模版中使用 -->
    <span> x: {{point.x}}</span>
    <br>
    <span> y: {{point.y}}</span>
</template>

<script lang="ts" setup name="Home">
    // 1. 导入hook
    import usePoint from '@/hooks/use_point'
    
    // 2. 解构 hooks 中的数据
    let {point} = usePoint()
    
</script>

<style scoped>

</style>

9. 其他常用组合式 API

1. 浅层响应式加工函数

1. shalloReactive()

shalloReactive: 只处理对象最外层属性的响应式(浅响应式)

2. shallowRef()

shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理

3. 应用场景

  1. 如果有一个对象数据,结构比较深,但变化时只是外层属性变化使用 shalloReactive
  2. 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换使用 shallowRef

2. 深浅只读

1. readonly

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

2. shallowReadonly

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

3. 应用场景

应用场景: 不希望数据被修改时

3. 转成非响应式数据

1. toRaw

toRaw : 将一个由 reactive 生成的响应式对象转为普通对象

2. markRaw

markRaw:标记一个对象,使其永远不会再成为响应式对象

3. 应用场景

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

markRaw:

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

4. 自定义响应式数据

1. customRef()

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

2. 案例: 防抖

案例: 实现防抖(延迟显示)效果

<template>
  <input type="text" v-model="keyword">
  <h3>{{ keyword }}</h3>
</template>

<script lang="ts" setup name="Index">
import {customRef,ref} from "vue";

// 自定义一个myRef
function myRef(value:string, delay:number) {
  let timer:number
  // 通过customRef去实现自定义
  return customRef((track, trigger) => {
    return {
      get() {
        track()  // 通知 vue 持续追踪 value 的变化,让 vue 知道 value 是有用的, 一旦数据有所变化, 就要去更新
        return value
      },
      set(newValue) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          value = newValue  // 将原数据修改为最新数据
          trigger()   // 通知 vue 数据已经变了, 重新解析模版
        }, delay)
      }
    }
  })

}

// let keyword = ref("hello")  // 使用Vue内置的ref, 无法实现防抖效果
let keyword = myRef("hello", 500)  // 使用自定义的 myRef 来加工响应式数据, 可以使用逻辑来自定义控制响应式数据
</script>

<style>

</style>

5. 响应式数据的判断

isRef: 检查一个值是不是一个 ref 对象

isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

isProxy: 检查一个对象是否是由 reactive 或者 readonly方法创建的代理

10. 新的组件

1. Fragment ( 虚拟标签 )

在Vue2 中组件必须有一个根标签,在Vue3中组件可以没有根标签,内部会将多个标签包含在一个Fragment组件(虚拟元素)中

目的: 减少标签层级、减小内存占用

2. Teleport ( 将组件移动至指定位置 )

Teleport 是一种能够让 组件HTML结构 移动到指定位置的技术

模态对话框案例

Son.vue

<template>
  <div class="son">
    <Dialog />
  </div>
</template>

<script lang="ts" setup name="Son">
import Dialog from './Dialog.vue';
</script>

<style scoped>
</style>

Dilog.vue

<template>
<div>
    <button @click="isShow = true">弹窗</button>
    <!-- 将以下的标签,传送到body中,只针对body的位置变化而变化,其他组件怎么变都不会影响到模态对话框 -->
    <teleport to="body">
        <div class="mask" v-if="isShow">
            <div class="dialog">
            <h3>我是弹窗</h3>
            <div>账号: <input type="text"></div>
            <div>密码: <input type="password"></div>
            <button @click="isShow = false">关闭</button>
            </div>
        </div>
    </teleport>
</div>
</template>

<script lang="ts" setup name="Dialog">
import {ref} from "vue";

let isShow = ref(false)
</script>

<style scoped>
.mask {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba(0,0,0,0.5);
}

.dialog {
    text-align: center;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-75%,-75%);
    width: 300px;
    height: 300px;
    background-color: green;
}
</style>

3. Suspense ( 标识为异步组件 )

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

**应用场景: ** 子组件中需要执行异步任务的时候, 由于使用了 setup 语法糖, 底层会自动加 async, 但是这样会导致无法正常显示子组件的内容, 此时就需要在父组件中, 用 Suspense 标识子组件是异步组件

1. 基本使用

src/components/Father.vue

<template>
  <h2>我是App</h2>
  <!-- 1. 父组件中使用 Suspense 来表示子组件是一个异步组件 -->
  <Suspense>
    <!-- 2. Suspense 底层是使用插槽实现的  -->
    <template #default>
      <Son />
    </template>
  </Suspense>
</template>

<script lang="ts" setup name="Father">
  import Son from './components/Son.vue';

</script>

<style>
</style>

src/components/Son.vue

<template>
    <h2>我是Home</h2>
</template>

<script lang="ts" setup name="Son">
import axios from 'axios'

// 发送网络请求
const res = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json")
console.log(res.data);
    
</script>

<style scoped>

</style>

2. 解决子组件闪现问题

在网络请求中, 由于有延迟, 子组件会突然出现在页面中, 用户体验不好

src/components/Father.vue

<template>
  <h2>我是App</h2>
  <!-- 1. 父组件中使用 Suspense 来表示子组件是一个异步组件 -->
  <Suspense>
    <!-- 2. 异步任务有结果的时候, 出现 default 中的内容 -->
    <template #default>
      <Home/>
    </template>

    <!-- 3. 异步任务没有结果的时候, 默认出现 fallback 中的内容 -->
    <template #fallback>
      <h2>加载中....</h2>
    </template>
  </Suspense>

</template>

<script lang="ts" setup name="App">
  import Home from './components/Home.vue';

</script>

<style>
</style>

子组件无需变动

3. V2 中的使用步骤

src/components/Father.vue

<template>
    <h2>我是App</h2>
    <Home/>
</template>

<script>
    // 静态引入方式
    // import Home from "@/components/Home";

    // 异步引入方式
    import {defineAsyncComponent} from 'vue'
    const Home = defineAsyncComponent(() => import('@/components/Home'))

    export default {
        name: 'App',
        components: {Home},
    }
</script>

<style>
</style>

1. 异步引入组件

<template>
    <h2>我是App</h2>
    <Home/>
</template>

<script>
    // 静态引入方式
    // import Home from "@/components/Home";

    // 异步引入方式
    import {defineAsyncComponent} from 'vue'
    const Home = defineAsyncComponent(() => import('@/components/Home'))

    export default {
        name: 'App',
        components: {Home},
    }
</script>

<style>
</style>

2. 使用 Suspense 组件解决组件闪现问题

<template>
    <h2>我是App</h2>
    <Suspense>
        <!-- 渲染完成展示的内容,插槽名必须是default -->
        <template v-slot:default>
            <Home/>
        </template>
        <!-- Home组件还没渲染完成之前默认显示的内容,插槽名必须是fallback -->
        <template v-slot:fallback>
            <h2>加载中....</h2>
        </template>

    </Suspense>
</template>

<script>
    // 异步引入方式
    import {defineAsyncComponent} from 'vue'

    const Home = defineAsyncComponent(() => import('@/components/Home'))

    export default {
        name: 'App',
        components: {Home},
        setup() {
        }

    }
</script>

<style>
</style>

Home.vue 中调用异步任务

<template>
    <h2>我是Home</h2>
</template>

<script>
    import {ref} from 'vue'

    export default {
        name: "Home",
        setup() {
            let sum = ref(0)
            return new Promise((resolve, reject) => {
                setTimeout(()=>{
                    console.log(reject)
                    resolve({sum})
                },3000)
            })
        },
        
        // 或者将setup函数变为异步
        async setup() {
            let sum = ref(0)
            let p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log(reject)
                    resolve({sum})
                }, 3000)
            })
            return await p
        },

    }
</script>

<style scoped>

</style>

11. 全局 API 的转移

1. Vue2中有很多全局API和配置

// 例如: 注册全局组件
Vue.component("myButton",{
    data: ()=>{
        count: 0
    },
    template: '<button @click="count++">Clicked {{count}} times.</button>'
})

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

2. Vue3中对这些API做出了调整

将全局API,即 Vue.xxx 调整到应用实例 app 上

Vue2 全局 API (Vue.xxx) Vue3 实例API (app.xxx)
Vue.config.xxx app.config.xxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

12. 非兼容改变

1. data选项

始终被声明为一个函数

2. 过度类名的更改

1. Vue2的写法

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

2. Vue3的写法

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

3. v-if 和 v-for 在同一个元素的优先级与 V2 相反

V2v-ifv-for 是不允许同时出现在同一个标签上的, 因为 v-for 的优先级要比 v-if

V3 中 允许同时出现在同一个标签上, 修改为 v-if 的优先级高于 v-for

4.移除 v-on 的 keyCode 修饰符

移除 keyCode 作为 v-on 的修饰符,同时也不再支持config-keyCodes,因为兼容性差

v-on:enter=""
v-on:13=""   <!-- 被移除 -->

5. 移除 v-on 的 nactive 修饰符

移除 v-on.nactive 修饰符,用来修饰为原生事件

1. 父组件中绑定事件

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

2. 子组件中声明自定义事件

export default {
    emits: ['close']  // 指定自定义事件,未声明的都是原生事件
}

6. 移除 v-bind.sync 修饰符, 将其整合到了 v-model 中

7. 移除 $on $off $once 实例方法

8. 移除过滤器 ( filter )

移除 过滤器(filter) 虽然看起来很方便,但它需要一个自定义语法,虚席成本和实现成本高,建议用方法调用或计算属性替换过滤器

9. 移除了 $children 实例 propert

posted @ 2024-01-18 18:00  河图s  阅读(139)  评论(0)    收藏  举报