在大数据盛行的今天,数据可视化变得越来越广泛。而在前端工作中,数据可视化用得最多的,可能就是图表了。在众多的图表插件中,echarts以其良好的性能和完善的API,图表的多样性和功能的完整性,被广大开发者认可,成为了前端图表使用最多的工具,所以,今天我就简单的讲一下,如何在vue中更优雅的使用echarts。
第一步,下载echarts我就不多说了:npm install echarts.
然后我们看一下一般人(刚开始用vue或echarts)的使用方法,大概是这个样子:
<template> <div> <div ref="chartColumn" style="width:100%; height:400px;"></div> <button @click="changeOption">点击改变内容</button> </div></template> <script> import echarts from 'echarts' export default { data() { return { chartColumn: null, option:{ title: { text: '普通图表' }, tooltip: {}, xAxis: { data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"] }, yAxis: {}, series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }, data:[5, 20, 36, 10, 10, 20,5, 20, 36, 10, 20, 36, 10, 10, 20,5, 20, 36, 10] } }, methods: { changeOption(){ var r = Math.floor(Math.random()*12); //splice会改变原来的数组 //var data = this.data.splice(r,6); var d = this.data.slice(r,r+6); this.option.series[0].data = d; this.columnChart.setOption(this.option); }, initChart() { this.chartColumn = echarts.init(this.$refs.chartColumn); this.chartColumn.setOption(this.option); } }, mounted: function () { this.initChart() } }</script>复制代码
上面的代码,虽然功能实现了,也没什么错误,但是需要优化的地方很多。
首先,我们考虑到多个地方需要用到echarts,封装一个组件出来,就像下面这样:
组件部分:
// components/chart-block.vue <template> <div ref="chartEl" style="height:100%"></div></template><script> import echarts from 'echarts'; export default { data(){ return { chart:null } }, props:{ option:{ type:Object } }, watch:{ option:{ handler(newValue, oldValue) { this.chart.setOption(newValue); }, // 因为option是个对象,而我们对于echarts的配置项,要更改的数据往往不在一级属性里面 // 所以这里设置了deep:true,不知道该属性的可以看一下vue的文档 deep: true } }, mounted(){ this.chart = echarts.init(this.$refs.chartEl); this.chart.setOption(this.option); } }</script>复制代码
然后使用它:
<template> <div> <div id="chartColumn" style="width:100%; height:400px;"> <chart-lock :option="option"></chart-lock> </div> <button @click="changeOption">点击改变内容</button> </div></template> <script> import chartBlock from '@/components/chart-block.vue' export default { components:{ chartBlock }, data() { return { option:{ //... 省略其他属性 series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }, data:[5, 20, 36, 10, 10, 20,5, 20, 36, 10, 20, 36, 10, 10, 20,5, 20, 36, 10] } }, methods: { changeOption(){ var r = Math.floor(Math.random()*12); //splice会改变原来的数组 //var data = this.data.splice(r,6); var d = this.data.slice(r,r+6); this.option.series[0].data = d; } } }</script>复制代码
和之前的比起来,我们看看封装后的组件做了什么:
1、不用给每个图表指定ref属性了
2、不用定义图表变量了
(1和2在我们同一个页面有多个图表时,减少了很多不必要的变量)
3、不用自己初始化图表了
4、数据改变时,我们自动监听,再也不需要手动处理了
(3和4在我们同一个页面有多个图表时,减少了重复逻辑的工作量)
上面针对的,是同一个页面有多个图表时的优化。接下来,我们继续优化,如果一个组件有多个页面都要用到,我们就该考虑把它注册为全局的,减少引用。代码如下:
// components/chart-block.vue <template> <div ref="chartEl"></div></template><script> import echarts from 'echarts'; var ChartBlock = { name:'ChartBlock', // 这里加个name属性,用于注册使用 ... //其他的属性 } /* 注册组件的方法 */ ChartBlock.install = function(Vue) { Vue.component(ChartBlock.name, ChartBlock); }; export default ChartBlock;</script>复制代码
main.js全局注册
import ChartBlock from '@/components/chart-block.vue'Vue.use(ChartBlock)复制代码
在需要使用组件的页面,下面的代码就不需要了
//import chartBlock from '@/components/chart-block.vue' export default { components:{ ... //chartBlock } ...}复制代码
好了,到这里已经解决了组件的复用性,以后再也不用引入和声明组件了。接下来再做点其他的。很多时候,我们的图表可能都需要跟随窗口进行实时的动态改变,每个组件都单独写,显然不现实,那我们最好的办法,就是把跟随窗口改变的代码直接写在组件里面,需要注意的是,一定要在组件销毁时移除窗口改变的监听。继续完善我们的组件,如下:
var ChartBlock = { name:'ChartBlock', data(){ return { chart:null } }, props:{ option:{ type:Object } }, methods:{ returnChartToParent(){ this.$emit('chartReady',this.chart); } }, watch:{ option:{ handler(newValue, oldValue) { this.chart.setOption(newValue); }, deep: true } }, mounted(){ this.chart = echarts.init(document.getElementById(this.id)); this.chart.setOption(this.option); // 添加窗口改变时的监听,跟随实时窗口改变 var chart = this.chart; this.chart.__resize = function(){ chart.resize(); }; setTimeout(() => { window.addEventListener('resize',this.chart.__resize); }, 200); }, beforeDestroy() { // 移除窗口改变监听 window.removeEventListener('resize',this.chart.__resize); } }复制代码
有了上面的补充,以后再也不用担心图表响应的问题了,只管调用即可。但是,这样就OK了吗?作为一个好(bushitailan)的开发,我们要的可不只是功能,还要考虑性能的问题啊。
第一点:像窗口改变大小这种事件,一旦存在拖动,将发生得太频繁,我们很有必要做一下节流处理(不知道函数节流的,自行了解一下,算是前端必须掌握的基础技能哦)。解决办法,自然就是添加一个节流函数,继续看代码,主要改变的就是mounted函数,其他不变。
var ChartBlock = { name:'ChartBlock', ... mounted(){ // 添加函数节流处理 this.chart = echarts.init(document.getElementById(this.id)); this.chart.setOption(this.option); // 节流函数,来自Lodash,这里可以自己写一个简单点的 // 如果有多个地方用到,也可以使用引入的方式 function throttle(func, wait, options) { let time, context, args, result; let previous = 0; if (!options) options = {}; let later = function() { previous = options.leading === false ? 0 : new Date().getTime(); time = null; func.apply(context, args); if (!time) context = args = null; }; let throttled = function() { let now = new Date().getTime(); if (!previous && options.leading === false) previous = now; let remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (time) { clearTimeout(time); time = null; } previous = now; func.apply(context, args); if (!time) context = args = null; } else if (!time && options.trailing !== false) { time = setTimeout(later, remaining); } }; return throttled; }; var chart = this.chart; this.chart.__resize = throttle(function(){ chart.resize(); },200); setTimeout(() => { window.addEventListener('resize',this.chart.__resize); }, 200); }, beforeDestroy() { window.removeEventListener('resize',this.chart.__resize); } }复制代码
第二点:也是更重要的一点,从vue 的角度出发,我们把图表的 option 写在 data 里,是很浪费性能的。为什么这么说呢,因为 vue 的数据改变监听实质上就是对 data 对象进行逐层循环,为每一个属性添加监听。而我们图表的数据对象,只是我们图表需要的一些配置项,压根不参与业务逻辑,一个简单的对象还好,但复杂的 echarts 图表,一个option 上100个属性,如果一个页面再有多个图表的话,得额外添加多少没用的监听器,可想而知。所以,我的建议是,把 option 数据写到 data 之外,然后通过调用 echarts 的 setOption 方法设置数据。既然如此,那我们就得在chart-block组件里暴露出我们的setOption方法,以供父组件调用了。继续修改代码:
<template> <div ref="chartEl" style="height:100%;"></div></template><script> import echarts from 'echarts'; var ChartBlock = { name:'ChartBlock', data(){ return { chart:null } }, // 去除props和watch,添加methods methods:{ setOption(option){ this.chart && this.chart.setOption(option) } }, mounted(){ ... //同上 }, beforeDestroy() { window.removeEventListener('resize',this.chart.__resize); } } /* 注册方法 */ ChartBlock.install = function(Vue) { Vue.component(ChartBlock.name, ChartBlock); }; export default ChartBlock;</script>复制代码
使用如下:
<template> <div> <div id="chartColumn" style="width:100%; height:400px;"> <!--少了option,多了ref,用于调用setOption方法--> <chart-lock ref="chart1"></chart-lock> </div> <button @click="changeOption">点击改变内容</button> </div></template> <script> import chartBlock from '@/components/chart-block.vue' // echarts图表的配置数据写在外面,而不是data里 let option1 = { //... 省略其他属性 series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }; let data = [5, 20, 36, 10, 10, 20,5, 20, 36, 10, 20, 36, 10, 10, 20,5, 20, 36, 10] export default { components:{ chartBlock }, data() { return { } }, methods: { changeOption(){ var r = Math.floor(Math.random()*12); var d = data.slice(r,r+6); option1.series[0].data = d; this.$refs.chart1.setOption(option1); } }, mounted(){ this.$refs.chart1.setOption(option1); } }</script>复制代码
这样一来,通过一个组件,我们就把echarts图表的复用性,响应性(还自带节流处理),以及数据分离都解决了,是不是跟一开始的代码比起来,优雅了很多呢。
好了,本文到此结束。文章如有写得不对或不足的地方,欢迎指出。如果有什么不明白的,可以通过留言提问。
最后,站在产品的角度说句话,我们可以给图表添加个 type 的 props 属性,在数据没拿到之前,根据type,展示不同的默认图,这样是不是比空白好了很多呢?
原文地址:liguixing.com/archives/10…
所有评论(0)