Vue3?中的ref與props屬性詳解
ref 屬性 與 props
一、核心概念對比
| 特性 | ref (標(biāo)簽屬性) | props |
|---|---|---|
| 作用對象 | DOM 元素/組件實(shí)例 | 組件間數(shù)據(jù)傳遞 |
| 數(shù)據(jù)流向 | 父組件訪問子組件/DOM | 父組件 → 子組件 |
| 響應(yīng)性 | 直接操作對象 | 單向數(shù)據(jù)流(只讀) |
| 使用場景 | 獲取 DOM/調(diào)用子組件方法 | 組件參數(shù)傳遞 |
| Vue3 變化 | 不再自動數(shù)組形式 | 需要 defineProps 聲明 |
二、ref 屬性的深度解析
1. 基礎(chǔ)用法
<!-- 獲取 DOM 元素 -->
<template>
<input ref="inputRef" type="text">
<ChildComponent ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
// DOM 引用
const inputRef = ref(null)
// 組件引用
const childRef = ref(null)
onMounted(() => {
inputRef.value.focus() // 操作 DOM
childRef.value.sayHello() // 調(diào)用子組件方法
})
</script>2. 組件引用規(guī)范
// 子組件 ChildComponent.vue
<script setup>
// 必須暴露方法才能被父組件調(diào)用
defineExpose({
sayHello: () => console.log('Hello from child!'),
childData: ref('子組件數(shù)據(jù)')
})
</script>3. 類型安全(TypeScript)
// 父組件中定義組件引用類型 import ChildComponent from './ChildComponent.vue' const childRef = ref<InstanceType<typeof ChildComponent>>()
三、props 的響應(yīng)式處理
1. 基礎(chǔ)聲明
<!-- 父組件 -->
<ChildComponent :title="parentTitle" />
<!-- 子組件 -->
<script setup>
const props = defineProps({
title: {
type: String,
required: true
}
})
</script>2. 保持響應(yīng)性
// 正確方式:使用 toRef
import { toRef } from 'vue'
const title = toRef(props, 'title')
// 錯誤!直接解構(gòu)會失去響應(yīng)性
// const { title } = props四、聯(lián)合使用場景
場景:表單驗(yàn)證組件
<!-- 父組件 -->
<template>
<ValidationForm
ref="formRef"
:rules="validationRules"
@submit="handleSubmit"
/>
</template>
<script setup>
const formRef = ref(null)
const validationRules = ref({/* 驗(yàn)證規(guī)則 */})
// 調(diào)用子組件方法
const validateForm = () => {
formRef.value.validate()
}
</script><!-- 子組件 ValidationForm.vue -->
<script setup>
defineProps(['rules'])
const validate = () => {
// 執(zhí)行驗(yàn)證邏輯
}
// 暴露方法給父組件
defineExpose({ validate })
</script>五、核心差異總結(jié)
| 對比維度 | ref (標(biāo)簽屬性) | props |
|---|---|---|
| 數(shù)據(jù)方向 | 父 → 子(操作子組件/DOM) | 父 → 子(數(shù)據(jù)傳遞) |
| 可修改性 | 可直接修改子組件暴露的內(nèi)容 | 只讀(需通過事件通知父組件修改) |
| 響應(yīng)式機(jī)制 | 直接引用對象 | 需要 toRef 保持響應(yīng)性 |
| 典型應(yīng)用 | DOM操作/調(diào)用子組件方法 | 組件參數(shù)配置 |
| 組合式 API | 通過 ref() 創(chuàng)建引用 | 通過 defineProps 聲明 |
六、最佳實(shí)踐指南
1. ref 使用原則
- 最小化暴露:只暴露必要的組件方法/數(shù)據(jù)
- 避免直接修改:不要通過
ref直接修改子組件狀態(tài) - 配合 TypeScript:使用類型定義確保安全訪問
2. props 使用規(guī)范
- 只讀原則:始終視
props為不可變數(shù)據(jù) - 響應(yīng)式轉(zhuǎn)換:使用
toRef處理需要響應(yīng)式的props - 明確驗(yàn)證:始終定義
props的類型驗(yàn)證
七、常見問題解決
Q1: 為什么通過 ref 訪問子組件屬性得到 undefined?
原因:子組件未通過 defineExpose 暴露屬性
解決方案:
// 子組件
defineExpose({
publicMethod: () => {/* ... */}
})Q2: 如何同時使用 ref 和 v-for?
<template>
<ChildComponent
v-for="item in list"
:key="item.id"
:ref="setItemRef"
/>
</template>
<script setup>
const itemRefs = ref([])
const setItemRef = el => {
if (el) itemRefs.value.push(el)
}
</script>Q3: 如何類型安全地組合 ref 和 props?
// 父組件
import ChildComponent from './ChildComponent.vue'
type ChildComponentExpose = {
validate: () => boolean
}
const childRef = ref<ChildComponentExpose>()
// 子組件
defineExpose<ChildComponentExpose>({
validate: () => true
})八、綜合應(yīng)用示例
父組件:
<template>
<UserForm
ref="userForm"
:user-data="formData"
@submit="handleSubmit"
/>
<button @click="validateForm">驗(yàn)證表單</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import UserForm from './UserForm.vue'
type UserFormExpose = {
validate: () => boolean
resetForm: () => void
}
const userForm = ref<UserFormExpose>()
const formData = ref({ name: '', email: '' })
const validateForm = () => {
if (userForm.value?.validate()) {
console.log('表單驗(yàn)證通過')
}
}
const handleSubmit = (data) => {
console.log('提交數(shù)據(jù):', data)
}
</script>子組件 UserForm.vue:
<template>
<form @submit.prevent="submitForm">
<input v-model="localData.name">
<input v-model="localData.email">
<button type="submit">提交</button>
</form>
</template>
<script setup lang="ts">
import { ref, toRefs } from 'vue'
const props = defineProps<{
userData: {
name: string
email: string
}
}>()
const emit = defineEmits(['submit'])
// 本地副本(避免直接修改 props)
const localData = ref({ ...props.userData })
const validate = () => {
return localData.value.name.length > 0
&& localData.value.email.includes('@')
}
const resetForm = () => {
localData.value = { name: '', email: '' }
}
const submitForm = () => {
emit('submit', localData.value)
}
defineExpose({
validate,
resetForm
})
</script>關(guān)鍵總結(jié):
ref屬性:用于直接訪問 DOM/子組件實(shí)例,需要配合defineExpose使用props:用于父組件向子組件傳遞數(shù)據(jù),需保持只讀特性- 協(xié)作模式:
- 父組件通過
props傳遞數(shù)據(jù) - 子組件通過事件通知父組件
- 必要時通過
ref調(diào)用子組件方法
- 父組件通過
- 類型安全:使用 TypeScript 類型定義確??煽吭L問
事件傳遞
在 Vue3 中,子組件向父組件傳遞數(shù)據(jù)主要通過 事件機(jī)制 實(shí)現(xiàn)。以下是 5 種主要實(shí)現(xiàn)方式及其使用場景:
一、基礎(chǔ)事件傳遞 (推薦)
實(shí)現(xiàn)方式:
子組件觸發(fā)自定義事件 → 父組件監(jiān)聽事件
<!-- 子組件 ChildComponent.vue -->
<script setup>
const emit = defineEmits(['sendData']) // 聲明事件
const sendToParent = () => {
emit('sendData', { message: 'Hello from child!' }) // 觸發(fā)事件
}
</script>
<template>
<button @click="sendToParent">發(fā)送數(shù)據(jù)</button>
</template><!-- 父組件 ParentComponent.vue -->
<template>
<ChildComponent @send-data="handleData" />
</template>
<script setup>
const handleData = (payload) => {
console.log(payload.message) // 輸出:Hello from child!
}
</script>最佳實(shí)踐:
- 使用
kebab-case事件名(如send-data) - 通過 TypeScript 定義事件類型:
const emit = defineEmits<{
(e: 'sendData', payload: { message: string }): void
}>()二、v-model 雙向綁定 (表單場景推薦)
實(shí)現(xiàn)原理:v-model 是 :modelValue + @update:modelValue 的語法糖
<!-- 子組件 InputComponent.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const updateValue = (e) => {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input
:value="modelValue"
@input="updateValue"
>
</template><!-- 父組件 -->
<template>
<InputComponent v-model="inputValue" />
</template>
<script setup>
const inputValue = ref('')
</script>多 v-model 綁定:
<ChildComponent v-model:name="userName" v-model:age="userAge" />
三、異步回調(diào)模式 (需要返回值時)
適用場景:需要等待父組件處理結(jié)果的異步操作
<!-- 子組件 -->
<script setup>
const emit = defineEmits(['request'])
const handleClick = async () => {
const response = await emit('request', { id: 123 })
console.log('父組件返回:', response)
}
</script><!-- 父組件 -->
<template>
<ChildComponent @request="handleRequest" />
</template>
<script setup>
const handleRequest = async (payload) => {
const data = await fetchData(payload.id)
return data // 返回給子組件
}
</script>四、Expose 方法調(diào)用 (需要直接訪問子組件)
<!-- 子組件 -->
<script setup>
const childData = ref('子組件數(shù)據(jù)')
defineExpose({
getData: () => childData.value,
updateData: (newVal) => childData.value = newVal
})
</script><!-- 父組件 -->
<template>
<ChildComponent ref="childRef" />
</template>
<script setup>
const childRef = ref(null)
const getChildData = () => {
console.log(childRef.value?.getData()) // 輸出:子組件數(shù)據(jù)
childRef.value?.updateData('新數(shù)據(jù)')
}
</script>五、狀態(tài)管理方案 (跨組件通信)
適用場景:多層嵌套組件或兄弟組件通信
// store/counter.js
import { reactive } from 'vue'
export const counterStore = reactive({
count: 0,
increment() {
this.count++
}
})<!-- 子組件 -->
<script setup>
import { counterStore } from './store/counter'
const updateCount = () => {
counterStore.increment()
}
</script><!-- 父組件 -->
<script setup>
import { counterStore } from './store/counter'
</script>
<template>
當(dāng)前計(jì)數(shù):{{ counterStore.count }}
</template>方法對比表
| 方法 | 適用場景 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| 基礎(chǔ)事件傳遞 | 簡單數(shù)據(jù)傳遞 | 直觀明確 | 多層嵌套時需逐層傳遞 |
| v-model 綁定 | 表單輸入組件 | 語法簡潔 | 僅適用簡單雙向綁定 |
| 異步回調(diào)模式 | 需要父組件響應(yīng)結(jié)果 | 支持異步交互 | 邏輯復(fù)雜度稍高 |
| Expose 方法 | 需要直接操作子組件 | 靈活性強(qiáng) | 破壞組件封裝性 |
| 狀態(tài)管理 | 跨組件/復(fù)雜場景 | 解耦組件關(guān)系 | 需要額外學(xué)習(xí)成本 |
最佳實(shí)踐指南
- 優(yōu)先使用事件傳遞:保持組件獨(dú)立性
- 復(fù)雜場景用狀態(tài)管理:Pinia 是 Vue3 官方推薦方案
- v-model 用于表單:保持雙向綁定的簡潔性
- 避免濫用 ref:防止組件過度耦合
- TypeScript 類型定義:
// 事件類型定義
defineEmits<{
(e: 'updateData', payload: { id: number }): void
(e: 'cancel'): void
}>()
// Props 類型定義
defineProps<{
userId: number
userName: string
}>()完整示例:購物車組件交互
<template>
<div class="cart-item">
<span>{{ item.name }}</span>
<input
type="number"
:value="item.quantity"
@input="updateQuantity($event.target.value)"
>
<button @click="emit('remove', item.id)">刪除</button>
</div>
</template>
<!-- 子組件 CartItem.vue -->
<script setup>
const props = defineProps({
item: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:quantity', 'remove'])
const updateQuantity = (newQty) => {
emit('update:quantity', {
id: props.item.id,
qty: newQty
})
}
</script><template>
<CartItem
v-for="item in cartItems"
:key="item.id"
:item="item"
@update:quantity="handleUpdate"
@remove="handleRemove"
/>
</template>
<!-- 父組件 ShoppingCart.vue -->
<script setup>
const cartItems = ref([
{ id: 1, name: '商品A', quantity: 2 },
{ id: 2, name: '商品B', quantity: 1 }
])
const handleUpdate = ({ id, qty }) => {
const item = cartItems.value.find(i => i.id === id)
if (item) item.quantity = Number(qty)
}
const handleRemove = (id) => {
cartItems.value = cartItems.value.filter(i => i.id !== id)
}
</script>到此這篇關(guān)于Vue3 中的ref與props的文章就介紹到這了,更多相關(guān)Vue3 ref與props內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue+element實(shí)現(xiàn)動態(tài)換膚的示例代碼
本文主要介紹了vue+element實(shí)現(xiàn)動態(tài)換膚的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
uniapp+vue3路由跳轉(zhuǎn)傳參的實(shí)現(xiàn)
本文主要介紹了uniapp+vue3路由跳轉(zhuǎn)傳參的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
Vue中textarea自適應(yīng)高度方案的實(shí)現(xiàn)
本文主要介紹了Vue中textarea自適應(yīng)高度方案的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
vue3(vite)設(shè)置代理封裝axios api解耦功能
這篇文章主要介紹了vue3(vite)設(shè)置代理封裝axios api解耦,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
vue項(xiàng)目input標(biāo)簽checkbox,change和click綁定事件的區(qū)別說明
這篇文章主要介紹了vue項(xiàng)目input標(biāo)簽checkbox,change和click綁定事件的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
vue中如何實(shí)現(xiàn)拖拽調(diào)整順序功能
這篇文章主要介紹了vue中如何實(shí)現(xiàn)拖拽調(diào)整順序功能問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05

