【问题标题】:VueJS $watch $refsVueJS $watch $refs
【发布时间】:2016-12-26 09:44:08
【问题描述】:

可以$watchVue$refs吗?

我想针对嵌套在当前 Vue 实例中但在 ready 回调中的子组件设置逻辑,$refs.childcomponent 在处理时最初是 undefined

内部ready()

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

结果:错误:超出最大调用堆栈

watch 实例的属性

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

结果:没有。

【问题讨论】:

  • 这里的; deep: true; 是错字吗?
  • 是的,这不在实际代码中,这只是我对 stackoverflow 的错误移植。好地方,已删除。
  • deep: true => 超出最大调用堆栈大小

标签: javascript vue.js


【解决方案1】:

你可以$watch$refs.<name>.<data>,但不能$refs.<name>本身,更不用说$refs了。

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. 在挂载时可能未定义。创建 comp 时,您应该 this.$watch(() =&gt; this.$refs.comp.prop, (val) =&gt; {...})
  • @aztack 所以在mounted钩子里做吧
  • @KenBerkeley 不一定,可以随时创建带有 ref 的元素。例如,可以在 API 调用返回响应后,通过单击按钮创建表示文章的元素。
  • 这个例外的答案是什么? OP 说:$refs.childcomponent is initially undefined while it's processed. 我处于同样的情况,模板看起来像:&lt;div v-if="product"&gt;&lt;div ref="myRef"&gt; 所以在挂载中this.$refs.myRef 是未定义的,this.$refs.myRef.someProp 会抛出。谷歌搜索vue watch ref 在顶部给我这个答案,但我看不出这个答案如何为我或 OP 工作
  • 我解决它的方法是看product: watch: { product() { this.$refs.myRef 因为v-if 有条件地呈现 ref 如果产品是真实的。
【解决方案2】:

不,$refs 不是响应式的,watch 不起作用。

【讨论】:

  • 是的...¯\_(ツ)_/¯
  • 那么 Vue 对 DOM 元素变化做出反应的正确方法是什么?
【解决方案3】:

在下面的挂载使用代码中:

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

【讨论】:

    【解决方案4】:

    如果您在 created() 期间在 $refs 中定义属性,或者如果您不需要数据中的后备存储,则可以在 beforeCreate() 期间定义属性。例如:

    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>

    这是我尝试过的唯一解决方案,当 ref 没有与组件本身一起安装时,它可以工作。

    这里使用defineProperty的原因是因为如果ref改变了,watcher就会失去对它的追踪。

    如果您想直接在 defineProperty 中处理您的操作,我想这没问题,然后您可以摆脱您的观察者。但是你总是需要有一个后备存储——尽管它不需要在数据中。

    【讨论】:

    • 这是唯一适用于我的解决方案,谢谢!
    【解决方案5】:

    有一个解决方法。考虑到 JavaScript 在将数组分配给变量时不会创建数组的副本,它只是创建对原始数组的引用。知道 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>
    

    然后瞧。在组件挂载后,我们将数据shownPets 设置为我们的宠物$ref。引用将根据showAvailabletrue 还是false 来保存不同的元素,现在我们可以观察$ref 或DOM 的变化。

    【讨论】:

      【解决方案6】:

      以下解决方案使用MutationObserver

      在挂载的钩子中添加:

      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);
      

      完整示例

      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>

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-12-22
        • 2020-08-28
        • 2021-07-16
        • 2021-07-14
        • 2021-03-21
        • 2017-04-14
        • 2021-05-17
        相关资源
        最近更新 更多