慎用watch监听——记一次Vue页面卡顿排查

这是我参与更文挑战的第9天,活动详情查看: 更文挑战css

开心,T恤到手~前端

策之不以其道,食之不能尽其材,鸣之而不能通其意,执策而临之,曰:“天下无马!”呜呼!其真无马邪?其真不知马也!vue

运营人员反馈某商品列表页面,每页1000条时,页面卡顿严重,已经影响使用,经实际测试,1000个条记录时,页面加载长达2分钟+,而且会出现卡死状况。遂开始优化之旅~~java

页面元素

页面主体部分截图以下,每一个元素包括一张主图,一些基本的信息字段。运营反馈当设置分页数量为1000时,会出现卡死,通过实际测试,能够重现。数据库

image.png

主体前端代码分为两个部分,一个是包裹这些元素的父元素,一个是元素组件:后端

<!-- 父元素关键代码 --> 
<div v-loading="productsLoading" class="choosingproduct-products">
     <!-- product-card为子组件,使用v-model绑定列表元素 -->
      <product-card
        v-for="(item, index) in result.list"
        :key="index"
        v-model="result.list[index]"
        :showpoint="false"
      />
      <div style="width: 100%;margin-top: 15px;margin-bottom: 85px" class="center">
        <pagination
          :total="result.totalRow"
          :page.sync="query.pageNum"
          :limit.sync="query.numPerPage"
          @pagination="getSkus"
        />
  	</div>
</div>
复制代码

子组件ProductCard的关键代码以下:markdown

<template>
  <div class="productcard flex-col-start-start" @click="changeChoosed">
    <div class="flex-row-center-spacebetween" style="width: 100%">
       <!-- 关键点1:状态切换使用了visibility -->
      <el-checkbox v-model="value.choosed" class="productcard-checkbox " :class="{'productcard-delete' : !value.choosed}" @change="changeChoosed" />
    </div>
    <div style="width: 100%" class="center">
       <!-- 关键点2: 图片过多 -->
      <el-image :src="value.picurl" style="height: 114px;width: 114px" />
    </div>
    <div .... />
  </div>
</template>

<script>
export default {
  name: 'ProductCard',
  props: {
    value: {
      type: Object,
      default() {
        return {}
      }
    },
  },
  data() {
    return {
      product: {
        choosed: false
      }
    }
  },
  watch: {
     // 关键点3: 也是最重要的一点,watch监听不当,致使性能与卡顿
     product: {
       deep: true,
       handler: function(newValue, oldValue) {
               // 向上层组件发送改变后的数据
        	this.$emit('input', this.product)
   	   }
    },
    value: {
      deep: true,
      handler: function() {
        this.product = this.value
      }
    }
  },
  created() {
    this.product = this.value
  },
  methods: {
    changeChoosed() {
      // 改变选中状态
      this.product.choosed = !this.product.choosed
    }
  }
}
</script>

<style scoped lang="scss">
.productcard {
  .productcard-checkbox {
  	visibility: hidden;
  }

  .productcard-delete {
    visibility: hidden;
  }
}

 .productcard:hover {
   .productcard-checkbox {
   	 visibility: visible;
   }
   .productcard-delete {
    visibility: visible;
   }
 }
</style>

复制代码

后端接口

后端根据前端选择的条件,执行查询SQL,返回数据,使用的数据库链接池Druid+ActiveRecord模式。(对这一点感兴趣的能够检索jfinal相关)curl

public Page<Record> getData() {
		......
		Page<Record> result = Db.paginate(pageNum, numPerPage, select, exceptSelect);
    	for(Record r : result.getList()) {
            display(r);
        }
	    return result;
}
复制代码

分析步骤

由于后端的性能更好分析一些,本人也更熟悉后端,因此先从后端入手:svn

后端

通过测试,接口返回1000条数据的时间大约是17秒。post

  1. 这个时间至关长,有优化空间
  2. 前台卡顿时间2分钟不符合,不是前端卡死的问题关键

前端

使用Chrome-Performance监听加载1000条数据的性能状况,咱们获得以下几张图:

image.png

image.png

从而肯定了卡顿问题主要在前端,并且从renderList咱们猜想,卡顿主要在于ProductCard这个组件的渲染上。

猜想

定位到了ProductCard组件后,咱们首先根据Network查看到图片的加载时间较长,猜想多是图片过多致使加载过长。

第二个,咱们注意到选中与不选中,咱们在CSS中是经过变换组件的visibility属性来实现的。是不是visibility切换会致使组件渲染卡顿呢?咱们检索资料发现visibility:hidden的性能实际比display:none要好。

第三个(终于找到你!),咱们在组件中使用了watch,同时监听组件内部元素productprops中的value,当product变更时,会经过this.$emit('input', this.product)传递给外部的value,而value变更,又会触发this.product = this.value,进而又致使product变更,造成近乎死循环。形成严重性能问题。

咱们获得如下结论:

  1. 图片过多致使加载过长
  2. 使用visibility
  3. 慎用watch

优化

后端

咱们使用parallelStream优化后端性能,优化后代码以下。接口响应时间从17秒左右降低到5秒。

public Page<Record> getData() {
		......
		Page<Record> result = Db.paginate(pageNum, numPerPage, select, exceptSelect);
    	result.getList().parallelStream().forEach(r -> {
			display(r);
		});
	    return result;
}
复制代码

前端

针对分析中的三点,咱们分别使用图片懒加载和修复watch后,1000条数据页面耗时大约为10秒,且不在出现卡死现象。

优化后的watch以下:

.......
watch: {
    value: {
      deep: true,
      handler: function() {
        this.product = this.value
      }
    }
  },
created() {
   this.product = this.value
},
methods: {
    changeChoosed() {
      this.product.choosed = !this.product.choosed
	  // 向上层发送数据
      this.$emit('input', this.product)
    }
......
}
复制代码

前端没有什么优化经验,欢迎指正!

若不吝可点个赞!

相关文章
相关标签/搜索