Vue3 语法讲解(setup/ref/reactive/computed/watch/生命周期/自定义Hook)
文章目录
一、Options API 与 Composition API 区别
1. Options API(Vue2 主流)
Vue2 采用配置式 API,代码按照 data、methods、computed、watch 拆分。
缺点:同一个业务逻辑的代码被分散在不同配置项中,项目复杂后难以维护、逻辑复用困难。
<script>
export default {
data() {
return { name: "张三", age: 18 }
},
methods: {
changeName() { this.name = "李四" }
}
}
</script>
2. Composition API(Vue3 主流)
Vue3 推出组合式API,以函数形式组织代码,相同功能的代码写在一起,逻辑聚合、复用灵活,是 Vue3 核心设计思想。
二、setup 入口函数
setup 是 Composition API 的执行入口,所有组合式 API 都要在 setup 中使用。
1. 基础特性
setup在beforeCreate生命周期之前执行,执行时this为undefined,不能使用 this。setup返回的对象属性/方法,模板可直接使用。- Vue2 的 Options API 可以访问
setup中的内容,setup无法访问 Options API。 - 命名冲突时,
setup优先级更高。
2. 基础用法(非语法糖)
<template>
<div>
<h2>姓名:{{ name }}</h2>
<button @click="changeName">修改姓名</button>
</div>
</template>
<script lang="ts">
export default {
name: "Person",
setup() {
// 普通变量(非响应式)
let name = "张三";
// 方法
function changeName() {
name = "李四";
console.log(name);
}
// 必须返回,模板才能使用
return { name, changeName };
},
};
</script>
注意:普通
let/const定义的数据不是响应式,修改后页面不会更新。
3. <script setup> 语法糖(重点推荐)
Vue3 官方推荐语法糖,简化 setup 写法,无需手动 return,代码更简洁。
基础使用
<template>
<h2>姓名:{{ name }}</h2>
<button @click="changeName">修改姓名</button>
</template>
<!-- 语法糖写法,整个标签等价于 setup 函数 -->
<script lang="ts" setup>
console.log(this); // undefined
let name = "张三";
function changeName() {
name = "李四";
}
</script>
扩展:自动补充组件名插件
原生语法糖无法直接写组件名,可借助插件简化:
- 安装依赖
npm i vite-plugin-vue-setup-extend -D
- 配置
vite.config.ts
import { defineConfig } from "vite";
import VueSetupExtend from "vite-plugin-vue-setup-extend";
export default defineConfig({
plugins: [VueSetupExtend()],
});
- 直接在标签上定义组件名
<script lang="ts" setup name="Person">
// 业务代码
</script>
三、响应式 API:ref 与 reactive
响应式是 Vue 核心,Vue3 使用 Proxy 替代 Object.defineProperty 实现响应式,解决了 Vue2 数组/对象更新的缺陷。
1. ref 基本类型响应式
作用
定义基本数据类型(字符串、数字、布尔)响应式,也可接收对象/数组。
语法
const 变量 = ref(初始值)
特点
- 返回
RefImpl实例对象,JS 中操作必须加.value,模板中直接使用。 - 支持基本类型、对象、数组。
<template>
<div>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeAge">年龄+1</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
// 定义响应式数据
let name = ref("张三");
let age = ref(18);
function changeAge() {
// JS 内部必须使用 .value
age.value += 1;
}
</script>
2. reactive 对象类型响应式
作用
专门定义对象、数组类型响应式数据,深度响应式(嵌套属性也可监听)。
语法
const 响应式对象 = reactive(原始对象)
特点
- 返回
Proxy代理对象,无需.value,直接操作属性。 - 天然支持深层嵌套响应。
- 不要直接重新赋值整个对象,会丢失响应式(可用
Object.assign批量更新)。
<template>
<div>
<h2>汽车:{{ car.brand }},价格:{{ car.price }}万</h2>
<button @click="changePrice">涨价</button>
<ul>
<li v-for="item in gameList" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
// 响应式对象
let car = reactive({ brand: "奔驰", price: 100 });
// 响应式数组
let gameList = reactive([
{ id: "1", name: "英雄联盟" },
{ id: "2", name: "王者荣耀" },
]);
function changePrice() {
car.price += 10;
}
</script>
3. ref 处理对象类型
ref 也可以接收对象/数组,底层内部会自动调用 reactive,操作依然需要 .value:
<script lang="ts" setup>
import { ref } from "vue";
let car = ref({ brand: "宝马", price: 80 });
function changePrice() {
car.value.price += 5;
}
</script>
4. ref vs reactive 选用原则
- 基本类型(字符串、数字、布尔):必须用
ref。 - 浅层对象/数组:
ref/reactive都可以。 - 深层嵌套对象:优先
reactive。 reactive禁止直接整体重新赋值,ref无此限制。
四、toRef 与 toRefs 解构响应式对象
直接解构 reactive 对象会丢失响应式,Vue 提供 toRef / toRefs 解决该问题。
1. toRef:单个属性转换
作用:将响应式对象单个属性转为 ref 对象,保留响应式。
2. toRefs:批量属性转换(常用)
作用:批量将响应式对象所有属性转为 ref,适合解构赋值。
<template>
<div>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeName">修改姓名</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, toRef } from "vue";
let person = reactive({ name: "张三", age: 18, gender: "男" });
// 批量解构(推荐)
const { name, age } = toRefs(person);
// 单个属性解构
const gender = toRef(person, "gender");
function changeName() {
name.value = "李四";
}
</script>
五、computed 计算属性
根据已有响应式数据衍生出新数据,依赖变化自动重新计算,和 Vue2 用法一致。
分为只读计算属性、可读写计算属性两种。
1. 只读计算属性(仅 get)
<template>
<div>
<input v-model="firstName" placeholder="姓" />
<input v-model="lastName" placeholder="名" />
<p>全名:{{ fullName }}</p>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
let firstName = ref("张");
let lastName = ref("三");
// 只读计算属性
const fullName = computed(() => {
return firstName.value + "-" + lastName.value;
});
</script>
2. 可读写计算属性(get + set)
支持读取和手动修改计算属性,修改时会触发 set 逻辑。
<script lang="ts" setup>
import { ref, computed } from "vue";
let firstName = ref("张");
let lastName = ref("三");
const fullName = computed({
// 读取
get() {
return firstName.value + "-" + lastName.value;
},
// 修改
set(val: string) {
const [f, l] = val.split("-");
firstName.value = f;
lastName.value = l;
},
});
// 主动修改计算属性
fullName.value = "李-四";
</script>
六、watch 侦听器
监听响应式数据变化,执行自定义逻辑。Vue3 中 watch 支持监听 ref、reactive、函数、数组四类数据。
1. 场景1:监听 ref 基本类型
直接传入变量名即可,可接收新值、旧值。
<script lang="ts" setup>
import { ref, watch } from "vue";
let sum = ref(0);
// 监听 ref 基本类型
watch(
sum,
(newVal, oldVal) => {
console.log("sum变化:", newVal, oldVal);
}
);
sum.value += 1;
</script>
2. 场景2:监听 ref 对象类型
监听对象内部属性,需要手动开启 deep: true 深度监听。
<script lang="ts" setup>
import { ref, watch } from "vue";
let person = ref({ name: "张三", age: 18 });
// 监听 ref 对象,开启深度监听
watch(
person,
(newVal) => {
console.log("对象变化:", newVal);
},
{ deep: true }
);
person.value.age = 20;
</script>
3. 场景3:监听 reactive 对象类型
reactive 默认自带深度监听,无需配置 deep。
<script lang="ts" setup>
import { reactive, watch } from "vue";
let person = reactive({ name: "张三", age: 18 });
watch(person, (newVal) => {
console.log("reactive对象变化", newVal);
});
person.age = 22;
</script>
4. 场景4:监听对象单个属性
监听对象中某一个属性,必须写成函数形式。
<script lang="ts" setup>
import { reactive, watch } from "vue";
let person = reactive({ name: "张三", age: 18 });
// 监听单个属性(函数写法)
watch(
() => person.name,
(newVal) => {
console.log("姓名变化:", newVal);
}
);
</script>
5. 场景5:监听多个数据
传入数组,同时监听多个值。
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
let name = ref("张三");
let person = reactive({ age: 18 });
// 监听多个数据
watch([name, () => person.age], ([newName, newAge]) => {
console.log("姓名/年龄变化", newName, newAge);
});
</script>
6. 补充配置项
immediate: true:组件挂载后立即执行一次监听回调。deep: true:开启深度监听。
七、watchEffect 副作用侦听器
特点
- 无需指定监听数据源,自动收集函数内部依赖,依赖变化就执行。
- 组件挂载时默认立即执行一次。
- 返回停止监听函数,可手动关闭监听。
示例
<template>
<h2>水温:{{ temp }}</h2>
<h2>水位:{{ height }}</h2>
<button @click="temp += 10">升温</button>
</template>
<script lang="ts" setup>
import { ref, watchEffect } from "vue";
let temp = ref(0);
let height = ref(0);
// 自动收集依赖 temp、height
const stopWatch = watchEffect(() => {
if (temp.value >= 50 || height.value >= 20) {
console.log("触发告警");
}
// 满足条件,停止监听
if (temp.value === 100) stopWatch();
});
</script>
watch 与 watchEffect 对比
watch:手动指定监听源,可获取新值/旧值,灵活可控。watchEffect:自动收集依赖,代码简洁,无法获取旧值。
八、标签 ref 与组件传值(defineExpose)
1. 普通 DOM 标签 ref
获取 DOM 元素节点:
<template>
<h2 ref="title">Vue3 核心语法</h2>
<button @click="getDom">获取元素</button>
</template>
<script lang="ts" setup>
import { ref } from "vue";
// 变量名和 ref 属性名一致
const title = ref<HTMLHeadingElement | null>(null);
function getDom() {
console.log(title.value?.innerText);
}
</script>
2. 组件 ref + defineExpose 父子传值
父组件通过 ref 获取子组件实例,子组件必须用 defineExpose 暴露属性/方法,父组件才能访问。
子组件 Child.vue
<script lang="ts" setup>
import { ref, defineExpose } from "vue";
let name = ref("子组件数据");
// 对外暴露数据
defineExpose({ name });
</script>
父组件 App.vue
<template>
<Child ref="childRef" />
<button @click="getChildData">获取子组件数据</button>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import Child from "./Child.vue";
const childRef = ref<InstanceType<typeof Child> | null>(null);
function getChildData() {
console.log(childRef.value?.name);
}
</script>
九、props 父传子(TypeScript 类型约束)
<script setup> 中使用 defineProps 接收父组件传值,支持类型校验、默认值。
1. 基础用法
父组件
<template>
<Child :list="userList" />
</template>
<script lang="ts" setup>
import { reactive } from "vue";
import Child from "./Child.vue";
interface User {
id: string;
name: string;
age: number;
}
const userList = reactive<User[]>([
{ id: "1", name: "张三", age: 18 },
{ id: "2", name: "李四", age: 19 },
]);
</script>
子组件(三种写法)
<template>
<div v-for="item in list" :key="item.id">
{{ item.name }} - {{ item.age }}
</div>
</template>
<script lang="ts" setup>
import { withDefaults, defineProps } from "vue";
interface User {
id: string;
name: string;
age: number;
}
// 写法1:仅接收,无类型校验
// const props = defineProps(["list"])
// 写法2:TS 类型校验
// const props = defineProps<{ list: User[] }>()
// 写法3:类型校验 + 可选 + 默认值(企业常用)
const props = withDefaults(defineProps<{ list?: User[] }>(), {
list: () => [{ id: "0", name: "默认用户", age: 0 }],
});
</script>
十、Vue3 生命周期
Vue3 组合式API 生命周期全部以 onXXX 形式导入使用,setup 替代了 beforeCreate 和 created。
生命周期对照表
| 阶段 | Vue2 钩子 | Vue3 组合式API 钩子 |
|---|---|---|
| 创建阶段 | beforeCreate/created | setup(替代) |
| 挂载阶段 | beforeMount/mounted | onBeforeMount / onMounted |
| 更新阶段 | beforeUpdate/updated | onBeforeUpdate / onUpdated |
| 卸载阶段 | beforeDestroy/destroyed | onBeforeUnmount / onUnmounted |
完整示例
<template>
<h2>{{ sum }}</h2>
<button @click="sum += 1">更新</button>
</template>
<script lang="ts" setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
let sum = ref(0);
console.log("setup 执行");
// 挂载前
onBeforeMount(() => console.log("挂载前"));
// 挂载完成(请求接口、操作DOM 常用)
onMounted(() => console.log("挂载完成"));
// 更新前
onBeforeUpdate(() => console.log("数据更新前"));
// 更新完成
onUpdated(() => console.log("数据更新完成"));
// 卸载前(清除定时器、解绑事件 常用)
onBeforeUnmount(() => console.log("组件卸载前"));
// 卸载完成
onUnmounted(() => console.log("组件卸载完成"));
</script>
十一、自定义 Hook
自定义 Hook 是 Vue3 逻辑复用的核心方案,本质就是一个普通 TS 函数,封装 setup 中的组合式 API,替代 Vue2 的 mixin。
1. 编写自定义 Hook
新建 hooks/useCount.ts
// 封装加减逻辑
import { ref, onMounted } from "vue";
export default function useCount() {
const count = ref(0);
// 加
const increment = () => {
count.value++;
};
// 减
const decrement = () => {
count.value--;
};
// 挂载后初始化
onMounted(() => {
count.value = 10;
});
// 向外暴露数据和方法
return { count, increment, decrement };
}
2. 组件中使用 Hook
<template>
<h2>计数:{{ count }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</template>
<script lang="ts" setup>
// 引入自定义Hook
import useCount from "@/hooks/useCount";
// 解构使用
const { count, increment, decrement } = useCount();
</script>
十二、总结
setup是组合式API入口,<script setup>语法糖为官方首选。- 响应式:基本类型用
ref,对象/数组优先reactive,解构用toRefs。 - 计算属性
computed:依赖缓存,适合数据加工;侦听器watch/watchEffect适合异步、复杂逻辑。 - 生命周期:
onMounted处理DOM/接口,onBeforeUnmount清理副作用。 - 自定义Hook:抽离通用逻辑,实现代码复用,是 Vue3 工程化必备。
更多推荐

所有评论(0)