Vue3 组合式 API 深度解析:ref/reactive 响应式,计算属性与侦听器

一、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 形式。
相关推荐
liangshanbo12159 分钟前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
斯内普吖12 分钟前
(开源)水果蔬菜商城实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源
去伪存真28 分钟前
从乱码到高精度检索:探矿业务中 TXT、Word、PDF 与网页的 RAG 清洗之道
前端·agent
两个西柚呀37 分钟前
JS事件总结
前端·javascript·css
小狼1545441 分钟前
拼多多订单数据导出 CSV 实战:接口分页、字段平铺与 5 个数据坑,多多开票助手
java·前端·javascript
ss2731 小时前
AI全栈实战 | 2.2-02 React 思维模型:数据不可变 vs Vue 数据可变,两种哲学把复杂度推给谁
前端·vue.js·react.js
天衍四九-1 小时前
Docker Compose企业实战系列(三):Vue/React前端项目容器化完整部署
前端·vue.js·docker
Rosanci1 小时前
从零到合入主线:我的 DeepSeek Harness 开源贡献实战手记
开发语言·前端·开源
用户960819842231 小时前
多周期联看时十字光标对不齐:前端排查「时间桶」的三步清单
前端
墨鱼老师2 小时前
SpringBoot+Vue 毕设项目开发踩坑记录(前后端分离)
vue.js·spring boot·毕业设计·课程设计·前后端分离·java 毕设