策略和状态模式应用——tab切换时,图表内容变化

509 阅读2分钟

注意:本篇博客不讲原理,如果看不懂请先补充策略模式、状态模式、闭包、惰性求值、函数式编程等相关内容

一、效果

请添加图片描述

二、为什么使用策略和状态模式

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>

案例演示 策略和状态模式应用——tab切换时,图表内容变化