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定义:

javascript 复制代码
<script setup>
const props = defineProps({
  msg: String
})

const emit = defineEmits(['change'])

function handleChange() {
  emit('change', 'new value')
}
</script>

异步支持直接await:

javascript 复制代码
<script setup>
const data = await fetch('/api/data').then(r => r.json())
</script>

<script setup>中编译器宏

在<script setup>中,编译器宏是一种特殊的函数,不需要导入,可以直接在<script setup>中使用,这些宏在编译阶段会被处理,不能在普通的逻辑代码如(if,console.log或普通函数内部)中调用,只能在<script setup>的顶层作用域使用。

defineProps()-声明组件接收的props

返回值:返回一个包含所有prop值得响应对象(只读)

javascript 复制代码
<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[]
}>()
</script>
defineEmits()-声明组件可以触发的事件

返回值:返回emit函数,用于触发事件

javascript 复制代码
<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)
}
</script>
defineExpose()-对外暴露显式指定那些属性/方法可被父组件通过ref访问

默认情况下,<script setup>组件时关闭的,父组件通过模板引用(ref)访问子组件实例时,无法访问到子组件内部定义的变量或方法

子组件对外暴露属性和方法

javascript 复制代码
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
function increment() {
  count.value++
}

// 显式暴露
defineExpose({
  count,
  increment
})
</script>

父组件进行访问

javascript 复制代码
<!-- Parent.vue -->
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const childRef = ref(null)

// 现在可以访问 childRef.value.count 和 childRef.value.increment()
</script>
defineModel()-简化组件间的双向数据绑定

以前实现父子组件双向绑定需要手动编写props,emit事件以及computed计算属性来实现,具体操作如下

子组件

javascript 复制代码
<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-model="name" /></label>
    <br />
    <label>年龄: <input v-model="age" /></label>
    <button @click="growUp">长大一岁</button>
  </div>
</template>

父组件

javascript 复制代码
<template>
  <!-- 使用参数化 v-model -->
  <MultiModel 
    v-model:name="userName" 
    v-model:age="userAge" 
  />
</template>
withDefaults()-仅配合TS使用

当使用ts语法定义defineProps时,无法直接设置默认值。withDefaults用于补充默认值。

javascript 复制代码
<script setup lang="ts">
interface Props {
  msg?: string
  labels?: string[]
}

// 设置默认值
const props = withDefaults(defineProps<Props>(), {
  msg: 'Hello',
  labels: () => ['new', 'feature'] // 对象/数组默认值需用工厂函数
})
</script>

组合式API-useSlots()和useAttrs()

对应选项式API中 this.slots this.attrs。组合式API需显式导入。

useAttrs()-属性透传,精细控制属性绑定

应用场景:封装原生的<input>组件。父组件可能会传递各种原生属性(比如placeholder,disabled,class,style)等

如果不使用useAttrs,需要在defineProps里把每个原生属性都定义一次.。

子组件

javascript 复制代码
<template>
  <!-- 
    这是一个多根节点结构 (Fragment):
    1. 外层有个 div 包裹
    2. 内部才是 input
    Vue 不知道把 class/style 给谁,所以默认会报警告或绑定到外层。
    需要用 useAttrs 手动绑定到 input 上。
  -->
  <div class="input-wrapper">
    <label v-if="label" class="label">{{ label }}</label>
    
    <!-- 核心:将 attrs 全部绑定到 input 元素上 -->
    <input 
      v-bind="attrs" 
      class="native-input"
      :value="modelValue"
      @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
    />
    
    <span class="suffix">px</span>
  </div>
</template>

<script setup lang="ts">
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>

父组件

javascript 复制代码
<template>
  <div>
    <!-- 
      注意:我们并没有在 SmartInput 的 props 里定义 
      placeholder, disabled, @focus, style, class 
      但它们都能完美工作!
    -->
    <SmartInput 
      v-model="searchText"
      label="搜索"
      placeholder="请输入关键词..."
      disabled="false"
      class="custom-highlight" 
      style="border-color: blue;"
      @focus="handleFocus"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import SmartInput from './SmartInput.vue'

const searchText = ref('')

const handleFocus = () => {
  console.log('Input focused!')
}
</script>
useSlots()-动态渲染插槽

应用场景:权限控制组件,根据某些条件(用户权限,数据是否为空),动态决定是否渲染某个插槽,对插槽的内容进行包装。

子组件:接收一个role属性。当前用户有权限则渲染默认插槽,如果没有权限就渲染'无权限'提示,或者渲染特定的插槽。

javascript 复制代码
<template>
  <!-- 常规情况:直接渲染 -->
  <div v-if="hasPermission" class="content-box">
    <slot />
  </div>

  <!-- 无权限情况:动态决定渲染什么 -->
  <div v-else class="error-box">
    <!-- 
      这里演示用 v-if 判断插槽是否存在。
      虽然模板里可以直接写 <slot name="denied">,
      但如果逻辑更复杂(比如要根据 slots.denied 的返回值类型做处理),
      就需要用到 useSlots。
    -->
    <slot name="denied">
      <p>🚫 您没有权限查看此内容。</p>
    </slot>
  </div>
</template>

<script setup lang="ts">
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>

父组件

javascript 复制代码
<template>
  <!-- 场景 1: 有权限,显示正常内容 -->
  <PermissionWrapper required-role="admin" current-role="admin">
    <h1>管理员仪表盘</h1>
    <p>这里是敏感数据...</p>
  </PermissionWrapper>

  <!-- 场景 2: 无权限,使用默认的拒绝提示 -->
  <PermissionWrapper required-role="admin" current-role="user">
    <h1>普通用户仪表盘</h1>
  </PermissionWrapper>

  <!-- 场景 3: 无权限,使用自定义的拒绝提示 (触发 denied 插槽) -->
  <PermissionWrapper required-role="vip" current-role="user">
    <template #denied>
      <div class="custom-alert">
        🔒 成为 VIP 才能解锁此功能!
        <button>立即升级</button>
      </div>
    </template>
    <h1>VIP 专属内容</h1>
  </PermissionWrapper>
</template>
相关推荐
后端不背锅2 小时前
订单超时取消系统:从数据库轮询到延迟队列演进
前端
小彭努力中2 小时前
195.Vue3 + OpenLayers:监听瓦片地图加载情况(200、403及异常处理)
前端·css·openlayers·cesium·webgis
给钱,谢谢!2 小时前
记录uni-app Vue3 慎用 Teleport,会导致页面栈混乱
前端·vue.js·uni-app
哈__2 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-haptic-feedback
javascript·react native·react.js
吴声子夜歌2 小时前
JavaScript——DOM与事件
开发语言·javascript·ecmascript
紫_龙2 小时前
最新版vue3+TypeScript开发入门到实战教程之路由详解
javascript·typescript·智能路由器
陈天伟教授2 小时前
人工智能应用- AI 增强显微镜:01.显微镜的瓶颈
前端·人工智能·安全·xss·csrf
大白菜1号2 小时前
踩坑了!Postman 正常,但本地项目 406 (Not Acceptable)
vue.js·测试工具·postman
Mintopia2 小时前
Pencil.dev 设计 → 规格 → 代码 → 校验
前端·人工智能