基于装饰器对通用表格的封装

在Vue中如何将通用的方法抽象出来进行封装,mixin是一种方式,但是mixin的缺点也很明显,关于mixin的缺点这里不做过多介绍,也可以通过这篇文章介绍的通过装饰器对通用列表的分装也是一种比较好的处理方式。

DataContext

先定义一个基础的数据上下文,作为对数据的操作逻辑的抽象,以达到对逻辑的复用。

import { getEvents } from './attachEvent'

class DataContext {
  @getEvents
  events
}

export { DataContext }

getEvents

export const getEvents = (target, key) => {
  return {
    get () {
      // target 指向 DataContext.prototype
      console.log('访问了key ===> ' + key)
      const cacheKey = `__${key}_events__`
      return this[cacheKey] ?? (this[cacheKey] = {})
    },
    set () {}
  }
}

对于装饰器不了解的这里不做过多的介绍,上述代码的意义是给 events 属性添加 get set 方法便于访问时会触发,此处的 target 指向的是实例的 __proto__

const data = new DataContext()
console.log('data.events ===> ', data.events)
console.log('data ===> ', data)

基于装饰器对通用表格的封装_第1张图片

再定义一个表格通用的类主要目的是为了将通用列表的基础通用逻辑进行封装

TableDataContext

import { DataContext } from './dataContext'
import { getEvents } from './attachEvent'
import { bind } from './bind'

class TableDataContext extends DataContext {
  @getEvents
  tableEvents

  constructor() {
    super()
  }

  @bind
  handleFieldWidthChanged() {
    console.log('change')
  }
}

export { TableDataContext }

bind

给类的成员方法绑定this。

const bind = (target, key, descriptor) => {
      return {
        configurable: true,
        get() {
          console.log('key ===> ', key)
          console.log('target ===> ', target)
          const boundFn = descriptor.value.bind(this)
          Object.defineProperty(this, key, {
            configurable: true,
            get() {
              return boundFn
            }
          })
          return boundFn
        }
      }
    }

调用  TableDataContext 查看 this上的属性和方法

const table = new TableDataContext()
console.log('table ===> ', table)
console.log(table.handleFieldWidthChanged())

基于装饰器对通用表格的封装_第2张图片

attachTableEvent

在 Vue 中基本上是通过 $emit 方法触发自定义事件的,但是在 .vue 文件中如何触发 .js 文件中定义的这些方法呢,这里对 TableDataContext 里面的 handleFieldWidthChanged 的方法进行改动,为方法绑定一个装饰器

import { attachTableEvent } from './attachEvent'

class TableDataContext extends DataContext {

  @bind
  @attachTableEvent('resize-change')
  handleFieldWidthChanged() {
    console.log('change')
  }
}

export { TableDataContext }

buildAttachEvent

给 tableEvents 用的预定义的 attachEvent

const attachTableEvent = buildAttachEvent('tableEvents')

const buildAttachEvent = propName => {
  console.log('propName ===> ', propName)
  return eventName => {
    console.log('eventName ===> ', eventName)
    return (target, key, descriptor) => {
      console.log('target ===> ', target)
      console.log('key ===> ', key)
      console.log('descriptor ===> ', descriptor)
      const privatePropName = `__${propName}__`
      if (!Object.hasOwnProperty.call(target, privatePropName)) {
        target[privatePropName] = { ...target[privatePropName] }
      }
      target[privatePropName][eventName] = key
      return descriptor
    }
  }
}

基于装饰器对通用表格的封装_第3张图片

 buildEventsObject

如何在访问 tableEvents 属性时拿到绑定在 __tableEvents__ 这个自定义属性上的方法呢,添加一个 buildEventsObject 装饰器,并对 getEvents 进行如下改造

export const getEvents = (target, key) => {
  return {
    get () {
      console.log('访问了key ===> ' + key)
      const cacheKey = `__${key}_events__`
      return this[cacheKey] ?? (this[cacheKey] = buildEventsObject(this, key))
    },
    set () {}
  }
}
const buildEventsObject = (instance, propName) => {
   console.log('instance ===> ', instance)
   console.log('propName ===> ', propName)
   const privatePropName = `__${propName}__`
   const eventsMap = instance[privatePropName] ?? {}
   console.log('eventsMap ==> ', eventsMap)
   return Object.keys(eventsMap).reduce((acc, key) => {
     acc[key] = instance[eventsMap[key]]
     return acc
   }, {})
}

const table = new TableDataContext()
console.log('table ===> ', table)
console.log('tableEvents ===> ', table.tableEvents)

基于装饰器对通用表格的封装_第4张图片

这里可以看到对同一个方法绑定多个装饰器执行的先后顺序。

  1. 通过 getEvents 方法对基础类里面的属性 tableEvents 绑定 get 方法。
  2. 通过 buildAttachEvent('tableEvents') 在为属性 tableEvents 预定义执行方法。
  3. 访问属性 tableEvents 时会触发 get 方法,通过 buildEventsObject 获取绑定到属性          tableEvents 上的方法。
  4. 通过 bing 装饰器修正调用时的 this 的指向。

接下来是对于基础列表的封装让我们定义的这些被装饰器修饰的方法可以在 .vue 文件里面被使用

ListTable



TableContextAdapter






BaseTable






如果多个方法 emit 需要触发同一个方法只需要进行如下改写即可

import { DataContext } from './dataContext'
import { attachTableEvent, getEvents, attachEvent } from './attachEvent'
import { bind } from './bind'

class TableDataContext extends DataContext {
  /**
   * 直接监听到 VapdTable 上的事件。
   */
  @getEvents
  tableEvents

  constructor() {
    super()
  }

  @bind
  @attachTableEvent('resize-change')
  handleFieldWidthChanged() {
    console.log('resize-change')
  }

  @bind
  @attachEvent('issue-delete')
  @attachEvent('issue-modify')
  @attachTableEvent('issue-change')
  handleIssueListChanged() {
    console.log('issue-change')
  }
}

export { TableDataContext }

你可能感兴趣的:(Vue,vue.js,javascript,装饰器模式)