在vue下封装echarts公共组件的总结

这几天公司里边有一个项目,叫做日控制台,该项目是在webview下的一个webapp,使用vue构建,项目中要求使用许多自定义的图表。考察了许多图表组件之后,发现echarts是所有表库中,最灵活,特效最好看的一种。

一、构建基础公共组件

1. 实现基础功能

在echart官网上搜索到,如何使用

# 1. 获取一个用于挂在 echarts 的 DOM 元素
let $echartsDOM = document.getElementById('echarts-dom')

# 2. 初始化
let myEcharts = echarts.init($echartsDOM)

# 3. 设置配置项
let option = {...}

# 4. 为 echarts 指定配置
myEcharts.setOption(option)

使用echart的步骤也就这几部,就是先获取到承载echart实例的dom实例,然后调用init()方法初始化图标实例,最后调用setOption()方法传入配置项
这几步都要在vue的mounted方法下实现.

mounted() {
      let $echartsDOM = document.getElementById('echarts-dom')
      let myEcharts = echarts.init($echartsDOM)
      let option = {
        title: {
          text: 'ECharts 入门示例'
        },
        tooltip: {},
        legend: {
          data: ['销量']
        },
        xAxis: {
            data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
        },
        yAxis: {},
        series: [{
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
        }]
    }
    myEcharts.setOption(option)
  }
}

注:在 Vue 中,首先我们需要使用 import echarts from 'echarts' 以引入 echarts。

二、组件化

思路很简单,就是将业务上用到的图表,比如柱状图、折线图,通通封装成组件,然后再main app文件中调用,通过分析,可以通过传props,来改变setOption()方法中的对象,达到封装不同图表组件的目的。

 <ocEcharts class="echarts-container" :options="ocoptions" />

将之前的options转移到main app中的data对象之中。
注:echarts图表要求承载的容器具有固定的宽高才能正常显示

.echarts-container{
  width: 100%;
  height: 20rem;
}

1. 组件优化-props的series校验

如果在传入组件的props中传入了空对象,就会发现,图表会抛出一个错误,即

Error: Option should contains series.

原因就是传入的 option 配置对象不含有 series 键,所以,默认值处理是需要存在的,即当调用方传入的对象为空或不存在 series 配置时,应在页面上显示一些提示( 对用户友好的提示,而不是对编程人员 ),即避免因报错而造成空白的情况。
此外,当我们像之前那样给 option 这一参数进行类型限制后,倘若调用方传入非对象类型,Vue 会直接抛出错误——这一结果也不是我们想要的。我们应该取消类型限制,并在 option 发生变化时进行依次以下判断:

// 1. 是否为对象;
export function isObject(option) {
  return Object.prototype.isPrototypeOf(option)
}

// 2. 是否为空对象;
export function isEmptyObject(option) {
  return Object.keys(option).length === 0
}

// 3. 是否包含 series 键;
export function hasSeriesKey(option) {
  return !!option['series']
}

// 4. series 是否为数组;
export function isSeriesArray(option) {
  return Array.isArray(option['series'])
}

// 5. series 数组是否为空。
export function isSeriesEmpty(option) {
  return option['series'].length === 0
}

export function isValidOption(option) {
  return isObject(option) && !isEmptyObject(option)
    && hasSeriesKey(option)
    && isSeriesArray(option) && !isSeriesEmpty(option)
}

然后在组件中引入最后的isValidOption方法作为判断,我们先使用一个watch监听options的变化

 watch: {
    options(options){
      this.checkAndSetOption()
    }
  },
  methods: {
    checkAndSetOption(){
      let options = this.options
      if(isValidOption(options)){
        this.myEcharts.setOption(options)
        this.isOptionAbnormal = false
      }else{
        this.isOptionAbnormal = true
      }
    }
  }

这里的checkAndSetOption方法是判断传入的option是否合法,如果合法,就执行setOption,isOptionAbnormal变量是监控页面是否显示options非法的flag。
同样的dom样式上的改变,也要使用v-show来判断isOptionAbnormal是否要渲染图表和渲染错误信息。

<div>
    <div class="shadow" v-show="isOptionAbnormal">数据为空</div>
    <div class="oc_echarts_container">
      <div class="echarts" id="echarts-dom" v-show="!isOptionAbnormal" />
    </div>
</div>

.oc_echarts_container, .echarts {
  width: 100%;
  height: 100%;
}

.shadow {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1rem;
  color: #8590a6;
}

2. 增强组件功能 - 数据加载提示

<div class="loading" v-show="isLoading">
        数据加载中...
 </div>

在组件内部需要一个外部的props,isLoading,但是这里需要注意,在 Vue 中,v-show 使用 display 控制组件的显隐。而当 echart init 的时候,如果其挂载 DOM 的 v-show 处于 false 状态,则其 init 的对象宽高都是 0。即使之后 v-show 状态改变,由于 mounted 生命周期不会再次触发,从而使得 echarts 显示不正常。所以需要使用css的visibility来控制显隐。

computed: {
    isChartVisible() {
       return !this.isLoading && !this.isOptionAbnormal
    }
  },
 <div class="oc_echarts_container">
      <div class="echarts" id="echarts-dom" :style="{visibility: isChartVisible ? 'visible' : 'hidden'}" />
    </div>

3. 组件复用-随机ID

echarts 进行 init 挂载时使用的是 DOM 元素的 ID。而在组件中,我们设置的 ID 是固定的( 注意与 scoped css 进行区分 )。如果多个组件的 ID 是相同的,只有一个组件会被 echarts 挂载。
所以我们要设定一个随机的randomId,赋值到承载echarts图表的dom元素的id中

<div class="oc_echarts_container">
      <div class="echarts" :id="randomId" :style="{visibility: isChartVisible ? 'visible' : 'hidden'}" />
    </div>

三、延迟加载

延迟加载是组件的一个优化,在业务的开发中可以看到,一个页面往往有着许多的图表,图标伴随着许多异步请求和canvas渲染,如果一次性渲染所有的图表会导致许多的性能问题。这里想到的一个解决方案就是延迟加载。
用通俗的话讲就是,页面滚动到哪张图表就去渲染哪一张图表。
完成这一功能需要以下步骤:

  1. 监听页面滚动事件;
  2. 滚动事件中获取 echarts 的位置;
  3. 在页面当前位置达到 echarts 位置的时候进行 echarts 的初始化。

1. 监听页面滚动

如果要监听页面滚动,需要用到dom的监听器,addEventListener('scroll', [funciton])。这样就能为每一个组件附上监听事件。

2. 获取当前滚动下边界和组件的上边界

这一个步骤需要封装成一个函数,checkPosition()

checkPosition() {
      let windowHeight = document.documentElement.clientHeight||window.innerHeight
      let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      let windowBottom = scrollTop + windowHeight
      const selfTop = _.get(this.$refs, 'selfEcharts.offsetTop', 0);
      if(windowBottom >= selfTop) {
        this.isPositionReady = true
        this.checkAndSetOption()
        window.removeEventListener('scroll',  this.scrollEvent)
      }
    },
  • windowBottom(滑动的下边界) = scrollTop(屏幕当前滑动的距离) + windowHeight(窗口的高度)
  • selfTop(当前组件的顶部位置)
    当第一个变量大于第二个变量时,就认为滑动到了该图表组件,就开开启加载

3. 初始化

data() {
    return {
      myEcharts: null,
      isOptionAbnormal: false,
      randomId: 'echarts-dom' + Date.now() + Math.random(),
      scrollEvent:  _.throttle(this.checkPosition, 500), // 滑动事件
      isPositionReady: false, //控制数据异步与页面滚动先后顺序的flag
    }
  },
mounted() {
    let $echartsDOM = document.getElementById(this.randomId)
    if(!$echartsDOM) return
    this.myEcharts = echarts.init($echartsDOM)
        // 第一次未滑动的时候
    this.checkPosition()
        //滑动之后的监听
    window.addEventListener('scroll', this.scrollEvent)
  },

checkPosition方法不仅要在scroll监听事件中调用,在组件第一次渲染的时候也要调用一次进行初始化,不然,组件无法正常渲染图表。

4.节流

组件代码经过测试之后发现,如果滚动过于快速,会不停的调用checkPosition(),关键是这个方法会不停的去setOption(),所以以上代码均采用了lodash的throttle节流方法,在500毫秒内只允许调用一次。

5. 解绑监听事件

解绑事件,组件中有设定监听器,如果该图表已经被加载了,那么这个监听器就没有作用了。

window.removeEventListener('scroll',  this.scrollEvent)

这段代码就是解绑监听器,多多少少会优化一下速度吧。因为增加监听和解绑监听的时间函数要求一致,所以在data中新增了scrollEvent,顺便把节流函数一起加了上去。

6. 请求异步控制setOption

由于用于渲染 echarts 的数据常常是异步获取的,也就是说,option 可能会在异步调用结束之后更新,从而触发 option 的 watch,进而导致 this.checkOption() 执行,最终使得 setOption 在页面没有滚动到合适位置时就触发了。
为了解决这个问题,我们应该让 setOption 的过程受制于一个标识位,而该标识位会在页面滚动到合适位置时置为 true,从而杜绝由于 option 更新、触发 watch 而导致的漏洞。
首先,我们要添加一个新的 data,取名为为 isPositionReady,然后,在 checkAndSetOption() 中加入对该标识位的判断:最后,在位置检测方法 checkPosition() 中,当达到合适位置时,将该标识位置为 true

    checkPosition() {
     // ....
      if(windowBottom >= selfTop) {
        this.isPositionReady = true
        // ....
      }
    },
    checkAndSetOption() {
    // ....
      if(!this.isPositionReady) return 
      //....
    }

四、echarts重绘

这里的重绘指的是 ehcarts 中的 resize() 方法。用于在某些时刻进行 echarts 的调整,包括:

  • 组件宽度设置为百分比,浏览器宽度发生变化时;
  • 页面收缩元素状态改变,如侧边栏收缩导致内容区宽度变化;

1.页面宽度改变

继续增加监听器就能完成

window.addEventListener('resize', _.throttle(() => {
    this.myEcharts.resize()
    console.log('---')
}, 500))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容