当前位置: 首页 > news >正文

前端-详解ref和$refs

目录

一. 什么是ref和$refs

二. 拿到的是什么?

三. 什么时候能用?

四. 是否响应式?

五. 典型用法(含踩坑点)

        1.聚焦输入框(DOM 场景)

        2.调子组件方法(实例场景)

        3.v-for 场景(得到数组)

                DOM 场景:多行列表的“可视化验证顺序”:

                子组件场景:批量调用实例方法:

                精准定位:用“唯一 ref 名称”避免顺序困扰

                动态收集为“字典”:规模更大时更好管

                和 v-if / v-show / 过渡 的差异

                更新时机与“只读快照”的思维

                一个“排序会坑你”的示例(生动版)

                Vue3 顺带一嘴(你主要用 Vue2,可做迁移预备)

                选型建议:

        4.和 v-if 共用的坑

        5.和 /排序的坑

六. 设计建议(什么时候该用/不该用)

七. 和 $el 的区别

八. Vue 2 vs Vue 3(顺便扫盲)

九. 小型“速查清单”


一. 什么是ref和$refs

        作用:利用ref和$refs可以用于获取dom元素,或组件实例

        ref 是你在模板里贴的“便签”,$refs 是组件实例上收集所有便签的“通讯录”。

  • ref(模板指令):写在标签或子组件上:<input ref="username"><Child ref="child" />

    •  目标组件-添加ref属性

      <BaseForm ref="baseForm"></BaseForm>
  • $refs(实例属性):在 JS 里通过 this.$refs.username / this.$refs.child 取到刚才标记的东西

    • 恰当时机,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法

      this.$refs.baseForm.组件方法()

   👉 注意:Vue 里没有 refs,只有 $refs


二. 拿到的是什么?

  • ref 标在原生元素:拿到 DOM 节点

  • ref 标在子组件:拿到 子组件实例(可以调用它公开的方法)

  • v-for 里多个同名 refthis.$refs.xxx 会变成 数组(顺序=当次渲染顺序)


三. 什么时候能用?

  • created 阶段拿不到(DOM/子组件还没挂上)

  • mounted 及之后 才可用

  • 每次更新后(例如数据变了)要等 DOM 刷新:this.$nextTick(() => { ... })


四. 是否响应式?

        $refs 不是响应式的
        不能拿 $refs.xxx 放到模板绑定里期待它自动更新;它只在渲染完成后被刷新一次快照。


五. 典型用法(含踩坑点)

        1.聚焦输入框(DOM 场景)

<template><input ref="username" placeholder="用户名">
</template><script>
export default {mounted() {// 挂载后再访问this.$refs.username.focus()}
}
</script>

        2.调子组件方法(实例场景)

<!-- Parent.vue -->
<template><ChildForm ref="form" /><button @click="submit">提交</button>
</template><script>
export default {methods: {submit() {// 直接调子组件公开方法(如 Element-UI 的 validate)this.$refs.form.validate(valid => {if (valid) { /* ... */ }})}}
}
</script>

        3.v-for 场景(得到数组)

                在 v-for 里给多个元素/子组件写同名 ref,渲染完成后 this.$refs.xxx 会是 数组

                这个数组的顺序严格等于“当次渲染后的 DOM 顺序”(受你的 v-for 数据顺序、key、过渡/重排影响)

                它不是响应式:每次更新刷新一遍快照,用的时候记得 this.$nextTick(...)

                DOM 场景:多行列表的“可视化验证顺序”:

<template><ul><liv-for="todo in todos":key="todo.id"ref="rows">{{ todo.text }}</li></ul><button @click="prepend">在最前面插一条,观察 $refs 顺序变化</button>
</template><script>
export default {data() {return {todos: [{ id: 1, text: 'A' },{ id: 2, text: 'B' },{ id: 3, text: 'C' },]}},mounted() {// 初次渲染完成后,$refs.rows 是 [<li>A</li>, <li>B</li>, <li>C</li>]this.$nextTick(() => this.dumpRefs('mounted'));},methods: {prepend() {// 在最前面插入一条this.todos.unshift({ id: Date.now(), text: 'X' });this.$nextTick(() => this.dumpRefs('after prepend'));},dumpRefs(tag) {const txts = this.$refs.rows.map(li => li.textContent.trim());console.log(tag, txts);// 比如: mounted -> ["A", "B", "C"]//       after prepend -> ["X", "A", "B", "C"]// 你会看到顺序完全等于当前 DOM 的顺序}}
}
</script>

                        要点:别用数组索引当“业务 ID”。一旦你在前面插入/排序,$refs.rows[i] 对应的 DOM 就换人了。

                子组件场景:批量调用实例方法:

                        假设有一个子组件 EditableRow,暴露了 validate() 方法(Element‑UI/自研都类似):

<!-- Parent.vue -->
<template><EditableRowv-for="row in rows":key="row.id":model="row"ref="editors"/><el-button type="primary" @click="validateAll">全部校验</el-button>
</template><script>
export default {data() {return { rows: [{id:1},{id:2},{id:3}] }},methods: {validateAll() {// this.$refs.editors 是 [EditableRow实例, EditableRow实例, ...]this.$nextTick(() => {const list = this.$refs.editors || [];Promise.all(list.map(c => c.validate())).then(() => this.$message.success('全部通过')).catch(() => this.$message.error('有不通过的项'));})}}
}
</script>

                精准定位:用“唯一 ref 名称”避免顺序困扰

                        当你必须通过 $refs 找到“某一条”时,不要依赖数组索引,给每一项做独一无二的 ref 名:

<liv-for="todo in todos":key="todo.id":ref="'row-' + todo.id"
>{{ todo.text }}
</li>

                        使用:

this.$nextTick(() => {const el = this.$refs['row-' + targetId]; // 单个 DOM,而不是数组el && el.scrollIntoView({ block: 'center' });
});

                        优点:无论插入/删除/重排多少次,都能稳准狠找到对应元素

                动态收集为“字典”:规模更大时更好管

                        如果你想同时拿到字典形式id -> 元素/实例),可用函数式 ref(Vue2 里也能这么写):

<liv-for="todo in todos":key="todo.id":ref="el => setRef(el, todo.id)"
>{{ todo.text }}
</li>
export default {data() {return { itemRefs: {} } // { [id]: HTMLElement }},methods: {setRef(el, id) {// el 为 null 时表示该 DOM 被卸载,清理一下if (el) this.$set(this.itemRefs, id, el);else this.$delete(this.itemRefs, id);},focusById(id) {this.$nextTick(() => this.itemRefs[id]?.focus?.());}}
}

                        优点:拿到的是稳定的“映射表”,跟着你的业务主键走,不被渲染顺序牵着鼻子走。

                和 v-if / v-show / 过渡 的差异

                        v-if="false":该项不渲染 → 不会出现在 $refs.xxx 数组里(或被移除)

                        v-show="false":DOM 仍在,只是 display:none → 仍然在 $refs.xxx 数组中

                        <transition-group> 或排序动画:最终数组顺序 = 动画结束后的 DOM 顺序;渲染期间数组可能临时处于中间态,读值务必放 nextTick 或动画钩子后。

                更新时机与“只读快照”的思维

                        在 created 钩子里还没有 DOM → $refs 为空

                        在 mounted/updated 之后访问,或改完数据后 this.$nextTick(...)

                        $refs 不是响应式:不要用它来驱动模板;只作为命令式把手(focus、scroll、measure、调用方法)

                一个“排序会坑你”的示例(生动版)

<template><ul><li v-for="user in usersSorted" :key="user.id" ref="items">{{ user.score }} - {{ user.name }}</li></ul><button @click="sortDesc">按分数降序</button><button @click="addOne">给张三 +10 分</button>
</template><script>
export default {data() {return {users: [{ id: 1, name: '张三', score: 80 },{ id: 2, name: '李四', score: 90 },{ id: 3, name: '王五', score: 85 }],desc: false}},computed: {usersSorted() {const arr = this.users.slice().sort((a, b) =>this.desc ? b.score - a.score : a.score - b.score);return arr;}},methods: {sortDesc() {this.desc = true;this.$nextTick(() => console.log(this.$refs.items.map(li => li.textContent.trim())));// 输出顺序会立刻变为降序},addOne() {const zhang = this.users.find(u => u.id === 1);zhang.score += 10;this.$nextTick(() => {// 再次输出,你会看到张三可能“跳了位置”console.log(this.$refs.items.map(li => li.textContent.trim()))});}}
}
</script>

                        结论$refs.items[i] 代表“第 i 个 DOM”,不是“ID=某人”的 DOM。别把索引当 ID

                Vue3 顺带一嘴(你主要用 Vue2,可做迁移预备)

                        Vue3(Composition API)里如果写 <div ref="els"> 且脚本 const els = ref(null)不会自动变成数组,只会拿到最后一个

                        推荐用函数式 ref + 手动数组/Map收集,并在 onBeforeUpdate 里清空一次,避免旧引用残留:

<script setup>
import { ref, onBeforeUpdate } from 'vue'
const itemEls = ref([])      // 或者用 Map:ref(new Map())
onBeforeUpdate(() => { itemEls.value = [] })
const setItemEl = el => { if (el) itemEls.value.push(el) }
</script><template><li v-for="todo in todos" :key="todo.id" :ref="setItemEl">{{ todo.text }}</li>
</template>

                选型建议:

                        需要“对每个项做一次命令式操作”(滚动、测量、调用子组件方法):用同名 ref(数组)+ nextTick

                        需要“按业务主键精确定位某项”:别用数组;用唯一 ref 名函数式 ref 收集成字典


        4.和 v-if 共用的坑

                v-if为false时,该ref不存在(undefined或从$refs中移除)     

                解决:访问前先判断空,或把逻辑放nextTick里:                                             

this.$nextTick(() => {const dlg = this.$refs.dialogif (dlg) dlg.open()
})

        5.和 <transition-group>/排序的坑

                列表有过渡或重排,$refs.xxx 的数组顺序跟随“当次渲染顺序”不要把它当成稳定索引映射;真正的业务映射请用你的数据源(id/map),$refs 仅作“操作 DOM/实例”的临时入口。


六. 设计建议(什么时候该用/不该用)

  • 该用 ref/$refs 的场景

    • 操作真实 DOM:聚焦、滚动、测量尺寸、操控 Canvas、第三方库挂载点

    • 调用子组件公开方法:如 validate() / resetFields() / open()

  • 不该用的场景

    • 数据流转:父子通信请用 props + emit(或 .sync),不要通过 $refs 去改子组件内部数据

    • 状态展示$refs 不是响应式,不要拿它去“驱动视图”

心法:能声明式就声明式(props/emit/v-model),只有当“必须命令式”时再上 $refs


七. 和 $el 的区别

  • this.$el:当前组件根 DOM

  • this.$refs.xxx子元素/子组件的句柄(可能是很多个)


八. Vue 2 vs Vue 3(顺便扫盲)

  • Vue 2(你当前项目场景):用字符串 ref,在 JS 里 this.$refs.xxx

  • Vue 3 Composition API

    • 模板里 <div ref="el"> 搭配脚本里 const el = ref(null)(同名变量自动注入)

    • 仍有 $refs(在 Options API 中),但更推荐上面这种“变量式模板 ref”

  • 注意区分 ref()(响应式引用)模板 ref 属性:同名不同物。


九. 小型“速查清单”

  • ref 标元素 → DOM;标组件 → 组件实例

  • v-for 同名 ref → 数组

  • $refs 只在 mounted/updated 后可用,更新读取请配合 this.$nextTick

  • $refs 非响应式,只做“临时把手”,别驱动 UI

  • v-if 搭配注意判空;与排序/过渡搭配不要依赖顺序稳定


文章转载自:

http://CEu3cTiY.rcbdn.cn
http://I1MYsWYm.rcbdn.cn
http://Y2Xvlg5p.rcbdn.cn
http://g4ZQ2pUq.rcbdn.cn
http://ruEc4hrm.rcbdn.cn
http://6J99ox5t.rcbdn.cn
http://76id9X19.rcbdn.cn
http://uEvOBeR3.rcbdn.cn
http://OKSik32j.rcbdn.cn
http://fhsedDjc.rcbdn.cn
http://vnHsun61.rcbdn.cn
http://fYr4fhNK.rcbdn.cn
http://czEybHu1.rcbdn.cn
http://MZIF6ncO.rcbdn.cn
http://GSj2WmyY.rcbdn.cn
http://EqtzF9qz.rcbdn.cn
http://PTKz820k.rcbdn.cn
http://3C0DQw3H.rcbdn.cn
http://ssY5vOGM.rcbdn.cn
http://05l88DUc.rcbdn.cn
http://WmYlaErp.rcbdn.cn
http://P8pRsZ0o.rcbdn.cn
http://v785M3Hw.rcbdn.cn
http://dM9YnGj6.rcbdn.cn
http://ypVkkozV.rcbdn.cn
http://BoPx3b3p.rcbdn.cn
http://Q8eM1UQz.rcbdn.cn
http://5Cd5t1KX.rcbdn.cn
http://qweYClTq.rcbdn.cn
http://c4hnJRgZ.rcbdn.cn
http://www.dtcms.com/a/384728.html

相关文章:

  • C++---变量的多维分类
  • Vue 3 前端工程化规范
  • NLP Subword 之 WordPiece 算法原理
  • 【SQL】MySQL中空值处理COALESCE函数
  • Kafka实时数据管道:ETL在流式处理中的应用
  • VBA数据结构深度解析:字典对象与集合对象的性能终极对决
  • 查看当前虚拟环境中安装的 PyTorch 版本
  • 布尔运算-区间dp
  • WWW‘25一通读 |图Anomaly/OOD检测相关文章(1)
  • 视频分类 pytorchvideo
  • RabbitMQ 基础概念与原理
  • 专题:2025中国消费市场趋势与数字化转型研究报告|附360+份报告PDF、数据仪表盘汇总下载
  • 预制菜行业新风向:企业运营与商家协同发展的实践启示
  • 晶台光耦 KL6N137 :以精密光电技术驱动智能开关性能提升
  • 贪心算法应用:最短作业优先(SJF)调度问题详解
  • javaee初阶 文件IO
  • 如何调整滚珠丝杆的反向间隙?
  • Python项目中的包添加后为什么要进行可编辑安装?
  • daily notes[45]
  • 基于51单片机的蓝牙体温计app设计
  • Git版本控制完全指南
  • 【CSS】一个自适应大小的父元素,如何让子元素的宽高比一直是2:1
  • 前端通过地址生成自定义二维码实战(带源码)
  • Android Doze低电耗休眠模式 与 WorkManager
  • 用 Go 重写 adbkit:原理、架构与实现实践
  • 通过Magisk service.d 脚本实现手机开机自动开启无线 ADB
  • NineData社区版 V4.5.0 正式发布!运维中心新增细粒度任务权限管理,新增MySQL至Greenplum全链路复制对比
  • centos配置环境变量jdk
  • 基于“能量逆流泵“架构的220V AC至20V DC 300W高效电源设计
  • 归一化实现原理