当前位置: 首页 > news >正文

4.1vue3的setup()

setup() 函数是 Vue 3 中引入的 Composition API 的核心入口,它为组件提供了更灵活、更强大的逻辑组织方式。

一、基本概念与执行时机

  • 入口函数setup() 是使用 Composition API 的起点。
  • 执行时机:在组件实例被创建之前执行,早于 beforeCreate 和 created 钩子。
  • this 指向:在 setup() 内部,this 为 undefined,因为组件实例尚未创建。

二、基本使用

setup() 函数可以返回一个对象,该对象中的属性和方法将暴露给模板和其他选项式 API。

<script>
import { ref } from 'vue'export default {setup() {const count = ref(0)const increment = () => {count.value++}// 返回的对象会暴露给模板return {count,increment}},mounted() {console.log(this.count) // 可以访问 setup 中返回的属性}
}
</script><template><div><p>{{ count }}</p> <!-- 模板中使用 ref 时自动解包,无需 .value --><button @click="increment">加1</button></div>
</template>

三、setup() 的参数

setup() 接收两个参数:

  1. props:包含组件所有 props 的响应式对象。注意:直接解构 props 会失去响应性,应使用 toRefs()toRef() 来解构。

    import { toRefs, toRef } from 'vue'setup(props) {// ❌ 错误:解构后 title 失去响应性// const { title } = props// ✅ 正确:使用 toRefsconst { title } = toRefs(props)console.log(title.value) // 访问值需要 .value// ✅ 正确:使用 toRef (针对单个 prop)const titleRef = toRef(props, 'title')console.log(titleRef.value)
    }
  2. context:上下文对象,包含 attrsslotsemitexpose。这个对象是非响应式的,可以直接解构。

    setup(props, { attrs, slots, emit, expose }) {// attrs: 透传的非 prop 属性,相当于 $attrsconsole.log(attrs)// slots: 插槽内容,相当于 $slotsconsole.log(slots)// emit: 触发自定义事件,相当于 $emitemit('custom-event', payload)// expose: 暴露公共属性/方法给父组件通过 ref 访问expose({publicMethod() {// ...}})
    }

四、定义响应式数据

主要使用 refreactive

  • ref

    • 用于定义基本类型数据(字符串、数字、布尔值等)。
    • 也可以用于对象或数组,内部会自动调用 reactive 转换。
    • 在 JavaScript 中访问或修改值需要 .value,在模板中使用时会自动解包(无需 .value)。
    import { ref } from 'vue'
    const count = ref(0)
    count.value++ // 修改值
  • reactive

    • 用于定义对象或数组类型的响应式数据。
    • 直接操作对象属性,无需 .value
    import { reactive } from 'vue'
    const user = reactive({ name: 'Alice', age: 25 })
    user.age++ // 直接修改

五、定义方法

setup() 内部直接定义函数即可,然后将其返回。

setup() {const handleClick = () => {console.log('Button clicked!')}return {handleClick}
}

六、计算属性 (computed)

import { ref, computed } from 'vue'setup() {const firstName = ref('John')const lastName = ref('Doe')// 简写:只有 getterconst fullName = computed(() => {return firstName.value + ' ' + lastName.value})// 完整写法:包含 getter 和 setterconst fullNameWritable = computed({get() {return firstName.value + '-' + lastName.value},set(newValue) {const [first, last] = newValue.split('-')firstName.value = firstlastName.value = last}})return {firstName,lastName,fullName,fullNameWritable}
}

七、侦听器 (watch)

import { ref, watch } from 'vue'setup() {const count = ref(0)// 侦听单个 refwatch(count, (newVal, oldVal) => {console.log(`count changed from ${oldVal} to ${newVal}`)})// 侦听多个源const firstName = ref('')const lastName = ref('')watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {console.log('Name changed!')})// 侦听 reactive 对象(注意:oldValue 可能不准确,deep 配置强制开启)const state = reactive({ count: 0, name: '' })watch(() => state.count, (newVal, oldVal) => {console.log(`state.count changed`)})// 立即执行watch(count,(newVal) => {console.log(newVal)},{ immediate: true })return { count, firstName, lastName, state }
}

八、与选项式 API 的关系

  • 兼容性:Vue 3 支持选项式 API (datamethodscomputedwatch)。
  • 访问规则
    • 选项式 API (datamethods 等) 中可以访问 setup() 返回的属性和方法。
    • setup() 中不能访问 datamethods 等选项式 API 定义的内容。
  • 优先级:如果 setup() 和选项式 API 存在同名属性或方法,setup() 中的定义优先。

九、<script setup> 语法糖

<script setup>setup() 函数的语法糖,让代码更简洁。

  • 特点
    • 无需显式定义 setup() 函数和 return
    • 在 <script setup> 中定义的顶层变量、函数、导入的组件都会自动暴露给模板。
    • 是单文件组件 (SFC) 中使用 Composition API 的推荐方式。
<script setup>
import { ref, computed } from 'vue'
import ChildComponent from './ChildComponent.vue'const count = ref(0)
const doubleCount = computed(() => count.value * 2)const increment = () => {count.value++
}// 导入的组件可直接在模板中使用
</script><template><div><p>{{ count }}</p><p>Double: {{ doubleCount }}</p><button @click="increment">+</button><ChildComponent /></div>
</template>

十、在 Vue 3 的 <script setup> 语法糖中,访问 propscontextattrs, slots, emit


访问 Props

1. 使用 defineProps() 宏

这是定义和访问 props 的标准方式。defineProps() 是一个编译时宏,不需要手动导入。

<script setup>
// 方式1: 使用对象类型声明 (推荐)
const props = defineProps({title: {type: String,required: true,default: 'Default Title'},count: {type: Number,default: 0}
})// 方式2: 使用运行时数组声明
// const props = defineProps(['title', 'count'])// 方式3: 使用类型声明 (TypeScript 推荐)
// defineProps<{
//   title?: string
//   count?: number
// }>()// 在 setup 脚本中使用 props
console.log(props.title)
console.log(props.count)// 注意:props 是响应式的,可以安全地解构(仅限读取)
// ❌ 错误:直接解构会失去响应性(在普通 setup() 中)
// const { title } = props // 不推荐// ✅ 正确:使用 toRefs 转换为 ref
import { toRefs } from 'vue'
const { title, count } = toRefs(props)// 或者,如果只是读取且不担心响应性丢失(比如在事件处理函数中)
// const title = props.title
</script>

访问 Context (attrs, slots, emit)

1. 访问 attrs 和 slots

使用 useAttrs()useSlots() 组合式 API 函数。

<script setup>
import { useAttrs, useSlots } from 'vue'const attrs = useAttrs()
const slots = useSlots()// 使用 attrs (相当于 $attrs)
console.log(attrs.id)
console.log(attrs.class)// 使用 slots (相当于 $slots)
// 通常在渲染函数中使用,模板中直接用 <slot> 标签
</script>
2. 访问 emit (触发事件)

使用 defineEmits() 宏来声明和触发自定义事件。

<script setup>
// 方式1: 使用对象类型声明 (推荐)
const emit = defineEmits({'update:title': (newTitle) => typeof newTitle === 'string','custom-event': (payload) => payload !== undefined
})// 方式2: 使用运行时数组声明
// const emit = defineEmits(['update:title', 'custom-event'])// 方式3: 使用类型声明 (TypeScript 推荐)
// defineEmits<{
//   (e: 'update:title', newTitle: string): void
//   (e: 'custom-event', payload: any): void
// }>()// 触发事件
const handleChangeTitle = () => {emit('update:title', 'New Title')
}const handleCustomEvent = () => {emit('custom-event', { message: 'Hello' })
}
</script><template><button @click="handleChangeTitle">Change Title</button><button @click="handleCustomEvent">Custom Event</button>
</template>

访问 expose

使用 defineExpose() 宏来暴露组件内部的属性或方法,供父组件通过 ref 访问。

<script setup>
import { ref } from 'vue'const count = ref(0)const increment = () => {count.value++
}const reset = () => {count.value = 0
}// 暴露给父组件
defineExpose({count, // 暴露 refincrement,reset
})
</script>

父组件中使用:

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'const childRef = ref()const callChildMethod = () => {childRef.value.increment() // 调用子组件暴露的方法
}
</script><template><ChildComponent ref="childRef" /><button @click="callChildMethod">Call Child Increment</button>
</template>

完整示例

<script setup>
import { ref, toRefs, useAttrs, useSlots } from 'vue'// 1. 定义和使用 Props
const props = defineProps({title: String,modelValue: Number
})// 将 props 转换为 refs (保持响应性)
const { title, modelValue } = toRefs(props)// 2. 定义 Emits
const emit = defineEmits(['update:modelValue', 'close'])// 3. 使用 useAttrs 和 useSlots
const attrs = useAttrs()
const slots = useSlots()// 4. 内部状态和方法
const localCount = ref(modelValue.value || 0)const increment = () => {localCount.value++emit('update:modelValue', localCount.value)
}const handleClose = () => {emit('close')
}// 5. 暴露给外部
defineExpose({localCount,increment
})
</script><template><div v-bind="attrs"> <!-- 透传 attrs --><h2>{{ title }}</h2><p>Count: {{ localCount }}</p><button @click="increment">+</button><button @click="handleClose">Close</button><!-- 使用插槽 --><slot name="header"></slot><slot></slot><slot name="footer"></slot></div>
</template>

总结

目的语法糖方式
定义 Propsconst props = defineProps({...})
触发事件const emit = defineEmits([...])
暴露 APIdefineExpose({...})
访问 attrsconst attrs = useAttrs()
访问 slotsconst slots = useSlots()

<script setup> 语法糖通过 definePropsdefineEmitsdefineExpose 这些编译时宏以及 useAttrsuseSlots 这些运行时函数,优雅地解决了 propscontext 的访问问题,代码更加简洁直观。

重要注意事项

  1. 不要使用 asyncsetup() 不能是 async 函数,因为其返回值必须是包含属性和方法的对象,而 async 函数返回的是 Promise
  2. 响应式原理
    • ref:基于 Object.defineProperty() (Vue 2) 或 Proxy (Vue 3) 的 get/set 实现。
    • reactive:基于 Proxy 实现,性能更好,支持深层响应式。
  3. <script setup> 优先:在单文件组件中,应优先使用 <script setup> 语法糖。

总而言之,setup() 函数为 Vue 3 带来了更灵活的逻辑组织方式,而 <script setup> 语法糖则极大地简化了其使用,是现代 Vue 开发的推荐实践。

http://www.lryc.cn/news/620393.html

相关文章:

  • EtherCAT概念介绍
  • 防抖 debounce.js
  • Synology File Station 官方 API 指南总结(中文版)
  • windows 资源管理器缩略图 ,支持.MP4(H.265/HEVC编码)视频格式和.HEIC(HEIF)图片格式的软件
  • 《吃透 C++ 类和对象(中):拷贝构造函数与赋值运算符重载深度解析》
  • Cypher注入详解:原理、类型与测试方法
  • Python入门第1课:环境搭建与第一个程序“Hello World”
  • SQL详细语法教程(三)mysql的函数知识
  • Mac 新电脑安装cocoapods报错ruby版本过低
  • 计算机如何进行“卷积”操作:从图像到矩阵的奥秘
  • Java进阶学习之Stream流的基本概念以及使用技巧
  • OS设备UDID查看方法
  • Java毕业设计选题推荐 |基于SpringBoot的健身爱好线上互动与打卡社交平台系统 互动打卡小程序系统
  • UniVoc:基于二维矩阵映射的多语言词汇表系统
  • 机海沉浮录,荣耀的HTC式困局
  • 重塑隐私边界,微算法科技(NASDAQ:MLGO)开发基于边缘计算的轻量级区块链身份隐私保护方案
  • 【论文阅读 | CVPR 2024 | UniRGB-IR:通过适配器调优实现可见光-红外语义任务的统一框架】
  • 【C++】细说继承(2w字详解)
  • 如何手动开启 Hyper-V?Windows 10/11 详细开启教程
  • AI绘画:从算法原理解读其风格、质量与效率变革
  • Python机器学习与深度学习;Transformer模型/注意力机制/目标检测/语义分割/图神经网络/强化学习/生成式模型/自监督学习/物理信息神经网络等
  • 医疗洁净间的“隐形助手”:富唯智能复合机器人如何重塑手术器械供应链
  • postgreSQL在arcmap中创建企业级数据库
  • k8s+isulad 网络问题
  • 从阿尔法狗到生活日常:机器学习如何重塑我们的世界?
  • 【Java 后端】Spring Boot 集成 JPA 全攻略
  • 推荐三个国内开源数据治理工具
  • 【完整源码+数据集+部署教程】肾脏病变实例分割系统源码和数据集:改进yolo11-CARAFE
  • 将mysql数据库表结构导出成DBML格式
  • 古中医学习笔记专题文章导航