element-plus源码解读2——vue3组件的ref访问与defineExpose暴露机制

vue3组件的ref访问与defineExpose暴露机制

vue官方文档:

refcn.vuejs.org/api/reactiv...

defineExposecn.vuejs.org/api/sfc-scr...

以el-button举例:

1. 正确的访问方式

看 Button 组件暴露的内容:

83:94:packages/components/button/src/button.vue 复制代码
defineExpose({
  /** @description button html element */
  ref: _ref,
  /** @description button size */
  size: _size,
  /** @description button type */
  type: _type,
  /** @description button disabled */
  disabled: _disabled,
  /** @description whether adding space */
  shouldAddSpace,
})

2. 实际使用示例

vue 复制代码
<template>
  <el-button ref="buttonRef" type="primary" size="large">
    按钮
  </el-button>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import type { ButtonInstance } from 'element-plus'

const buttonRef = ref<ButtonInstance>()

onMounted(() => {
  // ✅ 正确:访问所有暴露的属性
  console.log('DOM 元素:', buttonRef.value?.ref)        // HTMLButtonElement
  console.log('按钮尺寸:', buttonRef.value?.size)       // ComputedRef<'large'>
  console.log('按钮类型:', buttonRef.value?.type)      // ComputedRef<'primary'>
  console.log('是否禁用:', buttonRef.value?.disabled)   // ComputedRef<boolean>
  console.log('是否加空格:', buttonRef.value?.shouldAddSpace) // ComputedRef<boolean>
  
  // ✅ 打印整个组件实例,可以看到所有暴露的属性
  console.log('组件实例:', buttonRef.value)
})
</script>

3. 打印结果示例

当你 console.log(buttonRef.value) 时,会看到类似:

js 复制代码
{
  ref: HTMLButtonElement,           // DOM 元素
  size: ComputedRef<'large'>,        // 尺寸(注意是 ComputedRef)
  type: ComputedRef<'primary'>,      // 类型(注意是 ComputedRef)
  disabled: ComputedRef<false>,      // 禁用状态(注意是 ComputedRef)
  shouldAddSpace: ComputedRef<false> // 是否加空格(注意是 ComputedRef)
}

4. 重要提示:ComputedRef 的访问

注意 sizetypedisabled 等是 ComputedRef,访问值需要用 .value

ts 复制代码
// ❌ 错误:这样得到的是 ComputedRef 对象
console.log(buttonRef.value?.size)  // ComputedRef { ... }

// ✅ 正确:需要 .value 才能拿到实际值
console.log(buttonRef.value?.size.value)  // 'large'
console.log(buttonRef.value?.type.value)  // 'primary'
console.log(buttonRef.value?.disabled.value)  // false

说明 Vue 3 的生命周期和 ref 访问时机:

1. Vue 3 没有 onCreated 钩子

在 Vue 3 的 Composition API 中:

  • 没有 onCreated() 钩子
  • setup() 函数本身就相当于 Vue 2 的 created + beforeCreate
  • 如果需要访问 DOM 或组件实例,应该用 onMounted()

2. 为什么必须在 onMounted() 中?

setup() 顶层(组件未挂载)
vue 复制代码
<script setup>
import { ref } from 'vue'
import type { ButtonInstance } from 'element-plus'

const buttonRef = ref<ButtonInstance>()

// ❌ 错误:此时 buttonRef.value 是 undefined
// 因为组件还没有挂载,ref 还没有被赋值
console.log(buttonRef.value)  // undefined
</script>
onMounted() 中(组件已挂载)
vue 复制代码
<script setup>
import { ref, onMounted } from 'vue'
import type { ButtonInstance } from 'element-plus'

const buttonRef = ref<ButtonInstance>()

onMounted(() => {
  // ✅ 正确:此时组件已经挂载,ref 已经被赋值
  console.log(buttonRef.value)  // ButtonInstance 对象
  console.log(buttonRef.value?.ref)  // HTMLButtonElement
})
</script>

3. Vue 3 生命周期对比

Vue 2 Options API Vue 3 Composition API 说明
beforeCreate setup() 开始执行 组件创建前
created setup() 执行中 组件创建后(但未挂载)
beforeMount onBeforeMount() 挂载前
mounted onMounted() 挂载后(DOM 已存在)
beforeUpdate onBeforeUpdate() 更新前
updated onUpdated() 更新后
beforeUnmount onBeforeUnmount() 卸载前
unmounted onUnmounted() 卸载后

4. 完整示例对比

错误示例(在 setup 顶层)
vue 复制代码
<template>
  <el-button ref="buttonRef">按钮</el-button>
</template>

<script setup>
import { ref } from 'vue'
import type { ButtonInstance } from 'element-plus'

const buttonRef = ref<ButtonInstance>()

// ❌ 错误:此时 buttonRef.value 是 undefined
console.log('setup 顶层:', buttonRef.value)  // undefined
</script>
正确示例(在 onMounted 中)
vue 复制代码
<template>
  <el-button ref="buttonRef">按钮</el-button>
</template>

<script setup>
import { ref, onMounted, onBeforeMount } from 'vue'
import type { ButtonInstance } from 'element-plus'

const buttonRef = ref<ButtonInstance>()

// 在 setup 顶层
console.log('setup 顶层:', buttonRef.value)  // undefined

// 在 beforeMount 中
onBeforeMount(() => {
  console.log('beforeMount:', buttonRef.value)  // 可能还是 undefined
})

// 在 mounted 中
onMounted(() => {
  // ✅ 正确:此时组件已挂载,ref 已赋值
  console.log('mounted:', buttonRef.value)  // ButtonInstance 对象
  console.log('DOM 元素:', buttonRef.value?.ref)  // HTMLButtonElement
})
</script>

5. 为什么 ref 在 onMounted 中才有值?

Vue 的 ref 赋值时机:

  1. 模板编译阶段:Vue 识别 ref="buttonRef"
  2. 组件挂载阶段:创建组件实例,将实例赋值给 buttonRef.value
  3. DOM 渲染完成:onMounted() 执行时,ref 已经有值

6. 如果需要在 setup 中访问怎么办?

可以使用 watchEffectwatch

vue 复制代码
<script setup>
import { ref, watchEffect } from 'vue'
import type { ButtonInstance } from 'element-plus'

const buttonRef = ref<ButtonInstance>()

// 使用 watchEffect,会在 ref 有值后自动执行
watchEffect(() => {
  if (buttonRef.value) {
    console.log('ref 有值了:', buttonRef.value)
  }
})
</script>

7. 总结

  • Vue 3 没有 onCreated()setup() 本身就相当于 created
  • 访问 ref.value 必须在 onMounted() 中,因为此时组件已挂载
  • setup() 顶层访问 ref.value 会是 undefined
  • 如果需要响应式监听 ref 的变化,可以用 watchEffectwatch
相关推荐
张人玉25 分钟前
基于 Vue 3 + ECharts + Express + SQLite 构建的新能源汽车销量数据分析与可视化平台——新能源汽车销量数据分析系统
数据库·vue.js·sqlite·echarts
weixin_BYSJ19871 小时前
springboot3家政平台小程序--附源码00904
java·javascript·spring boot·python·django·flask·php
Wect3 小时前
TypeScript 完全指南
前端·javascript·typescript
清风programmer3 小时前
H5与小程序通信
前端·vue.js·uni-app
张元清3 小时前
React useDeepCompareEffect:修复 useEffect 的对象依赖问题(2026)
javascript·react.js
phltxy3 小时前
LangChain_v1_Agent快速开发和更新说明
前端·javascript·langchain
Cobyte3 小时前
通过 Vite 运行手写的模板编译器
前端·javascript·vue.js
用户298698530144 小时前
React 前端处理 Excel 工作表复制的技术实践
javascript·react.js·excel
光影少年4 小时前
RN原生交互 & 桥接
前端·javascript·react native·react.js·前端框架
张人玉4 小时前
基于 Vue 3 + ECharts + Express + SQLite 构—— LDA 模型的新闻舆论主题识别与情感分析系统
vue.js·echarts·express