Vue中defineProps、defineEmits、defineExpose使用深度解析
前言
本文深度解析Vue3 <script setup> 語法中的三個(gè)核心編譯器宏:defineProps、defineEmits 和 defineExpose。
defineProps 用于定義組件屬性,支持多種驗(yàn)證方式和TypeScript類型聲明;
defineEmits 用于聲明組件事件,可與v-model集成;
defineExpose 則用于暴露組件實(shí)例的公共API。
文章詳細(xì)介紹了每個(gè)宏的基本用法、TypeScript支持、最佳實(shí)踐以及常見問題解答,并展示了三者如何配合使用構(gòu)建完整組件。
這些宏必須在<script setup>頂層使用,具有編譯時(shí)常量限制,但能顯著提升代碼簡潔性和類型安全性。
這三個(gè)都是 Vue 3 <script setup> 語法中的編譯器宏(編譯時(shí)處理的特殊函數(shù))。它們不需要導(dǎo)入,直接在 <script setup> 中使用。
1.defineProps- 定義組件屬性
基本用法
<script setup>
// 方式1:數(shù)組形式(簡單,無類型檢查)
const props = defineProps(['title', 'count'])
// 方式2:對(duì)象形式(推薦,支持完整驗(yàn)證)
const props = defineProps({
title: {
type: String,
required: true,
validator: (value) => value.length > 0
},
count: {
type: Number,
default: 0,
validator: (value) => value >= 0
},
items: {
type: Array,
default: () => []
},
config: {
type: Object,
default: () => ({})
}
})
</script>TypeScript 用法
<script setup lang="ts">
// 方式1:純類型聲明(運(yùn)行時(shí)無驗(yàn)證)
interface Props {
title: string
count?: number
items?: string[]
}
const props = defineProps<Props>()
// 方式2:類型聲明 + 默認(rèn)值(Vue 3.3+)
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => []
})
// 方式3:復(fù)雜類型(聯(lián)合類型、自定義類型)
type Status = 'loading' | 'success' | 'error'
interface ComplexProps {
id: number | string // 聯(lián)合類型
status: Status // 自定義類型
metadata?: Record<string, any>
}
const props = defineProps<ComplexProps>()
</script>響應(yīng)式處理 Props
<script setup>
import { computed, toRef, toRefs } from 'vue'
const props = defineProps({
user: Object,
active: Boolean
})
// ? 錯(cuò)誤:直接解構(gòu)會(huì)丟失響應(yīng)式
const { user, active } = props
// ? 正確:使用 toRefs 保持響應(yīng)式
const { user, active } = toRefs(props)
// ? 使用 computed 派生值
const userName = computed(() => props.user?.name || 'Unknown')
// ? 使用 toRef 處理單個(gè) prop(props 可能沒有該屬性)
const userId = toRef(props, 'id') // 安全訪問,有默認(rèn)值
</script>2.defineEmits- 定義組件事件
基本用法
<script setup>
// 方式1:數(shù)組形式(簡單)
const emit = defineEmits(['submit', 'update:value'])
// 方式2:對(duì)象形式(支持驗(yàn)證)
const emit = defineEmits({
// 無驗(yàn)證
submit: null,
// 帶驗(yàn)證函數(shù)
'update:value': (value) => {
if (typeof value === 'string' && value.length > 0) {
return true
}
console.warn('Invalid value')
return false
},
// 多個(gè)參數(shù)的驗(yàn)證
'form-submit': (data, timestamp) => {
return data && typeof timestamp === 'number'
}
})
// 觸發(fā)事件
const handleSubmit = () => {
emit('submit', { id: 1, data: 'test' })
}
const updateValue = (value) => {
emit('update:value', value)
}
</script>TypeScript 用法
<script setup lang="ts">
// 方式1:類型字面量
const emit = defineEmits<{
(e: 'submit', data: FormData): void
(e: 'update:value', value: string): void
(e: 'toggle'): void
}>()
// 方式2:使用接口(更清晰)
interface Emits {
(e: 'submit', data: FormData): void
(e: 'update:modelValue', value: any): void
(e: 'click', event: MouseEvent): void
}
const emit = defineEmits<Emits>()
// 使用示例
const handleClick = (event: MouseEvent) => {
emit('click', event)
}
</script>與 v-model 集成
<!-- CustomInput.vue -->
<script setup>
// 支持 v-model
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const updateValue = (event) => {
emit('update:modelValue', event.target.value)
}
</script>
<template>
<input :value="modelValue" @input="updateValue" />
</template>
<!-- 父組件使用 -->
<template>
<CustomInput v-model="username" />
</template>多個(gè) v-model(Vue 3.3+)
<!-- UserForm.vue -->
<script setup>
// 多個(gè) v-model
const props = defineProps({
firstName: String,
lastName: String,
age: Number
})
const emit = defineEmits([
'update:firstName',
'update:lastName',
'update:age'
])
</script>
<template>
<input
:value="firstName"
@input="emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="emit('update:lastName', $event.target.value)"
/>
</template>
<!-- 父組件使用 -->
<template>
<UserForm
v-model:firstName="first"
v-model:lastName="last"
v-model:age="userAge"
/>
</template>3.defineExpose- 暴露組件實(shí)例
為什么需要?
默認(rèn)情況下,<script setup> 中的變量是私有的,父組件無法訪問。defineExpose 用于顯式暴露組件的方法和屬性。
基本用法
<!-- ChildComponent.vue -->
<script setup>
import { ref, computed } from 'vue'
// 私有變量(父組件無法訪問)
const privateCount = ref(0)
const internalData = ref('secret')
// 公共方法
const publicMethod = () => {
console.log('Public method called')
privateCount.value++
}
// 計(jì)算屬性
const publicComputed = computed(() => privateCount.value * 2)
// 暴露給父組件
defineExpose({
publicMethod,
publicComputed,
// 也可以直接暴露 ref
count: privateCount,
// 甚至暴露函數(shù)
reset: () => { privateCount.value = 0 }
})
</script>父組件使用
<!-- ParentComponent.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = ref(null)
onMounted(() => {
// 訪問暴露的屬性和方法
if (childRef.value) {
childRef.value.publicMethod() // ? 可以調(diào)用
console.log(childRef.value.publicComputed) // ? 可以訪問
console.log(childRef.value.count) // ? 可以訪問(因?yàn)楸槐┞叮?
// ? 無法訪問未暴露的
console.log(childRef.value.internalData) // undefined
console.log(childRef.value.privateCount) // undefined
}
})
</script>
<template>
<ChildComponent ref="childRef" />
</template>TypeScript 類型支持
<!-- ChildComponent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const name = ref('Vue')
const increment = () => {
count.value++
}
// 定義暴露的類型
export interface ExposedAPI {
count: number
name: string
increment: () => void
}
defineExpose<ExposedAPI>({
count,
name,
increment
})
</script>
<!-- ParentComponent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import ChildComponent, { ExposedAPI } from './ChildComponent.vue'
const childRef = ref<ExposedAPI>()
// 現(xiàn)在有完整的類型提示
childRef.value?.increment() // ? 類型安全
</script>三者的關(guān)系與配合使用
完整組件示例
<!-- UserProfile.vue -->
<script setup>
import { ref, computed, watch } from 'vue'
// 1. 定義 Props
const props = defineProps({
userId: {
type: [String, Number], // 支持多種類型
required: true
},
editable: {
type: Boolean,
default: false
}
})
// 2. 定義 Emits
const emit = defineEmits({
'update:user': (userData) => {
return userData && typeof userData.id === 'number'
},
'save': null,
'cancel': null
})
// 3. 組件內(nèi)部狀態(tài)
const userData = ref(null)
const loading = ref(false)
const error = ref(null)
// 4. 方法
const fetchUser = async () => {
loading.value = true
try {
const response = await fetch(`/api/users/${props.userId}`)
userData.value = await response.json()
emit('update:user', userData.value)
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
const saveChanges = async () => {
// 保存邏輯...
emit('save', userData.value)
}
// 5. 暴露給父組件的方法
defineExpose({
refresh: fetchUser,
reset: () => {
userData.value = null
error.value = null
}
})
// 6. 生命周期/監(jiān)聽
watch(() => props.userId, fetchUser, { immediate: true })
</script>
<template>
<!-- 模板內(nèi)容 -->
</template>注意事項(xiàng)和最佳實(shí)踐
1.執(zhí)行時(shí)機(jī)
// 這些編譯器宏必須在 <script setup> 的頂層作用域中使用
// ? 錯(cuò)誤:不能在函數(shù)內(nèi)使用
function setupProps() {
defineProps({}) // 編譯錯(cuò)誤
}
// ? 正確:頂層使用
defineProps({})2.重復(fù)定義
// ? 錯(cuò)誤:不能多次調(diào)用
defineProps({ title: String })
defineProps({ count: Number }) // 編譯錯(cuò)誤
// ? 正確:一次定義所有
defineProps({
title: String,
count: Number
})3.與 Options API 混用
vue
<script>
// 可以在同一個(gè)組件中與 Options API 混用
export default {
// Options API
inheritAttrs: false,
// 自定義選項(xiàng)
customOption: 'value'
}
</script>
<script setup>
// Composition API
const props = defineProps({/* ... */})
</script>4.使用限制
// 不能用在普通 <script> 中
<script>
// ? 錯(cuò)誤:不能在普通 script 中使用
defineProps({}) // 編譯錯(cuò)誤
</script>
// 不能動(dòng)態(tài)生成
const propName = 'title'
// ? 錯(cuò)誤:參數(shù)必須是編譯時(shí)常量
defineProps({ [propName]: String })5.最佳實(shí)踐總結(jié)
| 宏 | 最佳實(shí)踐 |
|---|---|
| defineProps | 1. 始終添加類型驗(yàn)證 2. 為可選屬性設(shè)置默認(rèn)值 3. 使用 TypeScript 泛型獲得更好類型安全 4. 避免在子組件中修改 props |
| defineEmits | 1. 事件名使用 kebab-case 2. 為復(fù)雜事件添加驗(yàn)證函數(shù) 3. 使用 TypeScript 定義完整事件簽名 4. 傳遞最小必要數(shù)據(jù) |
| defineExpose | 1. 僅暴露必要的 API 2. 為暴露的 API 添加 TypeScript 接口 3. 避免暴露內(nèi)部狀態(tài) 4. 提供清晰的公共方法名 |
常見問題解答
Q1: 為什么需要.value訪問 props?
const props = defineProps({ count: Number })
// ? 錯(cuò)誤:props 本身不是 ref
props.count.value // undefined
// ? 正確:直接訪問
console.log(props.count)
// ? 如果需要 ref,使用 toRef
import { toRef } from 'vue'
const countRef = toRef(props, 'count')Q2: 如何訪問未定義的 props?
const props = defineProps({ definedProp: String })
// 安全訪問未定義的 prop
import { toRef } from 'vue'
const optionalProp = toRef(props, 'optionalProp') // 返回一個(gè) ref,即使 prop 未定義Q3: defineExpose 會(huì)暴露所有內(nèi)容嗎?
// 不會(huì)!defineExpose 是選擇性的
const publicData = ref('public')
const privateData = ref('private')
defineExpose({ publicData })
// 只有 publicData 被暴露,privateData 保持私有Q4: 可以在組合式函數(shù)中使用這些宏嗎?
// ? 錯(cuò)誤:不能在組合式函數(shù)中使用
export function useFeature() {
defineProps({}) // 編譯錯(cuò)誤
return {}
}
// ? 正確:只能在組件頂層使用這三個(gè)編譯器宏是 Vue 3 <script setup> 的核心,它們讓組件定義更加簡潔、類型安全,同時(shí)保持了良好的封裝性。掌握它們的使用是高效開發(fā) Vue 3 應(yīng)用的關(guān)鍵。
總結(jié)
到此這篇關(guān)于Vue中defineProps、defineEmits、defineExpose使用的文章就介紹到這了,更多相關(guān)Vue defineProps、defineEmits、defineExpose內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用el-table+el-tree+el-select動(dòng)態(tài)選擇對(duì)應(yīng)值
小編在做需求時(shí),遇到了在el-table表格中加入多條數(shù)據(jù),并且每條數(shù)據(jù)要通過el-select來選取相應(yīng)的值,做到動(dòng)態(tài)選擇,下面這篇文章主要給大家介紹了關(guān)于如何使用el-table+el-tree+el-select動(dòng)態(tài)選擇對(duì)應(yīng)值的相關(guān)資料,需要的朋友可以參考下2023-01-01
Vue表單數(shù)據(jù)修改與刪除功能實(shí)現(xiàn)
本文通過實(shí)例代碼介紹了Vue表單數(shù)據(jù)修改與刪除功能實(shí)現(xiàn),結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友跟隨小編一起看看吧2023-10-10
vue中如何創(chuàng)建多個(gè)ueditor實(shí)例教程
這篇文章主要給大家介紹了關(guān)于vue中如何創(chuàng)建多個(gè)ueditor的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11
深入理解Vue.js3中Reactive的實(shí)現(xiàn)
reactive是Vue 3的Composition API中的一個(gè)函數(shù),它允許你創(chuàng)建一個(gè)響應(yīng)式的數(shù)據(jù)對(duì)象,本文主要介紹了深入理解Vue.js3中Reactive的實(shí)現(xiàn),感興趣的可以了解一下2024-01-01
vue如何調(diào)用攝像頭實(shí)現(xiàn)拍照上傳圖片、本地上傳圖片
這篇文章主要給大家介紹了關(guān)于vue如何調(diào)用攝像頭實(shí)現(xiàn)拍照上傳圖片、本地上傳圖片的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-07-07
vue項(xiàng)目中應(yīng)用ueditor自定義上傳按鈕功能
這篇文章主要介紹了vue項(xiàng)目中應(yīng)用ueditor自定義上傳按鈕功能,文中以vue-cli生成的項(xiàng)目為例給大家介紹了vue項(xiàng)目中使用ueditor的方法,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-04-04
Vite創(chuàng)建項(xiàng)目的實(shí)現(xiàn)步驟
隨著 Vite2 的發(fā)布并日趨穩(wěn)定,現(xiàn)在越來越多的項(xiàng)目開始嘗試使用它。本文我們就介紹了Vite創(chuàng)建項(xiàng)目的實(shí)現(xiàn)步驟,感興趣的可以了解一下2021-07-07

