可以
$watch
Vue $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
}
}
结果:什么都没有。
您可以
$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
没有反应,手表无法工作。
在安装使用代码如下:
this.$watch(
() => {
return this.$refs.<name>.<data>
},
(val) => {
alert('$watch $refs.<name>.<data>: ' + val)
}
)
有一个解决办法。考虑到 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
。该引用将根据 showAvailable
是 true
或 false
来保存不同的元素,现在我们可以观察 $ref 或 DOM 更改。
如果您在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>
这是我尝试过的唯一解决方案,当引用未与组件本身一起安装时,该解决方案有效。
这里使用defineProperty的原因是因为如果ref发生变化,观察者将失去对它的跟踪。
如果你想在defineProperty中处理你的动作,我想那没问题,然后你就可以摆脱你的观察者了。但您始终需要有一个后备存储 - 尽管它不需要位于数据中。
以下解决方案使用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>
在Vue 3中测试:你至少可以在
$watch
中的
$refs
上使用浅
mounted()
mounted () {
this.$watch(
'$refs',
this.$refs.childComponent?.whatever(),
{ immediate: true }
)
}
使用可选的链接运算符 (
?.
) 很重要,因为 $refs.childComponent
最初可能是未定义的。但是当它添加到$refs
时,观察者将再次被触发。
万一这里有人像我一样遇到这个问题,除了模板参考之外还需要观看构图参考,这里是如何做到这一点
import { ref, watch } from 'vue'
const watchedRef = ref('hello')
watch(() => watchedRef.value, () => {
console.log('watching', watchedRef.value
// do more stuff here
})