Vue3 中的ref与props属性详解
作者:旺代
这篇文章主要介绍了Vue3 中的ref与props属性知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
ref 属性 与 props
一、核心概念对比
特性 | ref (标签属性) | props |
---|---|---|
作用对象 | DOM 元素/组件实例 | 组件间数据传递 |
数据流向 | 父组件访问子组件/DOM | 父组件 → 子组件 |
响应性 | 直接操作对象 | 单向数据流(只读) |
使用场景 | 获取 DOM/调用子组件方法 | 组件参数传递 |
Vue3 变化 | 不再自动数组形式 | 需要 defineProps 声明 |
二、ref 属性的深度解析
1. 基础用法
<!-- 获取 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() // 调用子组件方法 }) </script>
2. 组件引用规范
// 子组件 ChildComponent.vue <script setup> // 必须暴露方法才能被父组件调用 defineExpose({ sayHello: () => console.log('Hello from child!'), childData: ref('子组件数据') }) </script>
3. 类型安全(TypeScript)
// 父组件中定义组件引用类型 import ChildComponent from './ChildComponent.vue' const childRef = ref<InstanceType<typeof ChildComponent>>()
三、props 的响应式处理
1. 基础声明
<!-- 父组件 --> <ChildComponent :title="parentTitle" /> <!-- 子组件 --> <script setup> const props = defineProps({ title: { type: String, required: true } }) </script>
2. 保持响应性
// 正确方式:使用 toRef import { toRef } from 'vue' const title = toRef(props, 'title') // 错误!直接解构会失去响应性 // const { title } = props
四、联合使用场景
场景:表单验证组件
<!-- 父组件 --> <template> <ValidationForm ref="formRef" :rules="validationRules" @submit="handleSubmit" /> </template> <script setup> const formRef = ref(null) const validationRules = ref({/* 验证规则 */}) // 调用子组件方法 const validateForm = () => { formRef.value.validate() } </script>
<!-- 子组件 ValidationForm.vue --> <script setup> defineProps(['rules']) const validate = () => { // 执行验证逻辑 } // 暴露方法给父组件 defineExpose({ validate }) </script>
五、核心差异总结
对比维度 | ref (标签属性) | props |
---|---|---|
数据方向 | 父 → 子(操作子组件/DOM) | 父 → 子(数据传递) |
可修改性 | 可直接修改子组件暴露的内容 | 只读(需通过事件通知父组件修改) |
响应式机制 | 直接引用对象 | 需要 toRef 保持响应性 |
典型应用 | DOM操作/调用子组件方法 | 组件参数配置 |
组合式 API | 通过 ref() 创建引用 | 通过 defineProps 声明 |
六、最佳实践指南
1. ref
使用原则
- 最小化暴露:只暴露必要的组件方法/数据
- 避免直接修改:不要通过
ref
直接修改子组件状态 - 配合 TypeScript:使用类型定义确保安全访问
2. props
使用规范
- 只读原则:始终视
props
为不可变数据 - 响应式转换:使用
toRef
处理需要响应式的props
- 明确验证:始终定义
props
的类型验证
七、常见问题解决
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 })
八、综合应用示例
父组件:
<template> <UserForm ref="userForm" :user-data="formData" @submit="handleSubmit" /> <button @click="validateForm">验证表单</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('表单验证通过') } } const handleSubmit = (data) => { console.log('提交数据:', 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>
关键总结:
ref
属性:用于直接访问 DOM/子组件实例,需要配合defineExpose
使用props
:用于父组件向子组件传递数据,需保持只读特性- 协作模式:
- 父组件通过
props
传递数据 - 子组件通过事件通知父组件
- 必要时通过
ref
调用子组件方法
- 父组件通过
- 类型安全:使用 TypeScript 类型定义确保可靠访问
事件传递
在 Vue3 中,子组件向父组件传递数据主要通过 事件机制 实现。以下是 5 种主要实现方式及其使用场景:
一、基础事件传递 (推荐)
实现方式:
子组件触发自定义事件 → 父组件监听事件
<!-- 子组件 ChildComponent.vue --> <script setup> const emit = defineEmits(['sendData']) // 声明事件 const sendToParent = () => { emit('sendData', { message: 'Hello from child!' }) // 触发事件 } </script> <template> <button @click="sendToParent">发送数据</button> </template>
<!-- 父组件 ParentComponent.vue --> <template> <ChildComponent @send-data="handleData" /> </template> <script setup> const handleData = (payload) => { console.log(payload.message) // 输出:Hello from child! } </script>
最佳实践:
- 使用
kebab-case
事件名(如send-data
) - 通过 TypeScript 定义事件类型:
const emit = defineEmits<{ (e: 'sendData', payload: { message: string }): void }>()
二、v-model 双向绑定 (表单场景推荐)
实现原理: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" />
三、异步回调模式 (需要返回值时)
适用场景:需要等待父组件处理结果的异步操作
<!-- 子组件 --> <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 方法调用 (需要直接访问子组件)
<!-- 子组件 --> <script setup> const childData = ref('子组件数据') 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()) // 输出:子组件数据 childRef.value?.updateData('新数据') } </script>
五、状态管理方案 (跨组件通信)
适用场景:多层嵌套组件或兄弟组件通信
// 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> 当前计数:{{ counterStore.count }} </template>
方法对比表
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
基础事件传递 | 简单数据传递 | 直观明确 | 多层嵌套时需逐层传递 |
v-model 绑定 | 表单输入组件 | 语法简洁 | 仅适用简单双向绑定 |
异步回调模式 | 需要父组件响应结果 | 支持异步交互 | 逻辑复杂度稍高 |
Expose 方法 | 需要直接操作子组件 | 灵活性强 | 破坏组件封装性 |
状态管理 | 跨组件/复杂场景 | 解耦组件关系 | 需要额外学习成本 |
最佳实践指南
- 优先使用事件传递:保持组件独立性
- 复杂场景用状态管理: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>
到此这篇关于Vue3 中的ref与props的文章就介绍到这了,更多相关Vue3 ref与props内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!