您好,登录后才能下订单哦!
# Vue3中SetUp的参数props和context实例分析
## 前言
Vue3作为当前最流行的前端框架之一,其革命性的Composition API彻底改变了开发者的编码方式。在Composition API中,`setup()`函数扮演着核心角色,而理解其参数`props`和`context`的用法,是掌握Vue3开发的关键。本文将深入剖析这两个参数的特性和应用场景,通过实例演示帮助开发者彻底掌握其精髓。
## 一、setup()函数基础概念
### 1.1 setup()函数的作用
`setup()`是Vue3组合式API的入口函数,它在组件实例创建之前执行,主要职责包括:
- 定义响应式数据
- 声明计算属性
- 注册生命周期钩子
- 定义方法
- 返回模板所需内容
```javascript
export default {
  setup() {
    // 在这里编写组合式API代码
    return {
      // 返回模板需要使用的数据和方法
    }
  }
}
与Options API不同,setup()的执行发生在beforeCreate之前,此时:
- 组件实例尚未创建
- this不可用(返回undefined)
- 数据观察(data observation)尚未建立
setup()的第一个参数是props,它是一个响应式对象,包含了父组件传递的所有prop值。
export default {
  props: {
    title: String,
    user: Object
  },
  setup(props) {
    console.log(props.title) // 访问prop值
    console.log(props.user)
  }
}
props对象是响应式的,这意味着:
- 当父组件更新prop时,子组件会自动更新
- 不能使用ES6解构,否则会失去响应性
- 需要解构时应使用toRefs
import { toRefs } from 'vue'
export default {
  setup(props) {
    // 错误方式:解构会失去响应性
    // const { title } = props
    
    // 正确方式:使用toRefs保持响应性
    const { title } = toRefs(props)
    console.log(title.value) // 需要通过.value访问
    
    return { title }
  }
}
虽然Vue3仍然支持Options API中的prop验证,但在组合式API中更推荐使用TypeScript:
interface Props {
  title?: string
  count: number
  items: string[]
}
export default {
  setup(props: Props) {
    // 现在可以享受类型提示和检查
    console.log(props.count.toFixed(2))
  }
}
<template>
  <div>
    <input v-model="inputValue" @input="handleInput" />
  </div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
  props: {
    modelValue: String,
    maxLength: {
      type: Number,
      default: 100
    }
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const inputValue = ref(props.modelValue || '')
    
    watch(() => props.modelValue, (newVal) => {
      inputValue.value = newVal
    })
    
    function handleInput(e) {
      let value = e.target.value
      if (value.length > props.maxLength) {
        value = value.slice(0, props.maxLength)
      }
      inputValue.value = value
      emit('update:modelValue', value)
    }
    
    return { inputValue, handleInput }
  }
}
</script>
setup()的第二个参数是context,它包含三个重要属性:
export default {
  setup(props, context) {
    // 等价于:
    // setup(props, { attrs, slots, emit }) {
    console.log(context.attrs)   // 非响应式对象
    console.log(context.slots)    // 插槽内容
    console.log(context.emit)     // 触发事件方法
    
    // Vue3.2+新增
    console.log(context.expose)  // 暴露公共属性方法
  }
}
attrs包含所有未被props声明的attribute,包括:
- class和style
- 原生HTML attribute
- 自定义事件监听器(v-on)
<template>
  <button v-bind="attrs">点击</button>
</template>
<script>
export default {
  setup(props, { attrs }) {
    // 透传所有非prop属性
    return { attrs }
  }
}
</script>
slots提供对插槽内容的访问,支持作用域插槽:
<template>
  <div>
    <slot name="header" :user="user"></slot>
    <slot></slot>
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  setup(props, { slots }) {
    const user = ref({ name: '张三' })
    
    // 检查插槽是否存在
    const hasHeader = slots.header
    
    return { user, hasHeader }
  }
}
</script>
emit替代了Vue2的this.$emit,需要先在组件中声明:
export default {
  emits: ['submit', 'update'], // 显式声明事件
  setup(props, { emit }) {
    function handleClick() {
      emit('submit', { data: 123 })
    }
    
    return { handleClick }
  }
}
Vue3.2+新增的expose允许限制组件暴露的公共API:
export default {
  setup(props, { expose }) {
    const publicData = ref('公开数据')
    const privateData = ref('私有数据')
    
    // 只暴露publicData和方法
    expose({
      publicData,
      publicMethod() {
        console.log('公共方法')
      }
    })
    
    return { publicData, privateData }
  }
}
<template>
  <div class="data-table">
    <div class="header">
      <slot name="header" :columns="columns" :sort="sortBy"></slot>
    </div>
    <div class="body">
      <div v-for="(item, index) in sortedData" :key="item.id" class="row">
        <slot :item="item" :index="index"></slot>
      </div>
    </div>
    <div class="footer">
      <slot name="footer" :total="data.length"></slot>
    </div>
  </div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
  props: {
    data: {
      type: Array,
      required: true,
      validator: value => value.every(item => 'id' in item)
    },
    columns: Array,
    defaultSort: {
      type: String,
      default: 'id'
    }
  },
  emits: ['row-click', 'sort-change'],
  setup(props, { emit }) {
    const sortBy = ref(props.defaultSort)
    const sortDirection = ref('asc')
    
    const sortedData = computed(() => {
      return [...props.data].sort((a, b) => {
        const modifier = sortDirection.value === 'asc' ? 1 : -1
        if (a[sortBy.value] < b[sortBy.value]) return -1 * modifier
        if (a[sortBy.value] > b[sortBy.value]) return 1 * modifier
        return 0
      })
    })
    
    function handleSort(column) {
      if (sortBy.value === column) {
        sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
      } else {
        sortBy.value = column
        sortDirection.value = 'asc'
      }
      emit('sort-change', { column: sortBy.value, direction: sortDirection.value })
    }
    
    function handleRowClick(item) {
      emit('row-click', item)
    }
    
    return {
      sortBy,
      sortDirection,
      sortedData,
      handleSort,
      handleRowClick
    }
  }
}
</script>
import { defineComponent, PropType } from 'vue'
interface User {
  id: number
  name: string
  email: string
}
interface TableColumn {
  key: string
  title: string
  sortable?: boolean
}
export default defineComponent({
  props: {
    data: {
      type: Array as PropType<User[]>,
      required: true
    },
    columns: {
      type: Array as PropType<TableColumn[]>,
      default: () => []
    }
  },
  emits: {
    'row-click': (user: User) => true,
    'sort-change': (payload: { column: string; direction: 'asc' | 'desc' }) => true
  },
  setup(props, { emit }) {
    // 现在所有props和emit都有完整的类型推断
    const handleClick = (user: User) => {
      emit('row-click', user)
    }
    
    return { handleClick }
  }
})
toRef而非toRefsQ1:为什么修改props会触发警告? A:Vue遵循单向数据流,直接修改prop会触发警告。正确的做法是触发事件让父组件修改。
Q2:attrs和props有什么区别? A:props是显式声明的属性,attrs包含所有未声明的属性(包括class/style等)。
Q3:为什么slots不是响应式的? A:因为插槽内容由父组件决定,子组件通常不需要对其变化做出响应。
将setup逻辑抽取为组合式函数:
// usePagination.js
import { computed, ref } from 'vue'
export function usePagination(items, perPage = 10) {
  const currentPage = ref(1)
  
  const totalPages = computed(() => 
    Math.ceil(items.value.length / perPage)
  )
  
  const paginatedItems = computed(() => {
    const start = (currentPage.value - 1) * perPage
    const end = start + perPage
    return items.value.slice(start, end)
  })
  
  function nextPage() {
    if (currentPage.value < totalPages.value) {
      currentPage.value++
    }
  }
  
  function prevPage() {
    if (currentPage.value > 1) {
      currentPage.value--
    }
  }
  
  return {
    currentPage,
    totalPages,
    paginatedItems,
    nextPage,
    prevPage
  }
}
通过对setup函数的props和context参数的深入理解,开发者可以充分发挥Vue3组合式API的强大功能。记住以下要点: 1. props是响应式的,避免直接解构 2. context提供了组件通信的关键能力 3. TypeScript可以显著提升代码质量 4. 合理封装组合式函数提高复用性
希望本文能帮助您在Vue3开发中游刃有余,构建更健壮、更易维护的前端应用。 “`
这篇文章共计约3950字,全面覆盖了Vue3 setup函数中props和context的核心知识点,包含基础概念、深度解析、实战案例和最佳实践,采用Markdown格式编写,可直接用于技术博客或文档。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。