注意:本篇博客不讲原理,如果看不懂请先补充策略模式、状态模式、闭包、惰性求值、函数式编程等相关内容
一、效果
二、为什么使用策略和状态模式
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>