script setup-组合式API编译时语法糖单文件组件(SFC)中使用组合式API编译时语法糖该语法在同时使用单文件组件与组合式API是默认推荐的。主要优点更少的样板代码无需手动export default无需setup()函数包裹。能够使用纯的ts声明和自定义事件。更好的运行性能(其模板会被编译成同一作用域内的渲染函数避免上下文代理对象)主要特性顶层绑定自动暴露给模板在script setup中声明的变量函数导入内容无需return可直接在模板中使用.自动defineComponent使用script setup的租价会自动包裹在defineComponent()中无需手动调用。简洁的props和emits定义script setup const props defineProps({ msg: String }) const emit defineEmits([change]) function handleChange() { emit(change, new value) } /script异步支持直接await:script setup const data await fetch(/api/data).then(r r.json()) /scriptscript setup中编译器宏在script setup中编译器宏是一种特殊的函数不需要导入可以直接在script setup中使用这些宏在编译阶段会被处理不能在普通的逻辑代码如(ifconsole.log或普通函数内部)中调用只能在script setup的顶层作用域使用。defineProps()-声明组件接收的props返回值返回一个包含所有prop值得响应对象(只读)script setup // 选项式语法 (Options Syntax) const props defineProps({ msg: String, list: { type: Array, required: true } }) // TypeScript 语法 (Type-Only Syntax) - 推荐 TS 用户使用 // 无需运行时验证类型仅在编译时检查 const props defineProps{ msg?: string list: number[] }() /scriptdefineEmits()-声明组件可以触发的事件返回值返回emit函数用于触发事件script setup // 选项式语法 const emit defineEmits([change, update]) // TypeScript 语法 const emit defineEmits{ (e: change, id: number): void (e: update, value: string): void }() function notify() { emit(change, 123) } /scriptdefineExpose()-对外暴露显式指定那些属性/方法可被父组件通过ref访问默认情况下script setup组件时关闭的父组件通过模板引用(ref)访问子组件实例时无法访问到子组件内部定义的变量或方法子组件对外暴露属性和方法!-- Child.vue -- script setup import { ref } from vue const count ref(0) function increment() { count.value } // 显式暴露 defineExpose({ count, increment }) /script父组件进行访问!-- Parent.vue -- script setup import Child from ./Child.vue import { ref } from vue const childRef ref(null) // 现在可以访问 childRef.value.count 和 childRef.value.increment() /scriptdefineModel()-简化组件间的双向数据绑定以前实现父子组件双向绑定需要手动编写propsemit事件以及computed计算属性来实现具体操作如下子组件script setup // 第一个模型 (对应 v-model) const name defineModel(name, { type: String, default: Guest }) // 第二个模型 (对应 v-model:age) const age defineModel(age, { type: Number, default: 0 }) function growUp() { age.value } /script template div label名字: input v-modelname //label br / label年龄: input v-modelage //label button clickgrowUp长大一岁/button /div /template父组件template !-- 使用参数化 v-model -- MultiModel v-model:nameuserName v-model:ageuserAge / /templatewithDefaults()-仅配合TS使用当使用ts语法定义defineProps时无法直接设置默认值。withDefaults用于补充默认值。script setup langts interface Props { msg?: string labels?: string[] } // 设置默认值 const props withDefaults(definePropsProps(), { msg: Hello, labels: () [new, feature] // 对象/数组默认值需用工厂函数 }) /script组合式API-useSlots()和useAttrs()对应选项式API中 this.$slots this.$attrs。组合式API需显式导入。useAttrs()-属性透传精细控制属性绑定应用场景封装原生的input组件。父组件可能会传递各种原生属性(比如placeholderdisabledclassstyle)等如果不使用useAttrs需要在defineProps里把每个原生属性都定义一次.。子组件template !-- 这是一个多根节点结构 (Fragment) 1. 外层有个 div 包裹 2. 内部才是 input Vue 不知道把 class/style 给谁所以默认会报警告或绑定到外层。 需要用 useAttrs 手动绑定到 input 上。 -- div classinput-wrapper label v-iflabel classlabel{{ label }}/label !-- 核心将 attrs 全部绑定到 input 元素上 -- input v-bindattrs classnative-input :valuemodelValue input$emit(update:modelValue, ($event.target as HTMLInputElement).value) / span classsuffixpx/span /div /template script setup langts import { useAttrs, defineOptions } from vue // 1. 定义简单的 props (只定义业务相关的) defineProps{ modelValue: string | number label?: string }() // 2. 定义事件 defineEmits{ update:modelValue: [value: string] }() // 3. 【关键】关闭自动继承 // 手动把 attrs 绑定到 input 上如果不关闭 // 属性会同时出现在外层的 div 和内部的 input 上导致重复 class 等 defineOptions({ inheritAttrs: false }) // 4. 获取 attrs 对象 const attrs useAttrs() // 可以在逻辑中读取特定属性例如用于调试或条件判断 // 注意attrs 是响应式的如果父组件动态改变 class这里也会变 console.log(当前绑定的额外属性:, attrs) /script style scoped .input-wrapper { display: flex; align-items: center; gap: 8px; } .native-input { border: 1px solid #ccc; padding: 4px 8px; /* 父组件传入的 class 会合并到这里 */ } /style父组件template div !-- 注意我们并没有在 SmartInput 的 props 里定义 placeholder, disabled, focus, style, class 但它们都能完美工作 -- SmartInput v-modelsearchText label搜索 placeholder请输入关键词... disabledfalse classcustom-highlight styleborder-color: blue; focushandleFocus / /div /template script setup import { ref } from vue import SmartInput from ./SmartInput.vue const searchText ref() const handleFocus () { console.log(Input focused!) } /scriptuseSlots()-动态渲染插槽应用场景权限控制组件根据某些条件(用户权限数据是否为空)动态决定是否渲染某个插槽对插槽的内容进行包装。子组件接收一个role属性。当前用户有权限则渲染默认插槽如果没有权限就渲染无权限提示或者渲染特定的插槽。template !-- 常规情况直接渲染 -- div v-ifhasPermission classcontent-box slot / /div !-- 无权限情况动态决定渲染什么 -- div v-else classerror-box !-- 这里演示用 v-if 判断插槽是否存在。 虽然模板里可以直接写 slot namedenied 但如果逻辑更复杂比如要根据 slots.denied 的返回值类型做处理 就需要用到 useSlots。 -- slot namedenied p 您没有权限查看此内容。/p /slot /div /template script setup langts import { useSlots, computed } from vue const props defineProps{ requiredRole: string currentRole: string }() // 1. 获取所有插槽函数 const slots useSlots() // 2. 计算是否有权限 const hasPermission computed(() { return props.currentRole props.requiredRole }) // 3. 【高级用法】在 JS 中检查插槽是否存在 // 有时候我们需要知道父组件是否传了特定的具名插槽以便做不同的逻辑处理 const hasCustomDeniedSlot computed(() { // slots.denied 是一个函数如果父组件没传它就是 undefined return !!slots.denied }) // 模拟一个场景如果有自定义 denied 插槽我们在控制台打个日志 if (hasCustomDeniedSlot.value !hasPermission.value) { console.log(检测到用户使用了自定义的无权限插槽内容) } // 4. 【极端高级用法】手动渲染插槽 (通常在 render 函数中用得多模板中少见) // 假设我们需要把插槽内容作为参数传给某个第三方 JS 库 function processSlotContent() { if (slots.default) { // 调用插槽函数得到 VNode 数组 const vnodes slots.default() console.log(默认插槽生成的虚拟节点:, vnodes) // 这里可以对 vnodes 进行修改、过滤或包装然后再返回 return vnodes } return null } /script父组件template !-- 场景 1: 有权限显示正常内容 -- PermissionWrapper required-roleadmin current-roleadmin h1管理员仪表盘/h1 p这里是敏感数据.../p /PermissionWrapper !-- 场景 2: 无权限使用默认的拒绝提示 -- PermissionWrapper required-roleadmin current-roleuser h1普通用户仪表盘/h1 /PermissionWrapper !-- 场景 3: 无权限使用自定义的拒绝提示 (触发 denied 插槽) -- PermissionWrapper required-rolevip current-roleuser template #denied div classcustom-alert 成为 VIP 才能解锁此功能 button立即升级/button /div /template h1VIP 专属内容/h1 /PermissionWrapper /template
vue3-<script setup>是Vue3.2+引入编译语法糖与编译器宏以及useSlots()和useAttrs()
script setup-组合式API编译时语法糖单文件组件(SFC)中使用组合式API编译时语法糖该语法在同时使用单文件组件与组合式API是默认推荐的。主要优点更少的样板代码无需手动export default无需setup()函数包裹。能够使用纯的ts声明和自定义事件。更好的运行性能(其模板会被编译成同一作用域内的渲染函数避免上下文代理对象)主要特性顶层绑定自动暴露给模板在script setup中声明的变量函数导入内容无需return可直接在模板中使用.自动defineComponent使用script setup的租价会自动包裹在defineComponent()中无需手动调用。简洁的props和emits定义script setup const props defineProps({ msg: String }) const emit defineEmits([change]) function handleChange() { emit(change, new value) } /script异步支持直接await:script setup const data await fetch(/api/data).then(r r.json()) /scriptscript setup中编译器宏在script setup中编译器宏是一种特殊的函数不需要导入可以直接在script setup中使用这些宏在编译阶段会被处理不能在普通的逻辑代码如(ifconsole.log或普通函数内部)中调用只能在script setup的顶层作用域使用。defineProps()-声明组件接收的props返回值返回一个包含所有prop值得响应对象(只读)script setup // 选项式语法 (Options Syntax) const props defineProps({ msg: String, list: { type: Array, required: true } }) // TypeScript 语法 (Type-Only Syntax) - 推荐 TS 用户使用 // 无需运行时验证类型仅在编译时检查 const props defineProps{ msg?: string list: number[] }() /scriptdefineEmits()-声明组件可以触发的事件返回值返回emit函数用于触发事件script setup // 选项式语法 const emit defineEmits([change, update]) // TypeScript 语法 const emit defineEmits{ (e: change, id: number): void (e: update, value: string): void }() function notify() { emit(change, 123) } /scriptdefineExpose()-对外暴露显式指定那些属性/方法可被父组件通过ref访问默认情况下script setup组件时关闭的父组件通过模板引用(ref)访问子组件实例时无法访问到子组件内部定义的变量或方法子组件对外暴露属性和方法!-- Child.vue -- script setup import { ref } from vue const count ref(0) function increment() { count.value } // 显式暴露 defineExpose({ count, increment }) /script父组件进行访问!-- Parent.vue -- script setup import Child from ./Child.vue import { ref } from vue const childRef ref(null) // 现在可以访问 childRef.value.count 和 childRef.value.increment() /scriptdefineModel()-简化组件间的双向数据绑定以前实现父子组件双向绑定需要手动编写propsemit事件以及computed计算属性来实现具体操作如下子组件script setup // 第一个模型 (对应 v-model) const name defineModel(name, { type: String, default: Guest }) // 第二个模型 (对应 v-model:age) const age defineModel(age, { type: Number, default: 0 }) function growUp() { age.value } /script template div label名字: input v-modelname //label br / label年龄: input v-modelage //label button clickgrowUp长大一岁/button /div /template父组件template !-- 使用参数化 v-model -- MultiModel v-model:nameuserName v-model:ageuserAge / /templatewithDefaults()-仅配合TS使用当使用ts语法定义defineProps时无法直接设置默认值。withDefaults用于补充默认值。script setup langts interface Props { msg?: string labels?: string[] } // 设置默认值 const props withDefaults(definePropsProps(), { msg: Hello, labels: () [new, feature] // 对象/数组默认值需用工厂函数 }) /script组合式API-useSlots()和useAttrs()对应选项式API中 this.$slots this.$attrs。组合式API需显式导入。useAttrs()-属性透传精细控制属性绑定应用场景封装原生的input组件。父组件可能会传递各种原生属性(比如placeholderdisabledclassstyle)等如果不使用useAttrs需要在defineProps里把每个原生属性都定义一次.。子组件template !-- 这是一个多根节点结构 (Fragment) 1. 外层有个 div 包裹 2. 内部才是 input Vue 不知道把 class/style 给谁所以默认会报警告或绑定到外层。 需要用 useAttrs 手动绑定到 input 上。 -- div classinput-wrapper label v-iflabel classlabel{{ label }}/label !-- 核心将 attrs 全部绑定到 input 元素上 -- input v-bindattrs classnative-input :valuemodelValue input$emit(update:modelValue, ($event.target as HTMLInputElement).value) / span classsuffixpx/span /div /template script setup langts import { useAttrs, defineOptions } from vue // 1. 定义简单的 props (只定义业务相关的) defineProps{ modelValue: string | number label?: string }() // 2. 定义事件 defineEmits{ update:modelValue: [value: string] }() // 3. 【关键】关闭自动继承 // 手动把 attrs 绑定到 input 上如果不关闭 // 属性会同时出现在外层的 div 和内部的 input 上导致重复 class 等 defineOptions({ inheritAttrs: false }) // 4. 获取 attrs 对象 const attrs useAttrs() // 可以在逻辑中读取特定属性例如用于调试或条件判断 // 注意attrs 是响应式的如果父组件动态改变 class这里也会变 console.log(当前绑定的额外属性:, attrs) /script style scoped .input-wrapper { display: flex; align-items: center; gap: 8px; } .native-input { border: 1px solid #ccc; padding: 4px 8px; /* 父组件传入的 class 会合并到这里 */ } /style父组件template div !-- 注意我们并没有在 SmartInput 的 props 里定义 placeholder, disabled, focus, style, class 但它们都能完美工作 -- SmartInput v-modelsearchText label搜索 placeholder请输入关键词... disabledfalse classcustom-highlight styleborder-color: blue; focushandleFocus / /div /template script setup import { ref } from vue import SmartInput from ./SmartInput.vue const searchText ref() const handleFocus () { console.log(Input focused!) } /scriptuseSlots()-动态渲染插槽应用场景权限控制组件根据某些条件(用户权限数据是否为空)动态决定是否渲染某个插槽对插槽的内容进行包装。子组件接收一个role属性。当前用户有权限则渲染默认插槽如果没有权限就渲染无权限提示或者渲染特定的插槽。template !-- 常规情况直接渲染 -- div v-ifhasPermission classcontent-box slot / /div !-- 无权限情况动态决定渲染什么 -- div v-else classerror-box !-- 这里演示用 v-if 判断插槽是否存在。 虽然模板里可以直接写 slot namedenied 但如果逻辑更复杂比如要根据 slots.denied 的返回值类型做处理 就需要用到 useSlots。 -- slot namedenied p 您没有权限查看此内容。/p /slot /div /template script setup langts import { useSlots, computed } from vue const props defineProps{ requiredRole: string currentRole: string }() // 1. 获取所有插槽函数 const slots useSlots() // 2. 计算是否有权限 const hasPermission computed(() { return props.currentRole props.requiredRole }) // 3. 【高级用法】在 JS 中检查插槽是否存在 // 有时候我们需要知道父组件是否传了特定的具名插槽以便做不同的逻辑处理 const hasCustomDeniedSlot computed(() { // slots.denied 是一个函数如果父组件没传它就是 undefined return !!slots.denied }) // 模拟一个场景如果有自定义 denied 插槽我们在控制台打个日志 if (hasCustomDeniedSlot.value !hasPermission.value) { console.log(检测到用户使用了自定义的无权限插槽内容) } // 4. 【极端高级用法】手动渲染插槽 (通常在 render 函数中用得多模板中少见) // 假设我们需要把插槽内容作为参数传给某个第三方 JS 库 function processSlotContent() { if (slots.default) { // 调用插槽函数得到 VNode 数组 const vnodes slots.default() console.log(默认插槽生成的虚拟节点:, vnodes) // 这里可以对 vnodes 进行修改、过滤或包装然后再返回 return vnodes } return null } /script父组件template !-- 场景 1: 有权限显示正常内容 -- PermissionWrapper required-roleadmin current-roleadmin h1管理员仪表盘/h1 p这里是敏感数据.../p /PermissionWrapper !-- 场景 2: 无权限使用默认的拒绝提示 -- PermissionWrapper required-roleadmin current-roleuser h1普通用户仪表盘/h1 /PermissionWrapper !-- 场景 3: 无权限使用自定义的拒绝提示 (触发 denied 插槽) -- PermissionWrapper required-rolevip current-roleuser template #denied div classcustom-alert 成为 VIP 才能解锁此功能 button立即升级/button /div /template h1VIP 专属内容/h1 /PermissionWrapper /template