Vue中的ref和refs

3,144 阅读3分钟

前言

写在前面的话,作者是一只技术菜鸟,文章参考了网上的文章,也有作者的思考。如果热爱技术的你刚巧路过作者的文章,希望对文中表述不准确或错误的地方给予指正,作者不吝赐教,谢谢。

来自官网的引述:

特别提醒:$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs。

ref

ref:被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的$refs对象上。如果在普通的DOM元素上使用,那么指向的就是普通的DOM元素。

ref 有三种用法:

  • ref 加在普通的元素上,用this.$refs.name获取到的是dom元素。
  • ref 加在子组件上,用this.ref.name 获取到的是组件实例,可以使用组件的所有方法。
  • 如何利用 v-forref 获取一组数组或者dom 节点。

1.普通的DOM元素上使用

<div id="app">
  <input type="text"ref="TEXT"/ >
  <button @click="add">添加</button>
</div>
var app=new Vue({
  el:"#app",
  data:{

  },
  methods:{
    add:function(){
      console.log(this.$refs);
    }
  }
})

2.子组件上使用

<div id="app">
  <a ref=inputText></a>
  <input type="text"ref="TEXT" >
  <button @click="add">添加</button>
</div>
Vue.component('a',{
   template:"<div>我是一个组件</div>"
 })
 var app=new Vue({
   el:"#app",
   data:{

   },
   methods:{
     add:function(){
       console.log(this.$refs.inputText);
       console.log(this.$refs);
     }
   }
 })
 var a=app.$refs.inputText;

3.v-for 和 ref 一起使用

前情提要:使用v-for循环组件,同时在组件上使用ref属性

<template>
  <div class="hello">
      {{ msg }}
      <ul>
          <list-item 
              v-for="item in items" 
              :key="item.id" 
              :value="item.text" 
              :ref="`item${item.id}`"
          />
      </ul>
  </div>
</template>
    
<script>
    import ListItem from "./ListItem";
    export default {
        name: "HelloWorld",
        components: {
            ListItem
        },
        data() {
            return {
                msg: "Welcome to Your Vue.js App",
                items: [
                    { id: 1, text: "foo" },
                    { id: 2, text: "bar" },
                    { id: 3, text: "baz" },
                    { id: 4, text: "foobar" }
                ]
            };
        },
        mounted() {
            var _this = this
                setTimeout(function () {
                  console.error(_this.$refs)
                  console.error(_this.$refs['[object Object],[object Object],[object Object],[object Object]'])
                  _this.$refs['[object Object],[object Object],[object Object],[object Object]'][2].highlight()
                  }, 1500)
        }
    };
</script>

And ListItem component:

<template>
    <li v-bind:class="{ highlight: isHighlighted }">
        {{value}}
    </li>
</template>

<script>
    export default {
        name: "list-item",
        props: ["value"],
        data() {
            return {
                isHighlighted: false
            };
        },
        methods: {
            highlight() {
                this.isHighlighted = !this.isHighlighted;
            }
        }
    };
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .highlight {
        color: red;
    }
</style>

运行报错: Uncaught TypeError: _this.$refs.item2.highlight is not a function

setTimeOut的回调函数中打印this.$refs,得到一个对象,如下图。对象中有一个key值为

[object Object],[object Object],[object Object],[object Object]']

value为长度为4的数组的键值对,取相应的index能得到对应的子元素。但是显然这不是一个很好的方案。

优化:

v-for中使用静态ref,组件或元素节点被存储在一个数组中, 不用再使用序号

<list-item
  v-for="item in items" 
  :key="item.id" 
  :value="item.text" 
  ref="items"
/>

And use the refs in your component like this:this.$refs.items[index]

总结:在v-for循环的组件或元素上使用静态ref属性,会将组件集合或元素集合储存在一个数组中,即:当v-for用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。

注意:只要想要在Vue中直接操作DOM元素,就必须用ref属性进行注册。

refs

refs:一个对象,包含所有注册过ref特性的所有DOM元素和组件实例

refs只会在组件渲染完成之后生效(只能在mounted及之后的生命周期中使用),并且它们不是响应式的(使用了v-ifv-showv-for动态操作DOM,在mounted阶段是访问不到refs的)。这只意味着一个直接的子组件封装的“逃生舱”——你应该避免在模板或计算属性中访问refs。也就是说只有等页面加载完成好之后你才能调用this.$refs,如果使用v-ifv-for渲染页面的话,那么在刚开始页面没没渲染之前你是拿不到this.$refs的,所以要等到页面渲染之后拿才可以。

Vue的生命周期:

  • creating状态 ---- vue 实例被创建的过程
  • mounting状态 ---- 挂到到真实的 DOM 节点,渲染出html页面
  • updating状态 ---- 如果 data 中的数据改变就会触发对应组件的重新渲染
  • destroying状态 ---- 实例销毁

解决办法:

1、如果在mounted里获取this.$refs,因为dom还未完全加载,所以是拿不到的,可以使用 this.$nextTick(() => {}) 等页面渲染好再调用。 update阶段则是完成了数据更新到 DOM 的阶段(对加载回来的数据进行处理),此时,就可以使用this.$refs

2、使用setTimeOut()

setTimeout(() => {
  console.log(this.$refs.***)
}, 0)

this.$refs.xxx为undefined的几种情况

1、在created里钩子函数中调用

原因:created()在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,el属性目前不可见。所以this.el 属性目前不可见。所以this.refs压根就调不到那个dom,因为页面还没有挂载上去。

解决:在mounted () 钩子函数中调用

注意:在此种情况中,元素节点一定是直接写在html中的,而不是通过数据或者条件渲染的

2、数据或条件渲染(v-if,v-show)之后的调用

原因:

ref 本身作为渲染结果被创建,在初始渲染的时候不能访问他们,是不存在的 refs不是响应式的,只在组件渲染完成后才填充用于元素或子组件注册引用信息,注册完成,将会注册在父组件refs不是响应式的,只在组件渲染完成后才填充 用于元素或子组件注册引用信息,注册完成,将会注册在父组件refs对象上 调用对象是否和v-if结合使用 ref不是响应式的,所有的动态加载的模板更新它都无法相应的变化。

解决:可以通过setTimeOut(()=>{...}, 0)来实现

总结:

通过this.$refs访问子组件或子元素的注意事项

  1. 在vue中操作DOM,必须使用ref属性注册一个引用id,再通过this.$refs.引用id操作DOM。

  2. this.$refs是一个对象,组件中所有已注册的组件或元素都可以通过this.$refs.注册id的方式访问。

  3. v-for中注册的组件或元素,通过this.$refs.注册id得到的是一个数组,可通过索引获得某个具体的组件或元素。

  4. this.$refs 只会在组件渲染完成之后生效,组件在mounted中开始渲染,完成时间不确定,所以只能在mounted及之后的生命周期中访问 this.$refs,也可以使用setTimeout(fun, 0)的方式来访问,因为这是新一轮宏任务的开始。

  5. mounted中使用this.$refs,由于DOM挂载时间不缺定,必须在this.$nextTick的回调函数中访问子组件或子元素。

  6. 避免在计算属性中使用this. $refs,由于vue是响应式的,在数据更新之后不会立马更新DOM,计算属性在依赖改变后立即执行,这时获取到的可能是旧的DOM。

  7. 使用id一般是为了获取元素操作DOM,Vue是数据驱动不操作DOM,在Vue项目一般是不使用id的。