zoukankan      html  css  js  c++  java
  • 设计模式(6): 数据抽象与业务封装

    概述

    最近最近做项目的时候总会思考一些大的应用设计模式相关的问题,我把自己的思考记录下来,供以后开发时参考,相信对其他人也有用。

    情景描述

    我们在做项目的时候,经常会碰到各种各样的业务情景,然后为了实现这些需求,就不断地在 vue 单文件组件里面加代码来实现,最终业务越来越多单文件组件越来越大,非常难以维护。

    解决方案

    我们都知道,vue 是通过数据来处理视图的,所以很多业务可以抽象成只处理数据,然后这些业务可以再抽象成 class 来进行业务封装。

    event-bus

    举个例子来说,vuex 或者 redux 这些状态管理的库,就是用的这个思想,把数据层脱离出去,带来的好处是简化了组件之间的数据流动。它们的源码有些复杂,我们以 event-bus 来举例说明。

    首先,我们可以自己实现一个 bus 类,这个类能够储存数据,还能够进行事件的分发与监听

    import Vue from 'vue';
    import Bus from 'xxxx';
    
    Vue.prototype.$bus = new Bus();
    

    然后,分别在组件 A 和 B 里面,我们可以监听事件和分发事件。

    // 组件A -- 监听事件
    created() {
      this.$bus.on('xxxx', this.xxx);
    },
    beforeDestroy() {
      this.$bus.off('xxxx', this.xxx);
    },
    
    // 组件B -- 分发事件
    methods: {
      xxxx() {
        this.$bus.emit('xxxx', this.xxx);
      }
    }
    

    这样,即使处于不同层级,组件 A 和 B 也能流畅的进行数据交互。

    抽象方法

    我们抽象一下实现方法,我们先把业务抽象为数据和对数据的操作,然后在组件之外实现一个 class,最后用这个 class 进行保存数据和业务处理

    上面这个例子把这个 class 放在了 Vue 实例上面,可能没有那么明显,下面举一个把它放在单文件组件里面的例子。

    cascader

    这一段参考了 element-cascader 的实现。

    比如说,我们要自己实现一个 cascader,要怎么做?

    我们上面提到过,我们对 cascader 的操作其实就是对数据的操作,所以我们可以把整个数据抽象出来,然后给它加上选中的业务功能:

    import { capitalize } from '@/utils/util';
    
    export default class Node {
      constructor(data, parentNode) {
        this.parent = parentNode || null;
    
        this.initState(data);
        this.initChildren(data);
      }
    
      initState(data) {
        // 加上本身的属性
        for (let key in data) {
          if (key !== 'children') {
            this[key] = data[key];
          }
        }
    
        // 自定义属性
        this.isChecked = false;
        this.indeterminate = false;
    
        // 用于自动取消
        this.isCheckedCached = false;
        this.indeterminateCached = false;
      }
    
      initChildren(data) {
        this.children = (data.children || []).map(child => new Node(child, this));
      }
    
      setCheckState(isChecked) {
        const totalNum = this.children.length;
        const checkedNum = this.children.reduce((c, p) => {
          const num = p.isChecked ? 1 : (p.indeterminate ? 0.5 : 0);
          return c + num;
        }, 0);
    
        this.isChecked = isChecked;
        this.indeterminate = checkedNum !== totalNum && checkedNum > 0;
      }
    
      doCheck(isChecked) {
        this.broadcast('check', isChecked);
        this.setCheckState(isChecked);
        this.emit('check', isChecked);
      }
    
      broadcast(event, ...args) {
        const handlerName = `onParent${capitalize(event)}`;
    
        this.children.forEach(child => {
          if (child) {
            child.broadcast(event, ...args);
            child[handlerName] && child[handlerName](...args);
          }
        });
      }
    
      emit(event, ...args) {
        const { parent } = this;
        const handlerName = `onChild${capitalize(event)}`;
    
        if (parent) {
          parent[handlerName] && parent[handlerName](...args);
          parent.emit(event, ...args);
        }
      }
    
      onParentCheck(isChecked) {
        if (!this.disabled) {
          this.setCheckState(isChecked);
        }
      }
    
      onChildCheck() {
        const validChildren = this.children.filter(child => !child.disabled);
        const isChecked = validChildren.length
          ? validChildren.every(child => child.isChecked)
          : false;
    
        this.setCheckState(isChecked);
      }
    }
    

    上面实现的 class 封装了如下业务:

    1. 通过 initState 加入了各种自定义的状态,这个状态有了业务:选中状态,半选中状态和未选中状态
    2. 通过 setCheckState 实现了 点击 的业务。
    3. 通过 broadcast 和 emit 实现了 父子组件联动 的业务。

    当然,实际情形可能比这个更加复杂,我们只需要在上面的代码中加入各种状态和处理方法即可。

    更进一步

    上面封装的底层的业务,再高一层,我们可能有 搜索、自动选中 等业务,这个时候要怎么办呢?

    方法是在 Node 类和单文件组件之间再封装一层,来实现这些业务,示例代码如下:

    export default class Store {
      constructor(data) {
        this.nodes = data.map(nodeData => new Node(nodeData));
      }
    
      // 自动选中
      autoSelect(query, label) {
    
      }
    
      // 搜索
      search(searchString) {
    
      }
    }
    

    然后我们可以在单文件组件里面直接使用它

    data() {
      return {
        store: null;
      };
    },
    watch: {
      data(newVal) {
        this.store = new Store(newVal);
      }
    },
    
  • 相关阅读:
    Git—分支管理
    Git—推送代码至Github
    Git入门—创建项目
    Mysql单表查询
    Mysql数据的增删改查
    Mysql完整约束性
    C++继承中同名成员变量处理方法
    C++继承和组合中的构造函数和析构函数调用原则
    C++继承中的类型兼容原则
    C++中的继承
  • 原文地址:https://www.cnblogs.com/yangzhou33/p/11762196.html
Copyright © 2011-2022 走看看