
一、什么是 setup1.1 定义setup是 Vue 3 引入的组合式 API(Composition API)的入口。它在组件创建之前执行(在beforeCreate之前),是整个组件逻辑的起跑线。关键点:setup是一个函数,你把响应式数据、计算属性、方法、生命周期钩子全都写在这个函数内部,然后通过return暴露给模板使用。1.2 生活类比把组件想象成一个公司:Options API(Vue 2):公司分好几个部门(data 部、methods 部、computed 部、mounted 部),各部在不同楼层上班,要协作得上下跑Composition API setup:一个大办公室,所有人都坐一起,数据、方法、计算、生命周期挨着写,想拿谁直接转身setup就是这个大办公室。1.3 为什么要用 setup痛点setup 的解法一个功能的代码散落各处同一功能的 data/methods/computed 写一起mixin 容易冲突、难追踪用组合函数(useXxx)替代,来源清晰类型推导差(TypeScript)组合式 API 天然适合类型推导逻辑复用难把 setup 里的代码抽成函数,随便复用二、两种写法:setup()函数 vsscript setupVue 3 提供了两种使用组合式 API 的方式,写法不同但能力基本一致。2.1 写法一:setup()函数(Options API 中的 setup)在export default里写一个setup方法,返回值暴露给模板。script import { ref } from vue; export default { setup() { const count ref(0); const increment () count.value; return { count, increment }; // ← 必须 return 模板才能用 } } /script template button clickincrement{{ count }}/button /template2.2 写法二:script setup语法糖(推荐)在script标签上加setup属性,所有顶层变量自动暴露给模板,不用return,也不用export default。script setup import { ref } from vue; const count ref(0); const increment () count.value; // 不用 return!不用 export default! /script template button clickincrement{{ count }}/button /template2.3 对比特性setup()函数script setup需不需要return必须 return自动暴露需不需要export default需要不需要组件注册components: {}注册导入即可用props/emitsetup(props, context)defineProps/defineEmits代码量多(return、注册)少推荐度兼容老代码时用推荐,更简洁结论:日常开发直接用script setup,简洁且是官方主推写法。三、响应式数据:ref reactivesetup里声明的普通变量不是响应式的,改了页面不更新。要让数据变响应式,必须用ref或reactive。3.1 ref:基本类型和对象都能用ref接收一个值,返回一个带.value的响应式对象。import{ref}fromvue;constcountref(0);// 基本类型count.value;// ← 取值/赋值都要 .valueconstuserref({name:张三});// 对象也行user.value.name李四;// ← 对象要 .value.name模板里不用写.value,Vue 自动解包:p{{ count }}/p!-- 不用 count.value --3.2 reactive:专门给对象用reactive只能接对象/数组,返回的响应式对象不用.value,直接访问属性。import{reactive}fromvue;constuserreactive({name:张三,age:18});user.age;// ← 直接 user.age,不用 .valueconstlistreactive([1,2,3]);list.push(4);// ← 数组也直接操作3.3 ref vs reactive 怎么选维度refreactive适用类型基本类型 对象(万能)只能对象/数组访问方式要.value不要.value解构赋值解构后仍响应式(因为是对象)解构后丢失响应式整体替换ref.value xxx可以不能整体替换(会丢响应)推荐度更通用,优先用适合嵌套深的对象记忆口诀:基本类型用 ref,复杂对象用 reactive;能 ref 就 ref,省事。3.4 注解1.reactive 解构会丢响应式constuserreactive({name:张三,age:18});const{age}user;// ← age 变成普通数字,改了不更新age;// 不响应要保持响应式,用toRefs:import{toRefs}fromvue;const{age}toRefs(user);// ← age 是 ref,仍是响应式age.value;// 响应2.ref 在模板里自动解包,但只解一层constobjref({count:ref(0)});// 模板里 obj.count 还是 ref,需要 obj.count.value避免嵌套 ref,保持数据结构扁平。四、computed watch4.1 computed:计算属性import{ref,computed}fromvue;constfirstNameref(张);constlastNameref(三);constfullNamecomputed(()firstName.valuelastName.value);// 模板里直接用 {{ fullName }},自动解包也支持get/set:constfullNamecomputed({get:()firstName.valuelastName.value,set:(val){firstName.valueval[0];lastName.valueval.slice(1);}});4.2 watch:侦听器watch可以监听响应式数据变化,执行副作用(请求、日志等)。import{ref,watch}fromvue;constcountref(0);watch(count,(newVal,oldVal){console.log(从${oldVal}变成${newVal});});监听多个源:watch([count,user],([newCount,newUser],[oldCount,oldUser]){// ...});监听对象的某个属性,要传 getter:watch(()user.age,(newVal,oldVal){console.log(age 变了);});watchEffect:自动收集依赖,不用指定监听谁,里面用到的响应式数据变了就触发。import{watchEffect}fromvue;watchEffect((){console.log(count.value);// ← count 变了就重新执行});4.3 注解1.watch 默认是懒执行的watch 第一次不会执行,只有数据变化才触发。想立即执行,加immediate: true:watch(count,fn,{immediate:true});// ← 立刻执行一次2.watch 监听对象默认不深度遍历watch(user, fn)只在user整体被替换时触发,改user.age不会触发。要深度监听加deep: true,或直接监听() user.age。五、生命周期钩子:全加 onsetup里没有created/beforeCreate(setup 本身就在这之前执行),其他钩子都加个on前缀。Options APIsetup 中写法beforeCreate不需要(setup 代替)created不需要(setup 代替)beforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpdatedbeforeUnmountonBeforeUnmountunmountedonUnmountedactivatedonActivateddeactivatedonDeactivatederrorCapturedonErrorCapturedimport{onMounted,onBeforeUnmount}fromvue;onMounted((){console.log(DOM 已挂载,可以操作 DOM 了);});onBeforeUnmount((){console.log(要卸载了,清理定时器/监听);});注意:钩子必须在setup同步执行时调用,不能放在异步回调里(如setTimeout里调onMounted无效)。六、props emit6.1setup()函数写法setup接收两个参数:props和context。exportdefault{props:{title:String,count:Number},setup(props,context){console.log(props.title);// ← 访问 propscontext.emit(change,props.count1);// ← 触发事件console.log(context.attrs);// 透传属性console.log(context.slots);// 插槽console.log(context.expose);// 暴露给父组件return{};}}注意:props是响应式的,但不能解构(解构会丢响应式):const{title}props;// ❌ 解构后 title 不是响应式consttitleprops.title;// ❌ 同上// 正确:直接用 props.title,或用 toRefsimport{toRefs}fromvue;const{title}toRefs(props);// ✅ title 是 ref6.2script setup写法:defineProps defineEmitsscript setup用编译器宏defineProps和defineEmits,不需要 import。script setup const props defineProps({ title: String, count: { type: Number, default: 0 } }); const emit defineEmits([change, update]); const handleClick () { emit(change, props.count 1); }; /script template div{{ title }}/div button clickhandleClick1/button /templateTypeScript 写法(泛型声明,更推荐):script setup langts interface Props { title: string; count?: number; } const props withDefaults(definePropsProps(), { count: 0 }); const emit defineEmits{ (e: change, value: number): void; (e: update): void; }(); /script6.3 defineExpose:暴露给父组件子组件默认不向父组件暴露任何东西,父组件通过ref拿到的只是组件实例。要暴露数据/方法,用defineExpose。!-- 子组件 Child.vue -- script setup import { ref } from vue; const count ref(0); const reset () { count.value 0; }; defineExpose({ count, reset }); // ← 暴露给父组件 /script!-- 父组件 -- script setup import { ref, onMounted } from vue; import Child from ./Child.vue; const childRef ref(null); onMounted(() { console.log(childRef.value.count); // 0 childRef.value.reset(); // 调子组件方法 }); /script template Child refchildRef / /template