vue中使用节流遇到的问题、解决方案以及深入探索

问题描述

想要实现这样的效果:当窗口大小发生变化时,图表的大小自动发生变化。

问题:一个页面中使用同一个组件多次时,当窗口大小发生变化时会触发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、会在实例中初始化一个 o p t i o n s , v m . options, vm. options,vm.options.proto=指向该组件函数的静态属性options上

源码:

2、初始化各个状态

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

initMethods中做了处理:通过bind将methods中的方法中的this改成当前组件实例。这也是为什么每个组件实例方法引用地址各不相同的原因

( console.log(this. 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.chart0.resizeChart === this. refs.chart0.resizeChart===this.refs.chart1.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>

总结

  1. 如果该封装的组件只在一个页面同时使用一次,那么节流函数则没有那么多限制
  2. 如果一个页面同时使用多次则需要按照问题解决中提到的两种方案进行使用 (踩坑 -> 推荐)
相关推荐
慧一居士8 小时前
Element Plus 按需引入的配置使用说明和完整示例
前端·vue.js
CoderWeen12 小时前
我写了个能一步步点着看的 Dijkstra 可视化项目(Vue3 + Leaflet + Generator)
前端·javascript·vue.js
无人生还13 小时前
第 1 篇:从 Vue3 到 React 的思维转变
前端·vue.js·react.js
就业发动机13 小时前
我用 Tauri 做了一个约 1.5 MB 的开源屏幕标注工具:MarkerOn
vue.js·rust
爱分享的程序猿-Clark13 小时前
【前端分享】vue开发项目,如何实现 从A页面跳转到B页面,并传值!
前端·javascript·vue.js
徐小夕14 小时前
花了3天,我写了一款开源AI公众号编辑器
前端·vue.js·github
come1123414 小时前
Vue 2 与 Vue 3 语法区别及使用技巧
前端·javascript·vue.js
腻害兔15 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:ERP 企业资源模块,一个轻量级进销存的完整实现?
前端·javascript·vue.js·人工智能·前端框架·产品经理·ai编程
Simon_He15 小时前
AI 对话里的流式 Markdown 为什么难:从不完整 Token 到增量渲染
vue.js
达子66615 小时前
第4章_图解HarmonyOS 的结构
vue.js