Vue3 中的ref与props属性详解

 更新时间:2025年04月26日 16:11:40   作者:旺代  
这篇文章主要介绍了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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 前端本地存储方案localForage在vue3中使用方法

    前端本地存储方案localForage在vue3中使用方法

    localForage是前端本地存储的库,支持多种存储后端,并提供了一致的API来存储和检索数据,这篇文章主要给大家介绍了关于前端本地存储方案localForage在vue3中使用的相关资料,需要的朋友可以参考下
    2024-09-09
  • vue 简单自动补全的输入框的示例

    vue 简单自动补全的输入框的示例

    这篇文章主要介绍了vue 简单自动补全的输入框的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • vue+element实现动态换肤的示例代码

    vue+element实现动态换肤的示例代码

    本文主要介绍了vue+element实现动态换肤的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • uniapp+vue3路由跳转传参的实现

    uniapp+vue3路由跳转传参的实现

    本文主要介绍了uniapp+vue3路由跳转传参的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11
  • Vue中textarea自适应高度方案的实现

    Vue中textarea自适应高度方案的实现

    本文主要介绍了Vue中textarea自适应高度方案的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • vue仿淘宝订单状态的tab切换效果

    vue仿淘宝订单状态的tab切换效果

    这篇文章主要为大家详细介绍了vue仿淘宝订单状态tab切换效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • vue3(vite)设置代理封装axios api解耦功能

    vue3(vite)设置代理封装axios api解耦功能

    这篇文章主要介绍了vue3(vite)设置代理封装axios api解耦,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • 使用vue3搭建后台系统的详细步骤

    使用vue3搭建后台系统的详细步骤

    这篇文章主要介绍了使用vue3搭建后台系统的过程记录,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • vue项目input标签checkbox,change和click绑定事件的区别说明

    vue项目input标签checkbox,change和click绑定事件的区别说明

    这篇文章主要介绍了vue项目input标签checkbox,change和click绑定事件的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • vue中如何实现拖拽调整顺序功能

    vue中如何实现拖拽调整顺序功能

    这篇文章主要介绍了vue中如何实现拖拽调整顺序功能问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05

最新评论