1. 组合式API(Composition API)
在 Vue 3 中,Options API 和 Composition API 是两种不同的编程方式,各有优缺点和适用场景。
a. 定义方式
Options API
- 通过一组固定的选项(如
data
、methods
、computed
等)定义组件逻辑。 - 常见于 Vue 2 时代,逻辑是按功能分块的。组件较大时,相关逻辑可能被分散,阅读和维护较难。
示例:
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
Composition API
- 使用 JavaScript 函数(如
setup
)来组织组件逻辑。 - 逻辑可以按功能分组,增强了代码的可重用性和灵活性。适合复杂场景,例如跨组件共享逻辑或处理状态。
示例:
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
};
b. 类型支持(TypeScript)
Options API
- TypeScript 支持有限,难以对
data
和props
进行静态推断。 - 方法和属性的类型需要手动指定。
Composition API
- 原生支持 TypeScript,类型推断更强大。
-
ref
和reactive
的类型定义清晰,开发体验更好。
c. 响应式系统
Options API
- 响应式由 Vue 内部自动处理,基于
this
的属性访问。 - 不够显式,难以跟踪响应式行为。
Composition API
- 显式使用
ref
和reactive
创建响应式数据。 - 更直观,开发者可以完全掌控响应式逻辑。
总结
特性 | Options API | Composition API |
---|---|---|
代码组织 | 按选项分块,逻辑可能分散 | 按功能分块,逻辑集中 |
类型支持 | TypeScript 支持有限 | 原生支持 TypeScript |
响应式系统 | 隐式响应式,基于 this
|
显式响应式,更灵活 |
适用场景 | 简单项目,易上手 | 复杂项目,高级功能需求 |
Vue 3 更推荐 Composition API,尤其是在需要模块化逻辑或使用 TypeScript 的项目中。然而,如果项目较小或团队成员熟悉 Options API,继续使用它也没有问题。
2. setup
setup
是 Vue 3 中 Composition API 的核心函数,用于定义组件的逻辑。它在组件实例创建之前执行,作为一个更灵活、更简洁的入口,整合了响应式状态、生命周期钩子以及依赖注入等功能。
a. 基本语法
<script lang="ts">
export default {
setup() {
// 组件逻辑
return {
// 暴露给模板使用的变量和方法
};
}
};
</script>
【特点】
- setup函数返回的对象中的内容,可直接在模板中使用。
- setup中访问this是undefined。
- setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的。(可以先不管)
b. 返回值
- 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用。
- 若返回一个函数:则可以自定义渲染内容。
c. 与Options API的关系
- vue2的配置可以访问到
setup
的属性和方法,反之不可以 - 若两者冲突,
setup
优先
d. setup独立
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<button @click="changName">修改名字</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
}
</script>
<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
console.log(this) //undefined
// 数据(注意:此时的name、age、tel都不是响应式数据)
let name = '张三'
// 方法
function changName(){
name = '李四'//注意:此时这么修改name页面是不变化的
}
}
</script>
扩展:上述代码,还需要编写一个不写setup的script标签,去指定组件名字,比较麻烦,我们可以借助vite中的插件简化
第一步:终端输入npm i vite-plugin-vue-setup-extend -D
第二步:修改vite.config.ts文件,修改如下
第三步:<script setup lang="ts" name="Person">
直接这样写
// 修改vite.config.ts文件
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [ VueSetupExtend() ]
})
e. 基本功能
- 定义响应式数据
- 访问 Props 和 Context
- 使用计算属性利用
computed
创建依赖于其他状态的计算值 - 监听数据变化,使用
watch
或watchEffect
监听数据变化 - 使用生命周期钩子,通过
onXXX
函数注册生命周期钩子
基本的功能会在后面有具体的展开
f. 常见问题
1. 为什么没有 this
?
在 setup
中,this
不再指向组件实例,而是完全通过函数作用域管理状态。这使得逻辑更清晰,也避免了传统 Options API
中 this
指向问题。
2. 如何暴露方法给模板?
通过 return
将需要在模板中使用的变量和方法显式暴露:
export default {
setup() {
const msg = 'Hello, Vue 3!';
const sayHello = () => console.log(msg);
return { msg, sayHello }; // 暴露给模板
}
};
3. 如何复用逻辑?
可以通过自定义 Hook(composables
)实现逻辑复用:
// useCounter.js
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
// 在组件中使用
import { useCounter } from './useCounter';
export default {
setup() {
const { count, increment } = useCounter();
return { count, increment };
}
};
g. 适用场景
- 需要更强的逻辑复用能力。
- 希望使用显式声明的响应式系统。
- 项目使用 TypeScript。
setup
是 Vue 3 的核心设计之一,通过简化组件逻辑、增强灵活性,为现代应用开发提供了更强大的工具。
3. 两种方式表示响应式数据
在 Vue 3 中,ref
和 reactive
是创建响应式数据的两种主要方式。虽然它们的功能类似,都是为了让数据变得响应式,但在使用场景和实现细节上有所不同。响应式数据就是当数据发生变化时,视图会自动更新以反映数据的变化,使得我们能够将数据和视图关联起来。
a. 基本定义
ref
- 用于创建单个响应式数据,更轻量
- 返回一个带有
.value
属性的对象,数据存储在.value
中。在模版中不需要.value
来使用。 - 适合处理原始数据类型(如字符串、数字、布尔值)或独立的值。也可以定义对象。
示例:
import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
// count不是响应式的,count.value才是响应式的
reactive
- 用于将整个对象或数组变成响应式。
- 返回一个 Proxy 对象,可以直接操作其属性。不需要
.value
来使用。 - 适合复杂数据结构(如对象或嵌套数据)。不可定义基本类型。
示例:
import { reactive } from 'vue';
const user = reactive({ name: 'Alice', age: 25 });
console.log(user.name); // Alice
user.age++;
console.log(user.age); // 26
b. 使用场景对比
特性 | ref |
reactive |
---|---|---|
适合的数据类型 | 原始值(如字符串、数字等) | 对象或数组 |
嵌套响应性 | 需要手动处理嵌套对象 | 自动处理嵌套对象的响应性 |
模板中使用 | 需要通过 .value 访问 |
直接访问属性即可 |
c. 响应式行为的区别
ref
的响应式行为
- 对原始值类型(如
number
、string
)直接处理。 - 对复杂对象(如数组、对象)时,仍然需要通过
.value
使用。 - 嵌套属性不是自动响应式,需额外处理。
示例:
const nestedRef = ref({ name: 'Alice' });
nestedRef.value.name = 'Bob'; // 响应式工作
console.log(nestedRef.value.name); // Bob
注意:
如果需要更深层嵌套的响应式行为,推荐使用 reactive
。
可使用TypeScript Vue Plugin(Volar)插件实现自动 .value
(需要在设置中开启)
子组件Person.vue中要使用defineExpose暴露内容
<script lang="ts" setup name="Person">
import {ref,defineExpose} from 'vue'
// 数据
let name = ref('张三')
let age = ref(18)
// 使用defineExpose将组件中的数据导出交给外部
defineExpose({name,age})
</script>
reactive
的响应式行为
- 自动处理嵌套属性的响应性。
- 对整个对象的修改都会触发视图更新。
- 重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。
示例:
const user = reactive({ name: 'Alice', details: { age: 25 } });
user.details.age++; // 响应式生效
console.log(user.details.age); // 26
d. 嵌套结构和组合使用
ref
配合 reactive
使用
对于需要单独管理某些原始值或嵌套结构时,可以组合使用:
setup() {
const user = reactive({
name: 'Alice',
age: ref(25) // 嵌套使用 ref
});
user.age.value++; // 修改嵌套的 ref
return { user };
}
reactive
嵌套响应式
reactive
会自动处理嵌套对象的响应性:
setup() {
const data = reactive({
user: { name: 'Alice', age: 25 },
settings: { theme: 'dark' }
});
data.user.age++; // 自动响应
return { data };
}
e. 选择指南
使用场景 | 推荐方式 |
---|---|
原始数据或单个值 | ref |
多属性的对象或数组 | reactive |
嵌套结构(深层次对象) | reactive |
组合简单数据和复杂数据 | 二者结合 |
4. toRefs
在 Vue 中,reactive
返回的是一个代理对象,而该对象是不可直接解构的。如果你尝试解构一个 reactive
对象,它的属性会失去响应性。因此,toRefs
就是为了解决这个问题,它能将 reactive
对象转换成每个属性都作为 ref
的普通对象,从而使得你可以解构使用。
a. 基本用法
假设你有一个 reactive
对象,其中包含多个属性。如果你直接解构这个对象,属性会丧失响应性,但使用 toRefs
可以保留每个属性的响应性。
示例 1:解构响应式对象
import { reactive, toRefs } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
name: 'Alice'
});
// 使用 toRefs 将 state 解构,并保持响应性
const { count, name } = toRefs(state);
// 返回解构后的响应式引用
return { count, name };
}
};
解释:
-
state
是一个响应式对象。 -
toRefs(state)
会将state
的每个属性转换成ref
,这样你可以解构并保持每个属性的响应性。 -
count
和name
都是ref
,你可以像访问ref
一样通过.value
来访问它们。
模板中的使用:
<template>
<div>
<p>Count: {{ count }}</p>
<p>Name: {{ name }}</p>
</div>
</template>
在模板中,你可以直接访问 count
和 name
,Vue 会自动解包 ref
的 .value
,所以不需要显式地使用 .value
。
b. 为什么使用 toRefs
而不是直接解构 reactive
对象?
- 1. 保持响应性
如前所述,直接解构 reactive
对象会丧失响应性。这是因为解构是对对象的浅拷贝,而不是引用传递。通过 toRefs
,你能够确保每个属性仍然是响应式的 ref
。
- 2. 与 ref 一致性
ref
和 reactive
都是响应式数据。ref
适用于单一数据项,而 reactive
适用于对象或数组。toRefs
允许你将 reactive
对象的每个属性转成 ref
,这样就能像处理 ref
那样,灵活地处理对象中的单个属性。
c. 与组件外部数据共享
toRefs
还可以用于在多个组件或函数之间共享响应式数据。在这种情况下,你可以将响应式对象传递给外部函数,并通过 toRefs
保证其属性的响应性。
import { reactive, toRefs } from 'vue';
function useFormState() {
const state = reactive({
username: '',
email: ''
});
// 将 state 转换为 ref
return toRefs(state);
}
export default {
setup() {
const { username, email } = useFormState();
return { username, email };
}
};
下篇文章会进一步详细地介绍更多vue3的核心基础知识,如果对你有帮助不妨点个赞支持一下~
参考:张天禹老师b站课程