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、会在实例中初始化一个 <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>

总结

  1. 如果该封装的组件只在一个页面同时使用一次,那么节流函数则没有那么多限制
  2. 如果一个页面同时使用多次则需要按照问题解决中提到的两种方案进行使用 (踩坑 -> 推荐)
相关推荐
程序员大金10 分钟前
基于SpringBoot+Vue+MySQL的在线学习交流平台
java·vue.js·spring boot·后端·学习·mysql·intellij-idea
qq_25183645717 分钟前
基于SpringBoot vue 医院病房信息管理系统设计与实现
vue.js·spring boot·后端
一 乐2 小时前
租拼车平台|小区租拼车管理|基于java的小区租拼车管理信息系统小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·微信·notepad++·拼车
寻找09之夏6 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
多多米10057 小时前
初学Vue(2)
前端·javascript·vue.js
看到请催我学习7 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
golitter.9 小时前
Vue组件库Element-ui
前端·vue.js·ui
道爷我悟了9 小时前
Vue入门-指令学习-v-on
javascript·vue.js·学习
.生产的驴10 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
老齐谈电商10 小时前
Electron桌面应用打包现有的vue项目
javascript·vue.js·electron