更好的阅读体验:点这里 ( www.foooor.com
)
4 Vue3的API
4.1 Vue2和Vue3的API风格
通过前面的 HelloWorld 程序,你如果学习过 Vue2,会发现 Vue3 的 API 和 Vue2 的区别很大。
Vue2 中的称为选项式 API (Options API):
vue
<!-- Vue2 -->
<script>
export default {
data() { // 组件中用到的数据
return {
count: 0
}
},
methods: { // 组件中用到的方法
increment() {
this.count++
}
},
watch:{}, // 监听器
computed:{} // 计算属性
}
</script>
组件中的 data
、methods
、watch
、computed
相当于一个个选项,选项式 API 可能会存在的问题是,一个功能的实现可能分布在多个选项中,不够集中,如果要修改就要分别修改多个选项,所以不易维护。
在 Vue3 中的称为组合式 API (Composition API),后面我们将以这种方式来学习,其实在 Vue3 中也是可以以选项式 API 来编程的,也就是 Vue3 是兼容 Vue2 的语法。
在 HelloWorld 中使用了 setup
选项,setup
选项的执行时机是很早的,比组件生命周期函数 beforeCreate 还要早(生命周期后面再讲),所以在 Vue2 的选项式 API 中是可以获取到 setup
返回的信息的,但是 setup
中无法获取 Vue2 选项中的数据。
举个栗子:
如果你有 Vue2 的基础,肯定能看得懂下面的代码;如果你没有 Vue2 的基础,也没有关系,这里是一个比较,后面我们还是以 Vue3 中的组合式 API 进行学习,但是老的项目肯定是 Vue2 的,如果有时间,可以多了解一些。
看一下下面的代码,在 Vue2 的选项式 API 中获取 Vue3 组合式 API 中的数据:
vue
<template>
<!-- 结构 -->
<div class="my-app">
<button @click="printMsg">打印msg</button>
</div>
</template>
<script lang="ts">
// 脚本
export default {
name: "App",
methods: {
printMsg() {
console.log(this.msg); // 通过this.name获取
}
},
setup() {
let msg = "Hello world";
return {
"msg": msg,
}
}
}
</script>
4.2 setup
前面 HelloWorld 中已经使用了 setup
, 它是 Vue3 中新的配置项,它的值是一个函数,在 Vue3 中,组件的数据、方法、计算属性、监视器等等,都配置在 setup
中。
setup 主要有以下特点,前面也介绍了几个:
- setup 函数返回的对象,可以直接在
<template>
模板中使用; - setup 函数会在
beforeCreate
之前执行,也就是在所有的生命周期回调函数之前执行; - setup 函数中是没有 this 的,这个和 Vue2 中的有区别;
下面来实现一个功能,点击页面的按钮,计数+1。
代码如下:
vue
<template>
<!-- 结构 -->
<div class="my-app">
<div>{{ count }}</div>
<button @click="increment">增加</button>
</div>
</template>
<script lang="ts">
import { ref } from 'vue'
// 脚本
export default {
name: "App",
setup() {
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
return {count, increment}
}
}
</script>
在上面的代码中:
const count = ref(0)
定义一个响应式变量count
,初始值为 0。count
实际上是一个对象,这个对象内部有一个.value
属性,它持有实际的数值(在这个例子中是0
)。- 在 setup 中将
count
和increment()
函数返回,这样就可以在 HTML 模板中使用它们了。 - 在模板中,通过差值表达式
{``{ count }}
读取到count
的值,通过@click
给元素绑定事件(后面再讲),当点击按钮时,调用increment
函数,修改count
的值,count
是响应式变量,修改count
的值,页面会自动渲染。
通过上面的代码可以看到,使用响应式编程,我们不用去获取和操作 DOM 元素,只需要修改数据,就可以实现页面的自动渲染,和传统的前端开发相比,非常的方便。
4.3 setup语法糖
在前面使用 setup 的时候,需要将模板中使用的变量和方法返回,这样会很麻烦,因为不小心就忘掉了。
针对这种情况,可以使用 setup 语法糖。
需要单独使用一个 <script>
标签, 举个栗子:
vue
<template>
<!-- 结构 -->
<div class="my-app">
<div>{{ count }}</div>
<button @click="increment">增加</button>
</div>
</template>
<script lang="ts">
// 脚本
export default {
name: "App"
}
</script>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
</script>
在上面的代码中,只是将 setup
中的内容放到了一个新的 <script>
标签中,这个标签的类型需要和之前的类型一致 lang="ts"
并添加 setup
属性。setup 的 <script>
标签中变量和方法自动返回。
但是上面有两个 <script>
标签,显得很冗余啊,可以将第一个标签删掉:
vue
<template>
<!-- 结构 -->
<div class="my-app">
<div>{{ count }}</div>
<button @click="increment">增加</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue'
// 响应式状态
const count = ref(0)
// 用来修改状态、触发更新的函数
function increment() {
count.value++
}
</script>
删掉以后,是可以正常工作的,但是删掉以后,组件的名称没有办法定义了,组件的名称默认就是文件的名称,也就是 App.vue
中 App
。正常情况下,这是没问题的。
但是非得要自定义名称,或者不同模块有相同的 .vue
文件,就会出现问题,导致组件名称相同,所以如何定义组件名称呢?
需要借助 vite-plugin-vue-setup-extend
插件,首先安装插件
bash
# 安装插件
npm install -D vite-plugin-vue-setup-extend
然后在项目的 vite.config.ts
文件中添加配置:
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'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// 2. 调用
VueSetupExtend()
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
导入并调用插件。
最后在 <script>
标签上添加 name
属性:
vue
<!-- setup -->
<script lang="ts" setup name="App123">
// ...略
</script>
通过浏览器开发者工具就可以看到组件的名称了:
4.4 ref
在 Vue2 中,只需要将数据放在 data(){}
中,就可以实现响应式数据。
在 Vue3 中需要使用 ref
和 reactive
(待会再讲)来创建响应式数据。
使用 ref
来创建基本类型 和对象类型 的响应式数据,如果你想让哪个数据是响应式数据,使用 ref
包裹一下。
举个栗子:
创建基本类型的响应式数据。
vue
<template>
<div>{{ name }}</div>
<div>{{ age }}</div>
<div>{{ phoneNumber }}</div>
<div>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue'
let name = ref("doubi"); // 使用ref创建响应式数据
let age = ref(13); // 使用ref创建响应式数据
let phoneNumber = "137xxxxxxxx"; // 不需要变化,则不需要ref
// 修改姓名
function changeName() {
name.value = "niubi";
}
// 修改年龄
function changeAge() {
age.value = 14;
}
</script>
创建对象类型的响应式数据,一样的用法:
vue
<template>
<div> {{ person.name }} - {{ person.age }}</div>
<div>
<button @click="changePerson">修改Person</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { ref } from 'vue'
// 对讲对象类型的响应式数据
let person = ref({"name": "doubi", "age": 13, "phoneNumber": "137xxxxxxxx"})
// 修改person
function changePerson() {
person.value.name = "niubi"
person.value.age = 15
}
</script>
在上面的代码中,使用 ref
创建响应式的数据,ref
将实际的数据包裹起来,实际变成了一个新的对象。
注意 :在代码中修改的时候,修改的是 变量.value
的值,但是在模板中引用的时候,引用的是变量的名称,不需要 .value
。
如果觉得每次首先 .value
很麻烦,可以通过插件自动添加。
点击 VSCode 左下角的设置按钮 -> 设置 -> 扩展 -> Vue -> Auto Insert:Dot Value
。
勾选配置后,每次写响应式扩展名,会自动补全 .value
。
4.5 reactive
reactive
只能用来定义对象类型(数组、函数等都是对象)的响应式数据。
用法和上面使用 ref
一样,将对象包裹起来就可以。
举个栗子:
vue
<template>
<div>{{ person.name }}</div>
<div>{{ person.age }}</div>
<div>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive } from 'vue' // 1.引入reactive
// 2.通过reactive定义响应式数据
let person = reactive({"name": "doubi", "age": 13})
// 修改姓名
function changeName() {
person.name = "niubi"
}
// 修改年龄
function changeAge() {
person.age = 14;
}
</script>
在上面的代码中,使用 reactive
将对象包裹起来,此时打印 person
变成了 Proxy 代理的对象。
其实 ref
在创建对象类型的响应式数据的时候,底层使用的也是 reactive
。
这样修改对象的数据,就可以让 Vue 重新渲染页面了。
在使用 reactive
的时候,有一个地方需要注意,就是重新分配一个新对象,会失去响应式。
举个栗子:
下面的 person 在创建的时候是响应式数据,如果重新给 person 赋值,会失去响应式。
vue
<template>
<div> {{ person.name }} - {{ person.age }}</div>
<div>
<button @click="changePerson">修改Person</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive } from 'vue'
// 对讲对象类型的响应式数据
let person = reactive({ "name": "doubi", "age": 13, "phoneNumber": "137xxxxxxxx" })
// 修改person
function changePerson() {
// 页面不更新,不是响应式数据
// person = {"name": "niubi", "age": 15, "phoneNumber": "137xxxxxxxx"}
// 页面也不行,也是不可以的
// person = reactive({"name": "niubi", "age": 15, "phoneNumber": "186xxxxxxxx"})
// 需要单独修改person的属性,但是如果有很多的属性,依次赋值就很麻烦
// person.name = "niubi"
// 可以使用assign函数,将一个对象的属性赋值给另一个对象
let newPerson = { "name": "niubi", "age": 15, "phoneNumber": "137xxxxxxxx" }
Object.assign(person, newPerson) // 将newPerson的属性赋值给person
}
</script>
所以需要使用 Object.assign
来进行属性赋值。
如果使用的是 ref
,就可以这个问题,应为直接是给 value
赋值:
vue
// 使用ref,是给.value赋值,不是失去响应式
person.value = { "name": "niubi", "age": 15, "phoneNumber": "137xxxxxxxx" }
后面在创建响应式数据的时候,如果是基本数据类型,使用 ref
,对象类型使用 ref
、 reactive
都可以。
4.6 toRefs和toRef
在 Vue 3 中,toRefs
和 toRef
是两个用于响应式转换的 API,它们提供了将对象或对象的某个属性转换为响应式引用(refs)的方法。
1 toRefs
toRefs
用于将一个响应式对象的所有属性转换为响应式引用(refs),可以帮助我们解构响应式对象时保持其响应性。
举个例子,先查看一下下面的代码:
下面的代码将响应式对象 person 结构赋值给变量。
vue
<template>
<div> {{ person.name }} </div>
<div> {{ person.age }} </div>
<div> {{ name }} </div>
<div> {{ age }} </div>
<div>
<button @click="changePerson">修改Person</button>
<button @click="changeNameAndAge">修改name和age</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive } from 'vue'
// 对讲对象类型的响应式数据
let person = reactive({ "name": "doubi", "age": 13})
// 结构对象给变量赋值!!!
let {name, age} = person
// 修改person
function changePerson() {
person.name = "niubi"
person.age = 15
}
function changeNameAndAge() {
name = "niubi"
age = 15
}
</script>
此时 name 和 age 变量不是响应式变量了,修改 person 对象和修改变量本身,页面都不会有响应。
此时可以使用 toRefs
,将结构的属性变成响应式变量。
举个栗子:
vue
<template>
<div> {{ person.name }} </div>
<div> {{ person.age }} </div>
<div> {{ name }} </div>
<div> {{ age }} </div>
<div>
<button @click="changePerson">修改Person</button>
<button @click="changeNameAndAge">修改name和age</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive, toRefs } from 'vue'
// 对讲对象类型的响应式数据
let person = reactive({ "name": "doubi", "age": 13})
// 结构对象给变量赋值
let {name, age} = toRefs(person) // 转换为响应式变量
// 修改person
function changePerson() {
person.name = "niubi"
person.age = 15
}
function changeNameAndAge() {
name.value = "niubi" // 修改为.value
age.value = 15
}
</script>
此时修改 person
对象还是 name 和 age
变量,person
的数据还是 name 和 age
变量都会同步变化。
通过 toRefs
的转换,不只是person对象,person
对象中的属性都变成了响应式对象。
2 toRef
toRef 和 toRefs 功能是一样的,只是用来将对象的单个属性的响应式对象。
js
// 将person对象的name属性转换为响应式对象
let name = toRef(person, "name")
举个栗子:
vue
<template>
<div> {{ person.name }} </div>
<div> {{ name }} </div>
<div>
<button @click="changePerson">修改Person</button>
<button @click="changeNameAndAge">修改name和age</button>
</div>
</template>
<!-- setup -->
<script lang="ts" setup>
import { reactive, toRef } from 'vue'
// 对讲对象类型的响应式数据
let person = reactive({ "name": "doubi", "age": 13})
// 将person对象的name属性转换为响应式对象
let name = toRef(person, "name")
// 修改person
function changePerson() {
person.name = "niubi"
}
function changeNameAndAge() {
name.value = "niubi" // 修改为.value
}
</script>
平时可能 toRefs
用的多一些