Vue 3中:setup 函数接收两个重要参数:props 和 context(附:setup 中无法访问 this 详解)

props 是响应式的只读对象,需通过defineProps声明,可使用toRefs保持解构后的响应性。


context 包含三个非响应式属性:attrs(未声明的属性)、slots(插槽内容)和emit(触发事件)。


setup 中无法访问 this,context 提供了 this 上面三个最常用的属性。


在<script setup>中推荐使用useAttrs()、useSlots()和defineEmits()。


注意事项包括setup执行时机、响应式丢失风险及TypeScript支持。


最佳实践建议优先使用<script setup>语法糖,配合类型声明和emit事件通信,充分利用Vue3响应式系统。


在 Vue3 的 Composition API 中,setup 函数接收两个重要参数:propscontext


1. props 参数

props 是响应式的对象,包含组件接收的所有 prop。


特性:

  • 响应式 :当父组件传递的 prop 变化时,props 会自动更新

  • 只读:不能在子组件中直接修改(会触发警告)

  • 需要显式声明 :必须通过 definePropsprops 选项声明


基本使用:

vue

javascript 复制代码
<script setup>
import { toRefs, watch } from 'vue'

// 声明 props
const props = defineProps({
  title: String,
  count: {
    type: Number,
    default: 0
  }
})

// 直接使用
console.log(props.title)

// 监听 props 变化
watch(() => props.count, (newVal) => {
  console.log('count changed:', newVal)
})

// 解构 props(会失去响应性)
const { title } = props // ❌ 非响应式

// 保持响应性的解构
const { title, count } = toRefs(props) // ✅ 响应式
</script>

2. context 参数

context 是一个普通对象(非响应式),包含三个组件属性。

context 包含三个属性:


a) attrs

vue

javascript 复制代码
<script setup>
import { useAttrs } from 'vue'

// 方式一:通过 setup 参数
setup(props, context) {
  console.log(context.attrs) // 所有未在 props 中声明的 attribute
}

// 方式二:在 <script setup> 中使用 useAttrs()
const attrs = useAttrs()
console.log(attrs.class) // 获取 class 属性
console.log(attrs.onClick) // 获取事件监听器
</script>

  • 包含所有未在 props 中声明的 attribute

  • 包括 classstyle、事件监听器等

  • 非响应式,但会自动更新


b) slots

vue

javascript 复制代码
<script setup>
import { useSlots } from 'vue'

// 方式一:通过 setup 参数
setup(props, context) {
  // 检查插槽是否存在
  if (context.slots.default) {
    // 渲染插槽内容
    return () => context.slots.default()
  }
}

// 方式二:在 <script setup> 中使用 useSlots()
const slots = useSlots()
console.log(slots.default) // 默认插槽
console.log(slots.header)  // 具名插槽
</script>

  • 包含所有插槽内容的函数

  • 用于渲染插槽内容或检查插槽是否存在


c) emit

vue

javascript 复制代码
<script setup>
import { defineEmits } from 'vue'

// 方式一:通过 setup 参数
setup(props, context) {
  const handleClick = () => {
    context.emit('update', newValue)
  }
}

// 方式二:在 <script setup> 中使用 defineEmits()
const emit = defineEmits(['update', 'delete'])

const handleClick = () => {
  emit('update', { id: 1, value: 'new' })
  emit('delete', 1)
}
</script>

  • 用于触发自定义事件

  • 推荐使用 defineEmits 进行声明


3. 完整示例

选项式 API 写法:

vue

javascript 复制代码
<script>
import { toRefs, watch } from 'vue'

export default {
  props: ['title', 'count'],
  emits: ['update-count'],
  
  setup(props, context) {
    // 使用 props
    const { title, count } = toRefs(props)
    
    // 使用 attrs
    console.log('所有 attributes:', context.attrs)
    
    // 使用 slots
    const hasHeaderSlot = !!context.slots.header
    
    // 使用 emit
    const increment = () => {
      context.emit('update-count', count.value + 1)
    }
    
    // 监听 props 变化
    watch(count, (newVal) => {
      console.log('Count updated:', newVal)
    })
    
    return {
      title,
      count,
      increment,
      hasHeaderSlot
    }
  }
}
</script>

<script setup> 语法糖写法:

vue

javascript 复制代码
<script setup>
import { toRefs, watch, useAttrs, useSlots } from 'vue'

// 声明 props
const props = defineProps({
  title: String,
  count: Number
})

// 声明 emits
const emit = defineEmits(['update-count'])

// 使用 attrs 和 slots
const attrs = useAttrs()
const slots = useSlots()

// 响应式解构 props
const { title, count } = toRefs(props)

// 检查插槽
const hasHeaderSlot = !!slots.header

// 方法
const increment = () => {
  emit('update-count', count.value + 1)
}

// 监听器
watch(count, (newVal) => {
  console.log('Count updated:', newVal)
})
</script>

<template>
  <div :class="attrs.class">
    <slot name="header" v-if="hasHeaderSlot" />
    <h2>{{ title }}</h2>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <slot />
  </div>
</template>

4. 注意事项

  1. setup 执行时机 :在 beforeCreate 之前执行,此时无法访问 this

  2. 响应式丢失 :直接解构 props 会丢失响应性,使用 toRefstoRef

  3. attrs 非响应式:但会自动更新,无需担心

  4. TypeScript 支持


vue

javascript 复制代码
<script setup lang="ts">
interface Props {
  title: string
  count?: number
}

const props = defineProps<Props>()
const emit = defineEmits<{
  (e: 'update', value: number): void
}>()
</script>

5. 最佳实践

  1. 优先使用 <script setup> 语法糖

  2. 使用 definePropsdefineEmits 进行声明

  3. 需要响应式解构时使用 toRefs

  4. 避免直接修改 props,使用 emit 触发事件

  5. 使用 TypeScript 增强类型安全


通过合理使用这两个参数,可以更好地组织组件逻辑,并充分利用 Vue3 的响应式系统。


setup 中无法访问 this,context 提供了 this 上面三个最常用的属性


这正是 Vue3 Composition API 的核心设计理念之一。


为什么 setup 中不能访问 this?

  1. 执行时机问题

    • setup 在组件实例创建之前 执行(在 beforeCreate 之前)

    • 此时组件实例(this)还没有被创建,所以无法访问

  2. 函数式编程风格

    • Composition API 鼓励函数式编程,减少对 this 的依赖

    • 避免 this 指向问题,特别是在高阶函数、回调函数中

  3. 更好的 TypeScript 支持

    • this 在 TypeScript 中类型推导复杂

    • 函数参数和返回值更容易进行类型声明


context 确实提供了 this 的三个核心功能

this 上的属性 context 对应属性 用途
this.$attrs context.attrs 获取未声明的 attribute
this.$slots context.slots 访问插槽内容
this.$emit context.emit 触发自定义事件

this 的其他功能在 setup 中如何获取?

vue

javascript 复制代码
<script setup>
import { 
  getCurrentInstance,
  useAttrs,
  useSlots
} from 'vue'

// 替代 this.$attrs
const attrs = useAttrs() // 等价于 this.$attrs

// 替代 this.$slots
const slots = useSlots() // 等价于 this.$slots

// 如果需要访问组件实例(谨慎使用)
const instance = getCurrentInstance()

// this.$props → 通过 defineProps
const props = defineProps(['title'])

// this.$data → 使用 ref/reactive
const data = reactive({ count: 0 })

// this.$watch → 使用 watch API
watch(() => props.title, (newVal) => {
  console.log(newVal)
})

// this.$nextTick → 使用 nextTick API
import { nextTick } from 'vue'
nextTick(() => {
  console.log('DOM updated')
})

// this.$root → 不推荐,考虑使用 provide/inject
</script>

详细的对比示例

Vue2 Options API(使用 this)

javascript 复制代码
export default {
  props: ['title'],
  data() {
    return { count: 0 }
  },
  mounted() {
    // 访问各种属性
    console.log(this.title)      // props
    console.log(this.count)      // data
    console.log(this.$attrs)     // attrs
    console.log(this.$slots)     // slots
    this.$emit('update', 1)      // emit
    this.$watch('title', fn)     // watch
    this.$nextTick(() => {})     // nextTick
  }
}

Vue3 Composition API(不使用 this)

vue

javascript 复制代码
<script setup>
import { 
  ref, 
  watch, 
  nextTick,
  getCurrentInstance,
  onMounted
} from 'vue'

// props(替代 this.props)
const props = defineProps(['title'])

// data(替代 this.data)
const count = ref(0)

// attrs(替代 this.$attrs)
const attrs = useAttrs()

// slots(替代 this.$slots)
const slots = useSlots()

// emit(替代 this.$emit)
const emit = defineEmits(['update'])

// watch(替代 this.$watch)
watch(() => props.title, (newVal) => {
  console.log(newVal)
})

// 生命周期
onMounted(() => {
  console.log(props.title)  // props
  console.log(count.value)  // data
  console.log(attrs)        // attrs
  console.log(slots)        // slots
  emit('update', 1)         // emit
  
  // nextTick(替代 this.$nextTick)
  nextTick(() => {
    console.log('DOM updated')
  })
  
  // 如果需要组件实例(慎用!)
  const instance = getCurrentInstance()
  console.log(instance)
})
</script>

为什么不推荐在 setup 中使用 getCurrentInstance()?

虽然可以通过 getCurrentInstance() 获取组件实例,但官方不推荐这样做:

  1. 破坏封装性:直接操作实例可能导致不可预测的行为

  2. 类型安全 :返回的实例类型是 ComponentInternalInstance,内部 API 不稳定

  3. 测试困难:依赖实例的方法难以进行单元测试

  4. 设计原则:违背了 Composition API 的声明式设计理念


javascript

javascript 复制代码
// ❌ 不推荐的做法
const instance = getCurrentInstance()
instance.proxy.$forceUpdate()  // 强制更新,应该避免

// ✅ 推荐的做法:使用响应式数据驱动
const state = reactive({ count: 0 })
state.count++  // 自动触发更新

特殊场景:在普通函数中访问组件上下文

如果你需要在 setup 外部的工具函数中访问组件功能,可以传递需要的部分:

javascript 复制代码
// utils.js
// ❌ 不好的做法:尝试在工具函数中访问 this
export function badUtil() {
  // 这里无法访问 this
}

// ✅ 好的做法:显式传递依赖
export function goodUtil(emit, props) {
  // 使用传入的 emit 和 props
  emit('event', props.value)
}

// 在组件中使用
import { goodUtil } from './utils'

const props = defineProps(['value'])
const emit = defineEmits(['event'])

// 显式传递依赖
goodUtil(emit, props)

总结

  1. setup 中无法访问 this 是设计选择,不是限制

  2. context 提供了 attrsslotsemit 这三个最常用的功能

  3. 其他功能通过独立的 API 提供(ref、watch、nextTick 等)

  4. 避免使用 getCurrentInstance(),优先使用声明式 API

  5. 这种设计带来更好的组合性、类型安全和测试便利


这种设计让组件逻辑更加清晰、可测试,也更容易提取和重用逻辑代码。

相关推荐
踩着两条虫1 小时前
VTJ.PRO 核心架构全公开!从设计稿到代码,揭秘AI智能体如何“听懂人话”
前端·vue.js·ai编程
蓝冰凌3 小时前
Vue 3 中 defineExpose 的行为【defineExpose暴露ref变量】详解:自动解包、响应性与实际使用
前端·javascript·vue.js
奔跑的呱呱牛3 小时前
generate-route-vue基于文件系统的 Vue Router 动态路由生成工具
前端·javascript·vue.js
sp42a3 小时前
在 NativeScript-Vue 中实现流畅的共享元素转场动画
vue.js·nativescript·app 开发
还是大剑师兰特5 小时前
Vue3 中 computed(计算属性)完整使用指南
前端·javascript·vue.js
孜孜不倦不忘初心5 小时前
Ant Design Vue 表格组件空数据统一处理 踩坑
前端·vue.js·ant design
csdn_aspnet5 小时前
查看 vite 与 vue 版本
javascript·vue.js
于先生吖6 小时前
SpringBoot+Vue 前后端分离短剧漫剧系统开发实战
vue.js·spring boot·后端
毕设源码-赖学姐6 小时前
【开题答辩全过程】以 基于VUE的环保网站设计为例,包含答辩的问题和答案
前端·javascript·vue.js