一、setup 执行时机
1.1 setup 是什么
setup 是组合式 API 的入口,在组件创建时(beforeCreate 之前)执行一次:
ts
import { ref } from "vue";
export default {
props: { title: String },
setup(props, ctx) {
const count = ref(0); // 声明响应式数据
// props:父组件传入的 props(只读)
// ctx.emit / ctx.attrs / ctx.slots
return { count }; // 返回对象暴露给模板
},
};
1.2 script setup 语法糖
实际项目中几乎都用 <script setup>,无需 return,变量/函数直接可用:
vue
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
注意 :
setup中拿不到this;需要访问组件实例的场景用getCurrentInstance()。
二、ref 与 reactive:两种响应式
2.1 ref:基础类型响应式
ts
import { ref } from "vue";
const count = ref(0); // 返回一个 { value } 对象
count.value = 1; // JS 中必须 .value 访问
// 模板中自动解包,直接写 count
// <p>{{ count }}</p>
ref 也可以装对象/数组 (内部会用 reactive 代理),但访问仍需 .value:
ts
const user = ref({ name: "张三" });
user.value.name = "李四";
2.2 reactive:对象响应式
ts
import { reactive } from "vue";
const state = reactive({ name: "张三", age: 18 });
state.name = "李四"; // 直接访问属性,无需 .value
2.3 选用区别
| 对比项 | ref | reactive |
|---|---|---|
| 支持类型 | 任意类型(含基础类型) | 仅对象/数组 |
| 访问方式 | .value(模板自动解包) |
直接访问属性 |
| 解构 | 安全(每层是 ref) | 解构丢失响应 |
| 整体替换 | obj.value = newObj ✅ |
obj = newObj ❌ 失效 |
| 适用 | 单值、通用场景 | 深嵌套对象、集中状态 |
推荐 :业务代码统一用 ref(心智简单);reactive 适合需要深层次集中管理的对象。
三、computed 计算属性
3.1 只读 computed
ts
import { ref, computed } from "vue";
const firstName = ref("张");
const lastName = ref("三");
const fullName = computed(() => firstName.value + lastName.value);
// 依赖变化自动重算,带缓存;只读,不能赋值
3.2 可写 computed(get/set)
ts
const fullName = computed({
get: () => firstName.value + lastName.value,
set: (val) => {
const [a, b] = val.split(" ");
firstName.value = a;
lastName.value = b;
},
});
computed 要求同步纯函数;异步或复杂副作用交给 watch。
四、watch 与 watchEffect 侦听器
4.1 watch:显式监听
ts
import { ref, watch } from "vue";
const keyword = ref("");
// 监听 ref
watch(keyword, (newVal, oldVal) => {
console.log(oldVal, "→", newVal);
});
// 监听 reactive 属性(getter 形式)
watch(
() => state.name,
(val) => console.log("name 变化:", val)
);
// 监听多个来源
watch([a, b], ([newA, newB]) => {});
// 深度监听
watch(state, (val) => {}, { deep: true });
4.2 watchEffect:自动收集依赖
ts
import { watchEffect } from "vue";
// 函数内用到的响应式数据变化都会触发;默认立即执行一次
watchEffect(() => {
console.log("当前关键词:", keyword.value); // 自动追踪依赖
});
4.3 两者对比
| 对比项 | watch | watchEffect |
|---|---|---|
| 依赖声明 | 显式指定 | 自动收集 |
| 立即执行 | 默认否(加 immediate) |
默认是 |
| 旧值获取 | ✅ | ❌ |
| 适用 | 精确监听某数据 | 副作用与多个数据相关时 |
五、高频踩坑:响应式失效
| 坑 | 现象 | 解决 |
|---|---|---|
| reactive 整体赋值 | state = newObj 后页面不更新(引用被替换) |
Object.assign(state, newObj) 或改用 ref |
| 解构 reactive | const { name } = state 后修改不响应 |
toRefs(state) / toRef(state, "name") |
ref 在 JS 中忘写 .value |
修改无效、类型报错 | 统一规范:JS 用 .value,模板裸用 |
| watch reactive 不触发 | 直接监听对象内部属性变化未生效 | getter 形式:() => state.name 或 deep: true |
| reactive 里塞基础类型 | 报错/无响应 | 基础类型用 ref |
| 在函数外解构 | 模板中变量不更新 | 组合式函数内返回 toRefs |
ts
// 正确示例
const { name } = toRefs(state); // 解构后仍是响应式
state.name = "李四"; // 或 name.value = "李四"
六、简单业务 Demo:搜索防抖
vue
<script setup lang="ts">
import { ref, watch } from "vue";
const keyword = ref("");
const results = ref<string[]>([]);
let timer: number | undefined;
watch(keyword, (val) => {
clearTimeout(timer);
timer = setTimeout(() => {
// 模拟请求
results.value = [`${val} 的搜索结果 1`, `${val} 的搜索结果 2`];
}, 300);
});
</script>
<template>
<input v-model="keyword" placeholder="输入搜索关键词" />
<ul>
<li v-for="(item, i) in results" :key="i">{{ item }}</li>
</ul>
</template>
七、总结
- setup :组件入口,
<script setup>免 return;无this; - ref vs reactive:ref 通用(.value + 可整体替换),reactive 只认对象(不可解构、不可整体赋值);
- computed:同步派生值 + 缓存,可写用 get/set;
- watch :显式精确监听(可拿旧值、可 deep);watchEffect:自动收集依赖 + 立即执行;
- 踩坑 :
Object.assign替代整体赋值、toRefs保解构响应、watch 用 getter 形式。