Vue3?中的ref與props屬性詳解
ref 屬性 與 props
一、核心概念對(duì)比
特性 | ref (標(biāo)簽屬性) | props |
---|---|---|
作用對(duì)象 | DOM 元素/組件實(shí)例 | 組件間數(shù)據(jù)傳遞 |
數(shù)據(jù)流向 | 父組件訪問(wèn)子組件/DOM | 父組件 → 子組件 |
響應(yīng)性 | 直接操作對(duì)象 | 單向數(shù)據(jù)流(只讀) |
使用場(chǎng)景 | 獲取 DOM/調(diào)用子組件方法 | 組件參數(shù)傳遞 |
Vue3 變化 | 不再自動(dòng)數(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. 類(lèi)型安全(TypeScript)
// 父組件中定義組件引用類(lèi)型 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') // 錯(cuò)誤!直接解構(gòu)會(huì)失去響應(yīng)性 // const { title } = props
四、聯(lián)合使用場(chǎng)景
場(chǎng)景:表單驗(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é)
對(duì)比維度 | ref (標(biāo)簽屬性) | props |
---|---|---|
數(shù)據(jù)方向 | 父 → 子(操作子組件/DOM) | 父 → 子(數(shù)據(jù)傳遞) |
可修改性 | 可直接修改子組件暴露的內(nèi)容 | 只讀(需通過(guò)事件通知父組件修改) |
響應(yīng)式機(jī)制 | 直接引用對(duì)象 | 需要 toRef 保持響應(yīng)性 |
典型應(yīng)用 | DOM操作/調(diào)用子組件方法 | 組件參數(shù)配置 |
組合式 API | 通過(guò) ref() 創(chuàng)建引用 | 通過(guò) defineProps 聲明 |
六、最佳實(shí)踐指南
1. ref
使用原則
- 最小化暴露:只暴露必要的組件方法/數(shù)據(jù)
- 避免直接修改:不要通過(guò)
ref
直接修改子組件狀態(tài) - 配合 TypeScript:使用類(lèi)型定義確保安全訪問(wèn)
2. props
使用規(guī)范
- 只讀原則:始終視
props
為不可變數(shù)據(jù) - 響應(yīng)式轉(zhuǎn)換:使用
toRef
處理需要響應(yīng)式的props
- 明確驗(yàn)證:始終定義
props
的類(lèi)型驗(yàn)證
七、常見(jiàn)問(wèn)題解決
Q1: 為什么通過(guò) ref
訪問(wèn)子組件屬性得到 undefined
?
原因:子組件未通過(guò) defineExpose
暴露屬性
解決方案:
// 子組件 defineExpose({ publicMethod: () => {/* ... */} })
Q2: 如何同時(shí)使用 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: 如何類(lèi)型安全地組合 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)證通過(guò)') } } 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
屬性:用于直接訪問(wèn) DOM/子組件實(shí)例,需要配合defineExpose
使用props
:用于父組件向子組件傳遞數(shù)據(jù),需保持只讀特性- 協(xié)作模式:
- 父組件通過(guò)
props
傳遞數(shù)據(jù) - 子組件通過(guò)事件通知父組件
- 必要時(shí)通過(guò)
ref
調(diào)用子組件方法
- 父組件通過(guò)
- 類(lèi)型安全:使用 TypeScript 類(lèi)型定義確??煽吭L問(wèn)
事件傳遞
在 Vue3 中,子組件向父組件傳遞數(shù)據(jù)主要通過(guò) 事件機(jī)制 實(shí)現(xiàn)。以下是 5 種主要實(shí)現(xiàn)方式及其使用場(chǎng)景:
一、基礎(chǔ)事件傳遞 (推薦)
實(shí)現(xiàn)方式:
子組件觸發(fā)自定義事件 → 父組件監(jiān)聽(tīng)事件
<!-- 子組件 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
) - 通過(guò) TypeScript 定義事件類(lèi)型:
const emit = defineEmits<{ (e: 'sendData', payload: { message: string }): void }>()
二、v-model 雙向綁定 (表單場(chǎng)景推薦)
實(shí)現(xiàn)原理:v-model
是 :modelValue
+ @update:modelValue
的語(yǔ)法糖
<!-- 子組件 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)模式 (需要返回值時(shí))
適用場(chǎng)景:需要等待父組件處理結(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)用 (需要直接訪問(wèn)子組件)
<!-- 子組件 --> <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)管理方案 (跨組件通信)
適用場(chǎng)景:多層嵌套組件或兄弟組件通信
// 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>
方法對(duì)比表
方法 | 適用場(chǎng)景 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|---|
基礎(chǔ)事件傳遞 | 簡(jiǎn)單數(shù)據(jù)傳遞 | 直觀明確 | 多層嵌套時(shí)需逐層傳遞 |
v-model 綁定 | 表單輸入組件 | 語(yǔ)法簡(jiǎn)潔 | 僅適用簡(jiǎn)單雙向綁定 |
異步回調(diào)模式 | 需要父組件響應(yīng)結(jié)果 | 支持異步交互 | 邏輯復(fù)雜度稍高 |
Expose 方法 | 需要直接操作子組件 | 靈活性強(qiáng) | 破壞組件封裝性 |
狀態(tài)管理 | 跨組件/復(fù)雜場(chǎng)景 | 解耦組件關(guān)系 | 需要額外學(xué)習(xí)成本 |
最佳實(shí)踐指南
- 優(yōu)先使用事件傳遞:保持組件獨(dú)立性
- 復(fù)雜場(chǎng)景用狀態(tài)管理:Pinia 是 Vue3 官方推薦方案
- v-model 用于表單:保持雙向綁定的簡(jiǎn)潔性
- 避免濫用 ref:防止組件過(guò)度耦合
- TypeScript 類(lèi)型定義:
// 事件類(lèi)型定義 defineEmits<{ (e: 'updateData', payload: { id: number }): void (e: 'cancel'): void }>() // Props 類(lèi)型定義 defineProps<{ userId: number userName: string }>()
完整示例:購(gòu)物車(chē)組件交互
<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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
前端本地存儲(chǔ)方案localForage在vue3中使用方法
localForage是前端本地存儲(chǔ)的庫(kù),支持多種存儲(chǔ)后端,并提供了一致的API來(lái)存儲(chǔ)和檢索數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于前端本地存儲(chǔ)方案localForage在vue3中使用的相關(guān)資料,需要的朋友可以參考下2024-09-09vue 簡(jiǎn)單自動(dòng)補(bǔ)全的輸入框的示例
這篇文章主要介紹了vue 簡(jiǎn)單自動(dòng)補(bǔ)全的輸入框的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03vue+element實(shí)現(xiàn)動(dòng)態(tài)換膚的示例代碼
本文主要介紹了vue+element實(shí)現(xiàn)動(dòng)態(tài)換膚的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09uniapp+vue3路由跳轉(zhuǎn)傳參的實(shí)現(xiàn)
本文主要介紹了uniapp+vue3路由跳轉(zhuǎn)傳參的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11Vue中textarea自適應(yīng)高度方案的實(shí)現(xiàn)
本文主要介紹了Vue中textarea自適應(yīng)高度方案的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01vue3(vite)設(shè)置代理封裝axios api解耦功能
這篇文章主要介紹了vue3(vite)設(shè)置代理封裝axios api解耦,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12使用vue3搭建后臺(tái)系統(tǒng)的詳細(xì)步驟
這篇文章主要介紹了使用vue3搭建后臺(tái)系統(tǒng)的過(guò)程記錄,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08vue項(xiàng)目input標(biāo)簽checkbox,change和click綁定事件的區(qū)別說(shuō)明
這篇文章主要介紹了vue項(xiàng)目input標(biāo)簽checkbox,change和click綁定事件的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08vue中如何實(shí)現(xiàn)拖拽調(diào)整順序功能
這篇文章主要介紹了vue中如何實(shí)現(xiàn)拖拽調(diào)整順序功能問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05