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
相关推荐
之歆5 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
Maimai108086 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
candyTong6 小时前
Claude Code 的 Edit 工具是怎么工作的
javascript·后端·架构
卡卡军9 小时前
agmd 1.0 重磅升级——Rust 重写,性能起飞
javascript·rust
Larcher9 小时前
🔥 告别抓瞎:用 Claude Code (cc) 优雅接手与维护已有项目
javascript·机器学习·前端框架
JYeontu9 小时前
轮播图不够惊艳?试下这个立体卡片轮播图
前端·javascript·css
亲亲小宝宝鸭9 小时前
如何监听DOM尺寸的变化?element-resize-detector 和 resizeObserver
前端·javascript
代码煮茶9 小时前
Vite 5.0 新特性深度解析:更快、更干净、更未来的前端构建利器
vue.js
卷帘依旧12 小时前
Generator 全面解析 + async/await 深度对比
前端·javascript
weixin_4713830312 小时前
统一缩放单位基础(px、em、rem)
开发语言·javascript·ecmascript