策略和状态模式应用——tab切换时,图表内容变化
注意:本篇博客不讲原理,如果看不懂请先补充策略模式、状态模式、闭包、惰性求值、函数式编程等相关内容
一、效果
二、为什么使用策略和状态模式
vue普通的tab切换很简单,使用下面代码就可以搞定。凡事就怕但是,如果使用下面的代码形式画echarts的话,切换图的时候,因为echarts获取不到宽高,导致图渲染不到页面上去。有人会说可以加this.$nexttick,是的。可以通过加入该函数做到延迟的echarts获取到dom宽高再渲染,但是需要有几个切换加几个this.$nexttick。而且有几个切换需要声明几个同于存储的echarts实例的变量,增加了以后的变量维护数量(有人会说,用一个变量存储ehcarts实例不就行了,如果站在方便的角度说的话,确实如此。如果是以后期维护角度的话,建议要和tab切换的li和content数量一致,因为它们是一一对应的一目了然)。
<div> <ul class="eofe-tab-title eo-flex"> <li :class="['eofe-tab-title-item', curIndex === 0 ? 'title-item-active' : '']" @click="operCtx(tabStrategy,'domestic')" > tab1 </li> <li :class="['eofe-tab-title-item', curIndex === 1 ? 'title-item-active' : '']" @click="operCtx(tabStrategy,'abroad')" > tab2 </li> </ul> <div class="eofe-tab-content"> <div v-show="curIndex === 0" class="eofe-tab-content-item"> tab1 </div> <div v-show="curIndex === 1" class="eofe-tab-content-item"> tab2 </div> </div> </div> 复制代码
在实际开发tab切换的时候,我没有使用上面形式的代码,而是使用下面形式的代码
<template> <div class="eo-finance-echarts"> <ul class="eofe-tab-title eo-flex"> <li :class="['eofe-tab-title-item', curIndex === 0 ? 'title-item-active' : '']" @click="operCtx(tabStrategy,'domestic')" > 国内融资 </li> <li :class="['eofe-tab-title-item', curIndex === 1 ? 'title-item-active' : '']" @click="operCtx(tabStrategy,'abroad')" > 海外融资 </li> </ul> <div class="eofe-tab-content"> <div class="eofe-tab-content-item"> <div class="banner">{{ banner }}</div> <div ref="chart" class="chart"></div> </div> </div> </div> </template> 复制代码
两个tab切换对应一个content内容(因为它们两个都是画echarts图的,所以就放到一个里面了)。这样的话声明用来存储echarts实例的变量就可以两个共用一个了,以后就维护一个变量就ok了,也解决了tab切换时echarts获取dom宽高的问题(不使用this.$nexttick)。因为本人比较注重代码耦性问题,不喜欢高耦合代码,所以在平时编程过程中使用设计模式比较多。在本次tab切换开发中使用了策略模式和状态模式。我使用策略模式主要是为了解决if判断问题,本人非常不喜欢if else判断,所以平时能不写if else判断的地方都不会写,除此之外策略模式还具有1、算法可以自由切换。2、扩展性、复用性良好等特性。又因为tab切换是二对一形式的,一个content 图表内容对应两种状态一个是国内,一个是国外。符合状态模式。使用设计模式对以后拓展和维护都是省力。
三、策略模式写tab切换
策略模式定义:把一些小的算法,封装起来,使他们之间可以相互替换(把代码的实现和使用分离开来)
/** * operCtx 操作上下文 * @param { any } strategyObj 策略对象 * @param { string } param * @param { any } arg * @return { void } */ operCtx(strategyObj: any, param: string, ...arg: any): void { strategyObj[param].apply(this, arg); } //operHandle 弹出层策略方法 tabStrategy = { domestic: function (this:EoFinanceEcharts) { if(this.curIndex===0)return; console.log('国内'); this.curIndex=0; this.banner='国内本周融资:4家 | 金额:2.76亿人民币'; this.stateCtx?.changeState('domestic').action(); }, abroad: function (this:EoFinanceEcharts) { if(this.curIndex===1)return; console.log('国外'); this.curIndex=1; this.banner='国外本周融资:5家 | 金额:5.76亿人民币'; this.stateCtx?.changeState('abroad').action(); } }; 复制代码
对相互替换的理解:在不同li中使用operCtx(tabStrategy,'xxx')进行click绑定,当进行不同li点击时operCtx函数中执行不同的函数,执行不同的函数之间互为替换关系。
四、状态模式写画图的不同状态
状态模式定义:允许一个对象在其状态改变时改变他的行为,对象看起来视乎修改了他的类(状态驱动行为)
createStateCtx() { let currentState: any = {}; let action: any = {}; const Ctx = { changeState: function (...arg: any) { currentState={}; arg.map((item: any) => { currentState[item] = true; }); return this; }, action: function () { //遍历参数对象,不含继承 Object.keys(currentState).forEach((k) => action[k] && action[k].call(this, this)); return this; }, setState: function (setAction: any) { action = setAction; return this; } }; return Ctx; } chartAction = { domestic: () => { console.log('国内domestic'); this.createEchart({xAxis:[ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],series:[ 820, 932, 901, 934, 1290, 1330, 1320 ]}); }, abroad: () => { console.log('国外abroad'); this.createEchart({xAxis:[ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],series:[ 800, 732, 901, 932, 1090, 1430, 1320 ]}); } }; mounted() { this.stateCtx = this.createStateCtx(); this.stateCtx.setState(this.chartAction)?.changeState('domestic').action(); } destroyed() { this.stateCtx = null;//因为使用到了闭包,所以声明周期最后,最好销毁 } 复制代码
总体代码
<template> <div class="eo-finance-echarts"> <ul class="eofe-tab-title eo-flex"> <li :class="['eofe-tab-title-item', curIndex === 0 ? 'title-item-active' : '']" @click="operCtx(tabStrategy,'domestic')" > 国内融资 </li> <li :class="['eofe-tab-title-item', curIndex === 1 ? 'title-item-active' : '']" @click="operCtx(tabStrategy,'abroad')" > 海外融资 </li> </ul> <div class="eofe-tab-content"> <div class="eofe-tab-content-item"> <div class="banner">{{ banner }}</div> <div ref="chart" class="chart"></div> </div> </div> </div> </template> <script lang="ts"> import Vue from 'vue'; import Component from 'vue-class-component'; import * as echarts from 'echarts'; @Component({ components: {} }) export default class EoFinanceEcharts extends Vue { curIndex = 0; stateCtx: { changeState: any; action: any; setState: any }|null; banner='本周融资:4家 | 金额:2.76亿人民币'; instance:echarts.ECharts|undefined; //stock echarts 实例对象 chartBaseConfig:echarts.EChartOption={ tooltip: { trigger: 'axis', //坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用 backgroundColor: '#fff', padding: 12, textStyle: { color: '#666666' }, transitionDuration: 0, confine: true, extraCssText: 'border-radius: 3px;box-shadow: 0 0 3px #E9EBF1;width:76px;' }, xAxis: { type: 'category', axisLine: { lineStyle: { color: '#EFEFF4' } }, axisLabel: { color: '#999' }, axisTick: { alignWithLabel: true, show: false }, splitLine: { //x轴分割线 show: true, lineStyle: { color: [ '#EFEFF4' ] //分割线颜色 } }, }, yAxis: { type: 'value', axisLine: { lineStyle: { color: '#EFEFF4' } }, axisLabel: { color: '#999', margin: 5 }, axisTick: { alignWithLabel: true, show: false }, splitLine: { //x轴分割线 show: true, lineStyle: { color: [ '#EFEFF4' ] //分割线颜色 } }, splitNumber: 5 }, grid: { // show:true, top: 7, right: 5, bottom: 20 }, series: [ { type: 'line', smooth: true } ] } /** * @description createEchart 画图 * @param { any } arg * @returns { void } */ createEchart(this: EoFinanceEcharts,...arg:any) { if ( this.instance !== null && this.instance !== undefined ) { this.instance.dispose(); //销毁实例 } this.instance = echarts.init(this.$refs.chart as HTMLCanvasElement); //创建实例 this.instance.setOption(this.chartBaseConfig); //基本配置 //X轴 this.instance.setOption({ xAxis: [ { data: arg[0].xAxis } ] }); //数据 this.instance.setOption({ series: [ { data: arg[0].series } ] }); } createStateCtx() { let currentState: any = {}; let action: any = {}; const Ctx = { changeState: function (...arg: any) { currentState={}; arg.map((item: any) => { currentState[item] = true; }); return this; }, action: function () { //遍历参数对象,不含继承 Object.keys(currentState).forEach((k) => action[k] && action[k].call(this, this)); return this; }, setState: function (setAction: any) { action = setAction; return this; } }; return Ctx; } chartAction = { domestic: () => { console.log('国内domestic'); this.createEchart({xAxis:[ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],series:[ 820, 932, 901, 934, 1290, 1330, 1320 ]}); }, abroad: () => { console.log('国外abroad'); this.createEchart({xAxis:[ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ],series:[ 800, 732, 901, 932, 1090, 1430, 1320 ]}); } }; /** * operCtx 操作上下文 * @param { any } strategyObj 策略对象 * @param { string } param * @param { any } arg * @return { void } */ operCtx(strategyObj: any, param: string, ...arg: any): void { strategyObj[param].apply(this, arg); } //tabStrategy tab策略方法 tabStrategy = { domestic: function (this:EoFinanceEcharts) { if(this.curIndex===0)return; console.log('国内'); this.curIndex=0; this.banner='国内本周融资:4家 | 金额:2.76亿人民币'; this.stateCtx?.changeState('domestic').action(); }, abroad: function (this:EoFinanceEcharts) { if(this.curIndex===1)return; console.log('国外'); this.curIndex=1; this.banner='国外本周融资:5家 | 金额:5.76亿人民币'; this.stateCtx?.changeState('abroad').action(); } }; mounted() { this.stateCtx = this.createStateCtx(); this.stateCtx.setState(this.chartAction)?.changeState('domestic').action(); } destroyed() { this.stateCtx = null; } } </script> <style lang="less" scoped> .eo-finance-echarts { // border: 1px solid red; width: 296px; height: 380px; padding: 16px; background-color: #fff; } .eofe-tab-title { color: #333; font-size: 18px; cursor: pointer; height: 33px; position: relative; &::before { content: ''; width: 100%; border-bottom: 1px dotted #333333; position: absolute; bottom: 0; } } .title-item-active { &::before { content: ''; width: 38px; height: 4px; position: absolute; bottom: 0; left: 0 !important; background-color: #333333; } } .eofe-tab-title-item { margin-right: 14px; font-weight: bold; overflow: hidden; position: relative; &:last-of-type { margin-right: 0; } &::before { content: ''; width: 38px; height: 4px; position: absolute; bottom: 0; left: -100%; background-color: #333333; transition: all 0.2s; } &:hover { &::before { left: 0; } } } .eofe-tab-content { margin-top: 16px; } .eofe-tab-content-item { // border: 1px solid lightblue; position: relative; .banner { width: 264px; height: 33px; line-height: 33px; text-align: center; color: #0086f0; background-color: rgba(0, 134, 240, 0.08); font-size: 12px; } .chart { // border: 1px solid lawngreen; height: 250px; margin-top: 16px; } } // .tip-outer{ // border-radius: 3px; // background-color: rgba(255, 255, 255, 1); // border: 0.79px solid red; // } </style>
作者:k_
链接:https://juejin.cn/post/7027753086827364360