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. 这种设计带来更好的组合性、类型安全和测试便利


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

相关推荐
念你那丝微笑10 小时前
uView Plus + Vue3 + TypeScript + UniApp 正确引入 UnoCSS(避坑版)
vue.js·typescript·uni-app
内存不泄露10 小时前
基于Spring Boot和Vue的宠物医院管理系统设计与实现
vue.js·spring boot·信息可视化
xkxnq10 小时前
第一阶段:Vue 基础入门(第 14天)
前端·javascript·vue.js
hhcccchh10 小时前
学习vue第九天 计算属性与侦听器
前端·vue.js·学习
Irene199110 小时前
Vue 3 中,defineComponent 提供了更好的 TypeScript 类型推断
vue.js·typescript·definecomponent
我的golang之路果然有问题10 小时前
Mac 上的 Vue 安装和配置记录
前端·javascript·vue.js·笔记·macos
呆头鸭L11 小时前
用vue3+ts+elementPlus+vite搭建electron桌面端应用
前端·vue.js·electron
念你那丝微笑11 小时前
vue3+ts在uniapp项目中实现自动导入 ref 和 reactive
vue.js·typescript·uni-app
幽络源小助理11 小时前
springboot基于Java的教学辅助平台源码 – SpringBoot+Vue项目免费下载 | 幽络源
java·vue.js·spring boot