一、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>

百分之一百零八
15 声望3 粉丝