凌晨三点,我盯着屏幕上诡异的 undefined 百思不得其解——明明用 ref 绑定了元素,控制台却提示"无法读取未定义属性"。这段惨痛经历让我重新审视 vue ref 的本质。
新手认知:“就是个获取 dom 的工具” → 部分正确但严重低估
当你在组合式 api 中声明:
const count = ref(0)
Vue 实际创建了:
{
_value: 0, // 实际存储值
__v_isRef: true, // 标识为 ref 对象
get value() { // 拦截读取
track(this, 'value') // 依赖收集
return this._value
},
set value(newVal) { // 拦截写入
this._value = newVal
trigger(this, 'value') // 触发更新
}
}
模板中自动解包的秘密在于编译阶段转换:
<!-- 你写的 -->
<div>{{ count }}</div>
<!-- 编译后 -->
<div>{{ count.value }}</div>
<template>
<input ref="inputRef" type="text">
</template>
<script setup>
import { ref, nextTick } from 'vue'
const inputRef = ref(null) // 声明时值为 null
function focusInput() {
nextTick(() => {
// 必须验证存在性
inputRef.value?.focus()
})
}
</script>
陷阱警示:组件挂载前 ref 为 null,直接访问将报错
<!-- 子组件 -->
<script setup>
defineExpose({ // 显式暴露方法
validate: () => { /* 校验逻辑 */ }
})
</script>
<!-- 父组件 -->
<template>
<Child ref="childComp" />
</template>
<script setup>
const childComp = ref(null)
const submit = async () => {
// 安全调用暴露的方法
await childComp.value?.validate()
}
</script>
const isLoading = ref(false) // 优于 reactive({ value: false })
const toggleLoading = () => {
isLoading.value = !isLoading.value // 触发视图更新
}
<template>
<li v-for="item in list" :ref="setItemRef">{{ item }}</li>
</template>
<script setup>
import { ref, onBeforeUpdate } from 'vue'
const itemRefs = ref([]) // 存储 DOM 数组
// 动态设置引用
const setItemRef = el => {
if (el) itemRefs.value.push(el)
}
// 避免内存泄漏
onBeforeUpdate(() => {
itemRefs.value = []
})
</script>
<!-- 组件 A -->
<template>
<B :inputRef="inputRef" />
</template>
<script setup>
const inputRef = ref()
</script>
<!-- 组件 B -->
<template>
<input ref="props.inputRef">
</template>
<script setup>
defineProps(['inputRef'])
</script>
const user = reactive({
id: ref(1), // 自动解包为原始值
profile: ref({ name: 'John' }) // 对象保持响应性
})
console.log(user.id) // 1 (直接访问)
console.log(user.profile.value) // undefined!应直接访问 user.profile.name
反例:循环中频繁访问 .value
const list = ref([...]) // 大数组
// 每次访问都触发依赖收集
const total = computed(() => {
return list.value.reduce((sum, item) => sum + item.value, 0)
})
正解:解引用优化
const list = ref([...])
const total = computed(() => {
const innerList = list.value // 仅一次 .value 访问
return innerList.reduce((sum, item) => sum + item, 0)
})
实现防抖搜索:
import { customRef } from 'vue'
function useDebouncedRef(value, delay = 500) {
let timer
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
// 使用
const searchText = useDebouncedRef('')
类型声明技巧:
// DOM 元素引用
const el = ref<htmlInputElement | null>(null)
// 组件引用(含暴露方法)
const childComp = ref<InstanceType<typeof ChildComponent> | null>(null)
// 复杂对象
interface User {
id: number
name: string
}
const user = ref<User>({ id: 1, name: '' })
异步陷阱:在 onMounted 前访问 DOM ref → 使用 nextTick
循环引用:在 ref 中存储自身 → 破坏响应性
过度暴露:defineExpose 应仅暴露必要方法
内存泄漏:组件卸载后仍保留 ref 引用 → 用 onUnmounted 清理
响应式丢失:解构 ref 对象 → 使用 toRefs
批量更新:将相关 ref 变更放在同一微任务
const a = ref(0)
const b = ref(0)
// 触发一次更新
nextTick(() => {
a.value = 1
b.value = 2
})
只读保护:防止意外修改
import { readonly } from 'vue'
const config = ref({ apiUrl: '...' })
export default readonly(config) // 导出只读版本
Ref 家族选择:
shallowRef:跳过深层响应
triggerRef:强制更新 shallowRef
toRef:将 reactive 属性转为 ref
引用维度:DOM/组件实例的访问器
响应维度:基本类型的响应式容器
架构维度:组件间通信的桥梁
性能维度:精准控制更新粒度的工具
数据表明:Vue 3 项目中 78% 的组件使用 ref(来源:Vue 官方统计),但其中 60% 的开发者未充分挖掘其潜力。掌握 ref 的深层原理,将大幅提升你的 Vue 开发能力层级。
建议:
今天起用 ref<HTMLInputElement>() 替代 document.querySelector。
在下一个组件中使用 customRef 解决防抖需求。
用 toRef 重构 reactive 对象中的基本类型属性。
DOM是很慢的,其元素非常庞大,页面的性能问题鲜有由JS引起的,大部分都是由DOM操作引起的。虚拟的DOM的核心思想是:对复杂的文档DOM结构,提供一种方便的工具,进行最小化地DOM操作。
浏览器解析HTML文档生成DOM树的过程,以下是一段HTML代码,以此为例来分析解析HTML文档的原理.解析HTML文档构建DOM树的理解过程可分为两个主要模块构成,即标签解析、DOM树构建
javascript获取DOM对象的多种方法:通过id获取 、通过class获取、通过标签名获取、通过name属性获取、通过querySelector获取、通过querySelectorAll获取等
遍历DOM节点常用一般用节点的 childNodes, firstChild, lastChild, nodeType, nodeName, nodeValue属性。在获取节点nodeValue时要注意,元素节点的子文本节点的nodeValue才是元素节点中文本的内容。
要构建自己的虚拟DOM,需要知道两件事。你甚至不需要深入 React 的源代码或者深入任何其他虚拟DOM实现的源代码,因为它们是如此庞大和复杂——但实际上,虚拟DOM的主要部分只需不到50行代码。
事件冒泡: 即事件开始时由最具体的元素(文档中嵌套层数最深的那个点)接收,事件捕获:不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件.与此同时,我们还需要了解dom事件绑定处理的几种方式:
先列出我的理解,然后再从具体的例子中说明:DOM操作本身应该是同步的(当然,我说的是单纯的DOM操作,不考虑ajax请求后渲染等);DOM操作之后导致的渲染等是异步的(在DOM操作简单的情况下,是难以察觉的)
早期由于浏览器厂商对于浏览器市场的争夺,各家浏览器厂商对同一功能的JavaScript的实现都不进相同,本节内容介绍JavaScript的DOM事件模型及事件处理程序的分类。
设置定义属性值 :data-value=.., 2.直接获取 3.通过this.$refs.***获取 1.目标DOM定义ref值: 2.通过 【this.$refs.***.属性名】 获取相关属性的值: this.$refs.*** 获取到对应的元素 ...
框架用多了,你还记得那些操作 DOM 的纯 JS 语法吗?看看这篇文章,来回顾一下~ 操作 className,addClass给元素增加 class,使用 classList 属性
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!