简体   繁体   English

VueJS $watch $refs

[英]VueJS $watch $refs

Is it possible to $watch Vue $refs ?是否可以$watch Vue $refs

I'm wanting to set logic against a child component that is nested inside my current Vue instance but inside the ready callback, $refs.childcomponent is initially undefined while it's processed.我想针对嵌套在当前 Vue 实例中但在ready回调中的子组件设置逻辑, $refs.childcomponent在处理时最初是undefined的。

inside ready()里面ready()

this.$watch('$refs', function() {
    console.log("not firing");
}, { deep: true });

Result: Error: Maximum call stack exceeded结果:错误:超出最大调用堆栈

watch property of the instance实例的watch属性

watch: {
  '$refs': {
     handler: function() { console.log("hit"); },
     deep: true
  }
}

result: nothing.结果:没有。

You can $watch $refs.<name>.<data> but not $refs.<name> itself, not to mention $refs .您可以$watch $refs.<name>.<data>但不能$refs.<name>本身,更不用说$refs

https://jsfiddle.net/kenberkeley/9pn1uqam/ https://jsfiddle.net/kenberkeley/9pn1uqam/

const Counter = {
  data: () => ({
    i: 0
  }),
  template: `<fieldset>
    <p>Counter</p>
    <code>i = {{ i }}</code>
    <button @click="i += 1"> Add One </button>
  </fieldset>`
}

const App = {
  components: { Counter },
  mounted () {
    this.$watch(
        () => {
            return this.$refs.counter.i
        },
      (val) => {
        alert('App $watch $refs.counter.i: ' + val)
      }
    )
  },
  template: `<fieldset>
    <p>App</p>
    <counter ref="counter" />
  </fieldset>`
}

new Vue({
    el: '#app',
    render: h => h(App)
})

不,$refs 没有反应性,watch 不起作用。

In mounted use code below:在下面的安装使用代码中:

this.$watch(
        () => {
            return this.$refs.<name>.<data>
        },
      (val) => {
        alert('$watch $refs.<name>.<data>: ' + val)
      }
    )

There is a work around to this.有一个解决方法。 Consider that JavaScript doesn't create a copy of an array when assigning it to a variable, it merely creates a reference to the original array.考虑到 JavaScript 在将数组分配给变量时不会创建数组的副本,它只是创建对原始数组的引用。 Knowing that Vue's $refs are arrays, we can do the following:知道 Vue 的 $refs 是数组,我们可以这样做:

<template>
    <div>
            <ul v-if="showAvailable">
                 <li v-for="pet in allPets.available" :key="pet.id" ref="pets">
                      {{ pet.name }}
                 </li>
            </ul>
            <ul v-else>
                 <li v-for="pet in allPets.unavailable" :key="pet.id" ref="pets">
                      {{ pet.name }}
                 </li>
            </ul>
    </div>
</template>

<script>

export default {
    props: ['allPets'],

    data() {
         showAvailable: true // Normally would change from a button or something
         shownPets: null // set to null for now
    },

    mounted() {
         this.$set(this.$data, 'shownPets', this.$refs.pets);    
    },

    watch: {
         shownPets: {
                         handler(newVal, oldVal){
                              // Do something when the DOM changes!
                         },
                         deep: true
                    }
    }
}
</script>

And voilà.瞧。 We set our data shownPets to our pets $ref after the component mounts.我们在组件安装后将我们的数据shownPets宠物设置为我们的宠物$ref The reference will hold different elements based on if showAvailable is true or false , and now we can watch for $ref or DOM changes.引用将根据showAvailabletrue还是false保存不同的元素,现在我们可以观察 $ref 或 DOM 的变化。

the following solution uses MutationObserver以下解决方案使用MutationObserver

in mounted hook add :在安装的钩子中添加:

const config = {
      attributes: true,
      childList: true,
      subtree: true
    };
 // this will be triggered for any change in your element
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation) {
          this.infoHeight = this.$refs.info.clientHeight + 'px'
          console.log(" changed ", this.$refs.info.clientHeight)
        }
      });
    });
//observe the referenced element
    this.observer.observe(this.$refs.info, config);

Full example完整示例

 Vue.config.devtools = false; Vue.config.productionTip = false; new Vue({ el: '#app', data: () => ({ infoHeight: 0, observer: null, img: "https://images.ctfassets.net/hrltx12pl8hq/6TOyJZTDnuutGpSMYcFlfZ/4dfab047c1d94bbefb0f9325c54e08a2/01-nature_668593321.jpg?fit=fill&w=480&h=270" }), mounted() { const config = { attributes: true, childList: true, subtree: true }; this.observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation) { this.infoHeight = this.$refs.info.clientHeight + 'px' console.log(" changed ", this.$refs.info.clientHeight) } }); }); this.observer.observe(this.$refs.info, config); }, beforeDestroy(){ this.observer.disconnect() }, methods: { changeImg() { this.img = "https://i.pinimg.com/originals/a7/3d/6e/a73d6e4ac85c6a822841e449b24c78e1.jpg" }, } })
 <link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script> <div id="app" class="container"> <p>{{infoHeight}}</p> <button class="btn btn-primary" @click="changeImg">Change image</button> <div ref="info"> <img :src="img" alt="image" /> </div> </div>

This is possible if you define the property in $refs during created(), or during beforeCreate() if you don't need the backing store in data.如果您在 created() 期间在 $refs 中定义属性,或者如果您不需要数据中的后备存储,则在 beforeCreate() 期间定义属性,这是可能的。 For example:例如:

 const vm = new Vue({ el: "#app", data() { return { mode: 0, refTest: undefined }; }, created() { Object.defineProperty(this.$refs, 'test', { get: function () { return this.refTest; }.bind(this), set: function (newValue) { console.log(`set - Setting test refs to an array? ${Array.isArray(newValue)}`); this.refTest = newValue; }.bind(this), enumerable: true }); }, watch: { refTest: { handler(newValue, oldValue) { console.log(`watch - array? ${newValue ? Array.isArray(newValue) : undefined}`); }, deep: false, immediate: true } }, methods: { toggle() { this.mode = (this.mode === 1) ? 2 : 1 } } });
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"> <button @click="toggle">Toggle</button> <ul v-if="mode === 1"> <li ref="test">Single Ref</li> </ul> <ul v-else-if="mode === 2"> <li ref="test" v-for="s in ['Multiple', 'Refs']" :key="s">{{ s }}</li> </ul> <div v-else>No Refs</div> </div>

This is the only solution I have experimented with which works when the ref is not mounted along with the component itself.这是我尝试过的唯一解决方案,当参考没有与组件本身一起安装时。

The reason to use defineProperty here is because if the ref changes, the watcher will lose track of it.这里使用defineProperty的原因是因为如果ref发生变化,观察者将失去对它的跟踪。

If you want to handle your action right in the defineProperty, I suppose that would be ok and then you could get rid of your watcher.如果您想在defineProperty 中处理您的操作,我想那没问题,然后您就可以摆脱您的观察者了。 But you will always need to have a backing store -- though it doesn't need to be in data.但是你总是需要有一个后备存储——尽管它不需要在数据中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM