问题描述
想要实现这样的效果:当窗口大小发生变化时,图表的大小自动发生变化。
问题:一个页面中使用同一个组件多次时,当窗口大小发生变化时会触发resize事件,且resize事件做了节流优化,预期效果不对:只有第一个组件和最后一个组件生效了
base-chart组件
xml
<template>
// todo...
<div ref="chart"></div>
</template>
<script>
export default {
methods: {
resizeChart: throttle(function () {
this.myChart && this.myChart.resize();
}, 300),
},
mounted() {
window.addEventListener('resize', this.resizeChart);
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeChart);
},
};
</script>
使用时引入多个BaseChart
ruby
<template>
<div>
<BaseChart ref="chart" :params="params" :api="$Invoke.dashboard.getStuckPoint" :genChartOption="genChartOption"></BaseChart>
<BaseChart ref="chart" :params="params" :api="$Invoke.dashboard.getStuckPoint" :genChartOption="genChartOption"></BaseChart>
<BaseChart :params="params" :api="$Invoke.dashboard.getStuckPoint" :genChartOption="genChartOption"></BaseChart>
<BaseChart :params="params" :api="$Invoke.dashboard.getStuckPoint" :genChartOption="genChartOption"></BaseChart>
<BaseChart :params="params" :api="$Invoke.dashboard.getStuckPoint" :genChartOption="genChartOption"></BaseChart>
<BaseChart :params="params" :api="$Invoke.dashboard.getStuckPoint" :genChartOption="genChartOption"></BaseChart>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$refs.chart[0].resizeChart === this.$refs.chart[1].resizeChart) // false
},
};
</script>
这种问题看起来特别像是所有的BaseChart都使用的同一个throttle(节流)函数似的,但是通过打印出来的结果发现是false。
通过常识排查不出问题所在...

问题排查

1、会在实例中初始化一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> o p t i o n s , v m . options, vm. </math>options,vm.options.proto=指向该组件函数的静态属性options上

源码:

2、初始化各个状态
先看下initMethods,从源码中可以看出initMethods的第二个参数是从$options中拿到的,也就意味着多个组件获取的是同一个method

initMethods中做了处理:通过bind将methods中的方法中的this改成当前组件实例。这也是为什么每个组件实例方法引用地址各不相同的原因
( console.log(this. <math xmlns="http://www.w3.org/1998/Math/MathML"> r e f s . c h a r t [ 0 ] . r e s i z e C h a r t = = = t h i s . refs.chart[0].resizeChart === this. </math>refs.chart[0].resizeChart===this.refs.chart[1].resizeChart) => false )

所以可以分析出来问题原因了,method中所有的属性都是在函数的静态属性(option)上,当同一个组件在同一页面使用多次,会使用methods中同一方法,也就造成节流函数也是同一个。
ini
console.log(this.$refs.chart[0].$options.methods.resizeChart === this.$refs.chart[1].$options.methods.resizeChart) // true
console.log(this.$refs.chart[0].$options.__proto__.methods.resizeChart === this.$refs.chart[1].$options.methods.resizeChart) // true
问题解决
问题解决有两种方案
第一种
data每个组件使用时,都会执行data函数生成一份新的对象,这样每个组件都有自己的resizeChartThrottle。
vue
<template>
// todo...
<div ref="chart"></div>
</template>
<script>
export default {
data() {
return {
resizeChartThrottle: throttle(this.resizeChart, 300),
}
},
methods: {
resizeChart(){
this.myChart && this.myChart.resize();
}
},
mounted() {
window.addEventListener('resize', this.resizeChartThrottle);
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeChartThrottle);
},
};
</script>
第二种
在组件的this中注入自己的resizeChart,这个每个组件都有自己的resizeChart
vue
<template>
// todo...
<div ref="chart"></div>
</template>
<script>
export default {
mounted() {
this.resizeChart = throttle(() => {
this.myChart && this.myChart.resize();
}, 300);
window.addEventListener('resize', this.resizeChart);
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeChart);
},
};
</script>
其他情况
经过查看源码,发现其实不止methods结合节流、防抖会存在这样的问题,watch和computed、mounted、created等也是同样如此。
如下面举例
vue
<template>
<div>
<p>Count: {{ count }}</p>
<p>ComputedCount: {{ computedCount }}</p>
<p>WatchCount: {{ watchCount }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { throttle } from "./utils"
export default {
data() {
return {
count: 0,
computedCount: 0,
watchCount: 0,
}
},
computed: {
// cCount: throttle(function () { TODO... }, 300),
cCount: {
get() {
return this.count
},
set: throttle(function (val) {
this.computedCount++
}, 300),
},
},
watch: {
// count: throttle(function () { TODO... }, 300),
count: {
immediate: true,
handler: throttle(function () {
this.watchCount++
}, 300),
},
},
// mounted:throttle(function (){
// //
// })
mounted() {
this.increment()
this.cCount++
},
methods: {
increment: throttle(function () {
this.count++
}, 300),
},
}
</script>
总结
- 如果该封装的组件只在一个页面同时使用一次,那么节流函数则没有那么多限制
- 如果一个页面同时使用多次则需要按照问题解决中提到的两种方案进行使用 (踩坑 -> 推荐)