一、ref的作用
js获取DOM是通过getElementById函数如下:
<template>
<li id="itemR"> getElementById获得"li" </li>
</template>
<script setup>
import {nextTick} from 'vue'
let el
nextTick(()=>{el=document.getElementById('itemR');console.log(el)})
</script>
vue使用ref获取DOM,如下:
<template>
<li ref="itemR"> 不用getElementById获得"li" </li>
</template>
<script setup>
import {ref,nextTick} from 'vue'
let itemR
nextTick(()=>{console.log(itemR)})
</script>
可见ref获取DOM不需要借助任何函数。
版本问题:如上的代码,在低版本的vite里,要定义响应性变量才正常打印,为兼容起见,还是要把标识符定义为响应变量!!!
二、ref规范
1、书写规范
- a、标签使用ref属性给DOM加上标识符
<li ref="标识符">
- b、在
<script setup>
里定义标识符。const itemR
- c、当DOM完成渲染就可以在
<script setup>
和<template>
里使用标识符引用此DOM
注:选项式API不需定义标识符,通过this.$refs.标识符
引用DOM
2、内部原理
使用ref标识不需要手动获取DOM,只需定义好存放DOM的变量即可。
当DOM渲染完成,系统会自动寻找与标识符同名的变量,将整个DOM对象赋值给这个变量。
特别注意
- 在DOM初始渲染后,系统会寻找标识同名变量并赋值DOM对象。在DOM初始渲染前变量值不是DOM对象,此时引用变量还不是DOM对象,因此建议将引用DOM变量放在函数体内,如nextTick()。
- 在DOM每次更改后,系统会再次对变量赋值DOM对象。也就是同名的变量即使被赋其它值,在DOM更新后变量又会自动赋回来。
- 对变量赋值DOM对象时,变量之前的值会被DOM对象覆盖。因此要注意其它变量不要和ref标识同名,否则值会被DOM对象替换。
- key标识改变,原标识的变量会清空DOM对象赋值为null,并重找现标识同名变量并赋值DOM对象。
系统寻找标识符同名变量需要时间,不要在改变key标识的函数体内立即引用DOM,此时系统还没完成清空原变量、寻找现标识同名变量、赋值变量的过程。如下:
<template> <li :ref="item.hhh"> 不用getElementById获得"li" </li> <button @click="ff">改变标识的函数体内引用DOM</button> </template> <script setup> import {ref,nextTick,reactive} from 'vue' var item=reactive({hhh:"itemR"}) var itemR=reactive({hhh:"itemR还没赋DOM对象"}) var it=reactive({hhh:"it还没赋DOM对象"}) function ff(){item.hhh='it',console.log(itemR,it)} </script>
如要在改变key标识后立即引用DOM,可加个延时器或在改变key标识的函数外引用。如下
<template> <li :ref="item.hhh"> 不用getElementById获得"li" </li> <button @click="ff">改变标识的函数体内引用DOM</button> <button @click="fe">改变标识的函数体外引用DOM</button> </template> <script setup> import {ref,nextTick,reactive} from 'vue' var item=reactive({hhh:"itemR"}) var itemR=reactive({hhh:"itemR还没赋DOM对象"}) var it=reactive({hhh:"it还没赋DOM对象"}) function ff(){item.hhh='it',setTimeout(() => {console.log(itemR,it)})} function fe(){console.log(itemR,it)} </script>
- 变量与标识的DOM对象始终绑定,用他值覆盖变量的DOM对象值只能是临时的,在DOM更新时会再次赋值绑定。
- ref出现在标签中。但他并不是DOM属性,不会出现在渲染出来的真实DOM中,无法引用ref值,但可通过绑定指令更改ref的值。
3、变量类型的选择
在书写规范中,只需定义与标识符同名变量就行,系统会把DOM对象赋给同名变量,如变量有值也会被DOM对象覆盖。无论什么类型的变量最终指针都是指向DOM对象,变量和DOM对象双向响应。但变量与变量的引用响应性是由变量类型决定,变量是响应性的,引用时会随变量改变而更新。
必须定义为ref响应性变量
由上可知响应性变量能使引用随变量因获得DOM对象而更新,非响应性变量的引用不会随变量获得DOM对象而更新。
为什么是ref而不是reactive
定义reactive类型变量,赋DOM值时直接赋在变量上,变量失去Proxy代理,沦为普通变量,引用变量不会随变量获得DOM对象而更新,还保持原值。
而ref类型变量,赋DOM值时是赋在value属性上,value属性赋DOM对象也会使value属性值失去Proxy代理,但引用会随value属性获得DOM对象而更新一次
所以如果是操作DOM对象定义变量无所谓,如果是引用变量必须是定义为ref响应性变量。
4、多标签同名ref
- 当有多个标签的ref标识相同时,只有最后的DOM对象被赋与同名变量。
在使用v-for生成的列表中,ref标识相同的DOM对象会被赋与同名数组变量。
v-for同名与非v-for同名如下:<template> <button @click="oi">我来测试</button> <li v-for="tt in wo" ref="item">{{tt}}</li> <li ref="it">我是非v-for一</li> <li ref="it">我是非v-for二</li> </template> <script setup> import {ref} from 'vue' const wo = ref(['我是v-for一','我是v-for二']) let item let it function oi(){console.log(item,it)} </script>
三、DOM对象
1、DOM对象的简显形式
使用console.log()在后台打印DOM对象,显示的不是对象的完整形式,只是简显形式。
DOM对象的简写形式是一个DOM树,DOM对象的其它属性被隐藏了起来。
如何查看DOM对象的完整形式?
可以将DOM对象赋给一个普通对象的属性,打印普通对象时,展开就可以看到完整形式的DOM对象。如下:
<template>
<li id="itemR"> getElementById获得"li" </li>
</template>
<script setup>
import {nextTick} from 'vue'
let el={}
nextTick(()=>{el.oo=document.getElementById('itemR');console.log("完整形式:",el,"简显形式:",el.oo)})
</script>
简显形式DOM对象被隐藏了的属性照样可以引用,不受显示的影响。
2、在<script setup>
操作DOM对象
操作DOM对象需在DOM渲染完成后,所以操作DOM代码需放在函数内,如nextTick()。
关于DOM对象的属性和函数,详见JavaScript HTML DOM。
outerHTML属性不可改写
此属性是DOM对象本身的html代码,一但改写ref标识会消失,变量会清空DOM对象赋值为null。
自定义属性的特殊处理
自定义属性不能直接操作,需要调用函数才操作。赋值用
setAttribute("属性名",'属性值')
。读取用getAttribute("属性名")
,注意属性名和值要带引号括起。<template> <button @click="oi">测试getAttribute函数引用自定义属性</button> <li ref="it" yy="我是自定义属性">我有自定义属性</li> </template> <script setup> let it function oi(){it.setAttribute("yy",'更改自定义属性值');console.log(it.getAttribute("yy"))} </script>
3、在模版上引用DOM对象
a、响应性DOM变量
变量在DOM初始渲染没完成时值还没有DOM对象,在<script setup>
可以通过将DOM变量放在函数体内来操作DOM对象,如nextTick(),等待渲染完成后就可通过变量操作DOM对象。
在模版上引用DOM对象,也需要等待DOM初始渲染完成,这就需要把DOM变量定义为ref响应性变量。这样才能在变量获得渲染后的DOM对象后更新DOM引用。
b、只能引用DOM变量的属性或函数
在模板部用文本插值显示DOM变量,显示的是:"[object HTMLButtonElement]
",这就是DOM对象在模版上的简显形式。
在模版上无法显示DOM对象的完整形式(DOM变量的属性和函数有几百条)。
因此,在模版上是无法引用整个DOM对象。
只能引用DOM对象的属性,如下:
<template>
<li ref="itemR"> getElementById获得"li" </li>
<div v-if="iu">
我是DOM对象:{{itemR}} <br>
我是DOM对象的outerHTML属性: {{itemR.outerHTML}}
</div>
<button @click="pop">li-DOM渲染后再渲染div</button>
</template>
<script setup>
import {nextTick,ref} from 'vue'
let itemR=ref()
let iu=ref(false)
function pop(){iu.value=!iu.value}
</script>
c、未渲染警告
如上,如果不在div标签使用v-if使渲染压后,引用DOM对象属性会跳出警告(无法读取未定义的属性“outerHTML”)。
避免未渲染引用报警的方法
(1)定义时赋值父层法
如果要引用DOM对象的属性或对象的函数,需要在未渲染时给变量一个无意义的值。可以通过在定义变量时,给变量赋值引用属性的父层的方法来消除报警,如是引用对象的函数(如const wowo=ref({getAttribute(){}})
),需定义一个同名的空函数。如下:
<template>
<button ref="wowo" >我是DOM对象</button><br>
{{wowo.outerHTML}}
</template>
<script setup>
import {ref} from 'vue'
const wowo=ref({})
</script>
(2)条件赋值法
- 调用普通函数 在函数内加入判断DOM对象渲染状态代码,渲染完成则用return返回属性。
- computed计算函数 在函数内加入判断DOM对象渲染状态代码,渲染完成则用return返回属性。
- watch侦听函数 侦听DOM对象渲染状态,渲染完成则赋值给
- nextTick函数
其它渲染后的生命函数
如下:<template> <button ref="wowo" >我是DOM对象</button><br> 普通函数法:{{www()}}<br> computed函数法:{{wq}} <br> watch侦听法:{{wow}}<br> nextTick函数法:{{wo}} <br> 生命函数法:{{w}} </template> <script setup> import {ref, computed,onMounted,watch,nextTick} from 'vue' const wowo=ref() const wow=ref() const wo=ref() const w=ref() function www(){if(wowo.value){return wowo.value.outerHTML}} const wq=computed(() => {if(wowo.value){return wowo.value.outerHTML}}) watch(wowo,(ne,ol) =>{wow.value=ne.outerHTML}) nextTick(()=>{wo.value=wowo.value.outerHTML}) onMounted(()=>{w.value=wowo.value.outerHTML}) </script>
d、ref变量与DOM、引用DOM的关系
ref变量与DOM的关系: 响应关系,ref变量的属性更新,DOM的属性也会响应更新
ref变量与引用DOM的关系: 非响应关系,ref变量的属性更新,引用DOM只有刷新(连带更新)才会更新属性值。
如下:
<template>
<input ref="wowo" type="text" value="我会变吗" > <br>
<button @click="uu">改变ref变量的value值</button><br>
<button @click="uuu">{{wow}}连带更新</button><br>
{{wowo.value}}
</template>
<script setup>
import {ref} from 'vue'
const wowo=ref({})
const wow=ref(0)
function uuu(){wow.value++}
function uu(){wowo.value.value='我变了'}
</script>
e、引用outerHTML属性生成DOM
<template>
<button ref="wowo" >我是DOM对象</button><br>
<component :is="{template:wowo.outerHTML}"></component>
<div :outerHTML="wowo.outerHTML"></div>
<div v-html="wowo.outerHTML"></div>
</template>
<script setup>
import {ref} from 'vue'
const wowo=ref({})
</script>
如上,采用了三种引用方式
(1) v-html方式
通过在本标签的v-html指令赋引用DOM的outerHTML值的方式,在标签下生成新的DOM:<div v-html="wowo.outerHTML"></div>
此方法是在div下面生成新DOM
(2) outerHTML方式
通过给本标签的outerHTML属性赋引用DOM的outerHTML值的方式,改变自身为新的DOM
(3) 动态组件方式
通过动态组件的方式生成新的DOM:<component :is="{template:wowo.outerHTML}"></component>
如果使用 template:属性无效,这是因为构建不支持模板选项的原因,需开启支持,方法是:在根目录vite.config.js文件加入如下语句(Vue3+vite架构)
import { defineConfig } from 'vite'
import path from "path";
export default defineConfig({
resolve: {
alias: {
'vue': path.resolve(__dirname, "node_modules/vue/dist/vue.esm-bundler.js")
},}
})
注意vue.esm-bundler.js文件路径的准确性,否则不生效。
4、在父组件上操作和引用子DOM对象
a、直接操作和引用
书写规范
父子组件都要使用ref标识,并都要定义标识变量。
(1)在<script setup>
操作子DOM对象
与操作普通DOM对象一样,要放在函数体内等DOM渲染完成才可以操作。
操作语句: 父标识变量.$refs.子标识变量
(2)在父模版上引用子DOM对象
与引用普通DOM对象一样,要避免未渲染引用报警。
采用定义空对象法消除报警要注意空对象一定要到子属性的父环节!如:const bb=ref({$refs:{wowo:{}}})
操作和引用子DOM对象如下:
父组件
<template>
<HelloWor ref="bb"/>
{{ bb.$refs.wowo.outerHTML}}
<button @click="ooo">操作子组件的ID属性</button>
</template>
<script setup>
import HelloWor from './Hello.vue'
import {ref,onMounted,watch} from 'vue'
const bb=ref({$refs:{wowo:{}}})
function ooo(){bb.value.$refs.wowo.id="uiu"}
</script>
子组件
<template>
<button ref="wowo" >我是DOM对象</button><br>
</template>
<script setup>
import {ref} from 'vue'
const wowo=ref({})
</script>
在组件式开发环境只可真接操作和引用DOM对象,选项式开发还可以直接操作和引用数据及函数,语句是:this.$refs.父标识符变量.$refs.子标识符变量
数据:this.$refs.父标识符变量.$refs.子数据变量
函数:this.$refs.父标识符变量.$refs.子函数
b、通过defineExpose操作和引用子参
组件式开发环境通过defineExpose不仅可以操作和引用DOM,还可以操作引用其它参数。
书写规范
父组件的父标签要使用ref标识,通过父标签标识变量来操作和引用子参数。
(1)子defineExpose暴露
在子组件中使用defineExpose向父暴露DOM对象、数据、函数。
书写方式:defineExpose({ data,DOM, func})
(2)父操作和引用
- 操作子参数
父组件可操作子参数,与操作普通DOM对象一样,要放在函数体内等DOM渲染完成才可以操作。
操作子DOM:父标识变量.子标识变量
操作子变量:父标识变量.子变量
操作子函数:父标识变量.子函数
。 引用子参数
避免未渲染引用报警
可采用定义对象法消除报警。注意定义对象时一定要引用体的上一级环节。
如引用子DOM对象的属性,就要定义到子对象标识名,调用子函数,就要定义到子函数,引用数据的子属性就要定义到子属性的上一级属性。
如:const wowo=ref({wo:{},oi(){},data:{}})
引用响应性
引用的响应性由子参数的响应性决定。
子参是响应性DOM对象,引用会在子首次获得DOM时更新。子参不是响应性DOM对象,引用不会在子首次获得DOM时更新。
子参是响应性变量,引用会响应子变量更新。
操作和引用子DOM对象如下:
父组件<template> <HelloWor ref="wowo" /><br> {{wowo.oi()}} <br>{{wowo.data.ooo}}<br>{{wowo.wo.outerHTML}}<br> <button @click="ooo">操作子组件的ID属性</button> </template> <script setup> import HelloWor from './Hello.vue' import {ref,onMounted,watch} from 'vue' const wowo=ref({wo:{},oi(){},data:{}}) onMounted(()=>{wowo.value.oi(); console.log(wowo.value.data.ooo)}) function ooo(){wowo.value.wo.id="uiu"} </script>
子组件
<template> <button ref="wo">子DOM</button> </template> <script setup> import {ref} from 'vue' const data=ref({ooo:'子数据'}) const wo=ref() function oi(){console.log('子方法');return '子方法'} defineExpose({ data, wo, oi }) </script>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。