Vue项目之ES6装饰器在项目实战中的应用

目录
  • 前言
  • 装饰模式(Decorator)
  • ES6 装饰器
  • 装饰器应用
    • Validate
    • CatchError
    • Confirmation
  • 总结
  • 参考

前言

在面向对象(OOP)的设计模式中,装饰器的应用非常多,比如在 Java 和 Python 中,都有非常多的应用。ES6 也新增了装饰器的功能,本文会介绍 ES6 的装饰器的概念、作用以及在 Vue + ElementUI 的项目实战中的应用。

装饰模式(Decorator)

装饰模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。 这种模式属于结构型模式,它是作为现有的类的一个包装。 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

优点:

  • 不需要通过创建子类的方式去拓展功能(不需要子类化),这样可以避免代码臃肿的问题
  • 装饰类的方法复用性很高
  • 不会影响到原对象的代码结构

ES6 也开始有了装饰器,写法与其他语言的写法保持了统一,就是使用@ + 函数名的方式

ES6 装饰器

关于ES6 装饰器的用法可以参考阮老师的 ECMAScript 6 入门,这里从中展示一下两种用法。

  • 类的装饰

装饰器方法:给对象添加一个 isTestable 属性

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

使用的时候直接用 @ + 函数名,就可以为对象添加 isTestable 属性了

@testable(true)
class MyTestableClass {}

MyTestableClass.isTestable // true
  • 方法的装饰

日志装饰器

function log(target, name, descriptor) {
  var oldValue = descriptor.value;

  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };
  return descriptor;
}

使用装饰器

class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

const math = new Math();

// Calling add with arguments
math.add(2, 4);

从上面两个简单例子可以看出,装饰器应用在类和类的方法上时非常的方便,有几个优点:

  • 语义化,可以非常清晰看出装饰器为对象添加了什么功能
  • 装饰器不改变原对象的结构,原对象代码简洁、易维护。

接下来将介绍一下我在 Vue 项目中,利用装饰器的功能做的代码优化。

装饰器应用

目前我们了解到,装饰器可以用来注释或修改类和类方法。而且装饰器使用起来非常灵活,只需要用@ + 函数名就可以修改类,可以改善代码结构。那么在做项目的时候,编写代码时是否有些功能可以抽象成装饰器,提高复用性和改善代码结构。

下面的例子所用到的技术栈是 Vue2 + ElementUI + TypeScript + vue-property-decorator

Validate

在很多 UI 组件库中,都有表单组件,其中表单重要的功能之一就是表单校验,以 ElementUI 的 form 举例,首先校验表单是否通过,如果通过,就将表单数据提交给后台,

完整的代码如下:

submitForm() {
  this.$refs['formName'].validate(async (valid) => {
    if (valid) {
      try {
        // 调用接口
        await this.handleTest();
        this.$message.success('Submit Successfully!')
      } catch(error) {
        console.log(error);
      }
    } else {
      console.log('error submit!!');
      return false;
    }
  });
},

这里有几个问题:

  • 这个代码嵌套到第三层才开始进入主逻辑代码,嵌套太多了,万一在主要业务逻辑代码还有很多嵌套,看起来就十分的难受。
  • 记不住,在实际开发中,一般不回特意去记触发校验的写法,通常要去找文档或者找别人的代码,最后抄过来
  • 此功能很常用,每做一个表单都要写一遍,重复写这份代码

分析上面代码,其实主要的代码是在 if (valid) 的条件下,而触发表单校验的代码是可以抽象出来的,因为它非常常用,而且这部分代码是无关业务逻辑的。抽象出去,可以更好地关注到业务逻辑代码。

export function Validate(refName: string) {
  return function (target: any, name: string, descriptor: PropertyDescriptor) {
    const fn = target[name]; // 被装饰的方法
    descriptor.value = function (...args: any[]) {
      // 将触发校验的代码封装在此
      (this as any).$refs[refName].validate((valid: boolean) => {
        if (valid) {
          fn.call(this, ...args); // 在这里调用“被装饰的方法”
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    };
  };
}

然后在使用的时候就非常简单了,只需要在提交方法上方写上 @Validate('refName'),传入表单组件的 ref 名,就可以实现了触发表单校验的功能,这样不但大大优化了代码结构,而且使用起来非常简单, 再也不用担心我记不住怎么写了。

import { Validate } from '@/utils/decorator'
export default class TestForm extends Vue {

  @Validate('formName')
  async submitForm() {
    try {
      // 调用接口
      await this.handleTest();
      this.$message.success('Submit Successfully!')
    } catch(error) {
      console.log(error);
    }
  }
}

这样是不是好多了!特别是在业务逻辑非常复杂的场景,减少嵌套和非业务逻辑的代码,可以让业务逻辑代码更加清晰。

CatchError

在写代码的时候经常用 try catch 去捕获程序中的错误,但是 try catch 会加深了代码嵌套层级,而且很常用,我们可以将 try catch 的部分抽象出去,作为装饰器的功能。

比如原来的代码是这样的:

export default class TestForm extends Vue {

  async submitForm() {
    try {
      await this.handleTest();
      this.$message.success('Submit Successfully!')
    } catch(error) {
      console.log(error);
    }
  }
}

try catch 的功能作为装饰函数

export function CatchError() {
  return function (target: any, name: string, descriptor: PropertyDescriptor) {
    const fn = target[name];
    descriptor.value = async function (...args: any[]) {
      try {
        await fn.call(this, ...args);
      } catch (error) {
        console.log('error', error);
      }
    };
    return descriptor;
  };
}

使用起来后,就少了一层 try catch 的嵌套了,而且错误也被捕获到了,CatchError 的命名也很好理解,并且你可以统一处理捕获到的错误。

import { CatchError } from '@/utils/decorator'
export default class TestForm extends Vue {
  @CatchError()
  async submitForm() {
    await this.handleTest();
    this.$message.success('Submit Successfully!')
  }
}

现在目前有 Validate 和 CatchError 两种装饰器,分别是表单校验和错误捕捉的作用,而表单提交都有用到这两种功能,装饰器可以同时满足它,因为一个方法可以拥有多个装饰器。

如果同一个方法有多个装饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。

那么提交表单的函数最终可以被装饰器优化成这样:

import { CatchError, Validate } from '@/utils/decorator'
export default class TestForm extends Vue {
  @CatchError()
  @Validate('ruleForm')
  async submitForm() {
    await this.handleTest();
    this.$message.success('Submit Successfully!')
  }
}

发现了没有,提交表单的代码中,完完全全只有业务逻辑代码了!而其他的功能作为装饰器引入并作用到这个方法上。而且这些装饰功能就像是个语法糖一样,当我下次还需要用到的时候,只需要引用在我的方法上即可,十分方便。

Confirmation

确认消息:提示用户确认其已经触发的动作,并询问是否进行此操作时会用到此对话框。这种场景十分常见,在点击提交表单确认、点击删除的时候,都会弹出提示框,在用户点击确认后,再提交。其中最终我们只需要点击确认那一下按钮提交的功能,其他的功能属于交互功能。

代码实现:

<template>
  <div>
    <el-button type="text" @click="handleDelete"
      >点击打开 Message Box 提示是否删除</el-button
    >
  </div>
</template>

<script>
import { Vue, Component } from "vue-property-decorator";
@Component
export default class DecoratorTest extends Vue {
  handleDelete() {
    this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
      showCancelButton: true,
      beforeClose: (action, instance, done) => {
        if (action === "confirm") {
          instance.confirmButtonLoading = true;
          setTimeout(() => {
            done();
            setTimeout(() => {
              instance.confirmButtonLoading = false;
            }, 300);
          }, 2000);
        } else {
          done();
        }
      },
    }).then(() => {
      this.$message({
        type: "success",
        message: "删除成功!",
      });
    });
  }
}
</script>

同样的问题,实现这样一个通用的功能,需要太多与业务逻辑无关的代码了。代码嵌套很深,主要业务逻辑代码不够清晰可见。因此对于这种通用的功能,也可以抽离出去作为装饰器。

同样我们把 confirm 的功能封装起来,instance.confirmButtonLoading 控制的是按钮的 loading,done() 是关闭弹窗的方法,这两个功能很好用,因此我们把 instancedone 作为参数传给被装饰的方法。

import Vue from "vue";
interface ConfirmationConfig {
  title: string;
  message: string;
  // eslint-disable-next-line @typescript-eslint/ban-types
  options?: object;
  type?: string;
}
export function Confirmation(config: ConfirmationConfig) {
  return function (target: any, name: string, descriptor: PropertyDescriptor) {
    const fn = target[name];
    let _instance: any = null;
    descriptor.value = function (...args: any[]) {
      Vue.prototype
        .$confirm(
          config.message,
          config.title,
          Object.assign(
            {
              beforeClose: (action: string, instance: any, done: any) => {
                _instance = instance;
                if (action === "confirm") {
                  instance.confirmButtonLoading = true;
                  fn.call(this, instance, done, ...args);
                } else {
                  done();
                }
              },
            },
            config.options || {}
          )
        )
        .then(() => {
          _instance.confirmButtonLoading = false;
        });
    };
    return descriptor;
  };
}

完成封装 confirm 之后,这么使用即可:

<template>
  <div>
    <el-button type="text" @click="handleDelete"
      >点击打开 Message Box 提示是否删除</el-button
    >
  </div>
</template>

<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Confirmation } from "@/utils/decorator";
@Component
export default class DecoratorTest extends Vue {
  @Confirmation({
    title: "提示",
    message: "此操作将永久删除该文件, 是否继续?",
  })
  handleDelete(instance: any, done: any) {
    setTimeout(() => {
      done();
      setTimeout(() => {
        instance.confirmButtonLoading = false;
        this.$message({
          type: "success",
          message: "删除成功!",
        });
      }, 300);
    }, 2000);
  }
}
</script>

最终这样减少了很多代码和嵌套,并且将这个常用的功能封装起来了,以后遇到可以直接复用起来,使用也很方便,只需要引入并传入 title 和 message 就可以了。

总结

装饰器可用于给类和类的方法添加功能,且不会影响原对象的结构。可用于拓展原对象的功能。在实际业务项目开发中,常常会把功能性代码和业务性代码耦合在一起,可以将功能性代码抽象出去,作为装饰器装饰业务功能代码,这样就能专注于业务组件的业务逻辑代码,优化代码结构,减少代码嵌套等。

到此这篇关于Vue项目之ES6装饰器在项目实战中应用的文章就介绍到这了,更多相关Vue ES6装饰器在实战应用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

代码示例已经发布到 github 上了代码地址 ,也可以把项目来下来跑跑看。

参考

ECMAScript 6 入门

(0)

相关推荐

  • Vue中使用装饰器的方法详解

    目录 前言 什么是装饰器? 装饰器的使用 js中使用装饰器 不使用装饰器 vue 中使用装饰器 一些常用的装饰器 1. 函数节流与防抖 2. loading 3. 确认框 总结 前言 相信各位在开发中一定遇到过二次弹框确认相关的需求.不管你使用的是UI框架的二次弹框组件,还是自己封装的弹框组件.都避免不了在多次使用时出现大量重复代码的问题.这些代码的积累导致项目的可读性差.项目的代码质量也变得很差.那么我们如何解决二次弹框代码重复的问题呢?使用装饰器 什么是装饰器? Decorator是ES7的

  • Vue项目之ES6装饰器在项目实战中的应用

    目录 前言 装饰模式(Decorator) ES6 装饰器 装饰器应用 Validate CatchError Confirmation 总结 参考 前言 在面向对象(OOP)的设计模式中,装饰器的应用非常多,比如在 Java 和 Python 中,都有非常多的应用.ES6 也新增了装饰器的功能,本文会介绍 ES6 的装饰器的概念.作用以及在 Vue + ElementUI 的项目实战中的应用. 装饰模式(Decorator) 装饰模式(Decorator Pattern)允许向一个现有的对象添

  • 实现一个VUE响应式属性装饰器详析

    目录 前言 不使用任何的响应Api 使用 reactive 实现 使用 ref 实现 使用装饰器实现 实现Reactive装饰器 实现Watch装饰器 总结 前言 使用面向对象的开发思想难免会用到类,既然有了类,那就应该有实例,然而我们使用类的时候可能需要实例中的某个属性是vue的响应属性,也可能里面的某个方法也可以被vue的watch监听.我就开始琢磨如何通过 Composition API 来实现这个类属性装饰器 不使用任何的响应Api // TestReactive.ts export c

  • Python类中的装饰器在当前类中的声明与调用详解

    我的Python环境:3.7 在Python类里声明一个装饰器,并在这个类里调用这个装饰器. 代码如下: class Test(): xx = False def __init__(self): pass def test(func): def wrapper(self, *args, **kwargs): print(self.xx) return func(self, *args, **kwargs) return wrapper @test def test_a(self,a,b): pr

  • 利用Python的装饰器解决Bottle框架中用户验证问题

    首先来分析下需求,web程序后台需要认证,后台页面包含多个页面,最普通的方法就是为每个url添加认证,但是这样就需要每个每个绑定url的后台函数都需要添加类似或者相同的代码,但是这样做代码就过度冗余,而且不利于扩展. 接下来我们先不谈及装饰器,我们都知道Python是个很强大的语言,她可以将函数当做参数传递给函数,最简单的: def p(): print 'Hello,world' def funcfactor(func): print 'calling function named', fun

  • PHP设计模式之装饰器模式定义与用法详解

    本文实例讲述了PHP设计模式之装饰器模式定义与用法.分享给大家供大家参考,具体如下: 什么是装饰器模式 作为一种结构型模式, 装饰器(Decorator)模式就是对一个已有结构增加"装饰". 适配器模式, 是为现在有结构增加的是一个适配器类,.将一个类的接口,转换成客户期望的另外一个接口.适配器让原本接口不兼容的类可以很好的合作. 装饰器模式是将一个对象包装起来以增强新的行为和责任.装饰器也称为包装器(类似于适配器) 有些设计设计模式包含一个抽象类,而且该抽象类还继承了另一个抽象类,这

  • 详解利用装饰器扩展Python计时器

    目录 介绍 理解 Python 中的装饰器 创建 Python 定时器装饰器 使用 Python 定时器装饰器 Python 计时器代码 其他 Python 定时器函数 使用替代 Python 计时器函数 估计运行时间timeit 使用 Profiler 查找代码中的Bottlenecks 总结 介绍 在本文中,云朵君将和大家一起了解装饰器的工作原理,如何将我们之前定义的定时器类 Timer 扩展为装饰器,以及如何简化计时功能.最后对 Python 定时器系列文章做个小结. 这是我们手把手教你实

  • 详解Python中的装饰器、闭包和functools的教程

    装饰器(Decorators) 装饰器是这样一种设计模式:如果一个类希望添加其他类的一些功能,而不希望通过继承或是直接修改源代码实现,那么可以使用装饰器模式.简单来说Python中的装饰器就是指某些函数或其他可调用对象,以函数或类作为可选输入参数,然后返回函数或类的形式.通过这个在Python2.6版本中被新加入的特性可以用来实现装饰器设计模式. 顺便提一句,在继续阅读之前,如果你对Python中的闭包(Closure)概念不清楚,请查看本文结尾后的附录,如果没有闭包的相关概念,很难恰当的理解P

  • Python 装饰器实现DRY(不重复代码)原则

    Python装饰器是一个消除冗余的强大工具.随着将功能模块化为大小合适的方法,即使是最复杂的工作流,装饰器也能使它变成简洁的功能. 例如让我们看看Django web框架,该框架处理请求的方法接收一个方法对象,返回一个响应对象: def handle_request(request): return HttpResponse("Hello, World") 我最近遇到一个案例,需要编写几个满足下述条件的api方法: 返回json响应 如果是GET请求,那么返回错误码 做为一个注册api

  • Python装饰器(decorator)定义与用法详解

    本文实例讲述了Python装饰器(decorator)定义与用法.分享给大家供大家参考,具体如下: 什么是装饰器(decorator) 简单来说,可以把装饰器理解为一个包装函数的函数,它一般将传入的函数或者是类做一定的处理,返回修改之后的对象.所以,我们能够在不修改原函数的基础上,在执行原函数前后执行别的代码.比较常用的场景有日志插入,事务处理等. 装饰器 最简单的函数,返回两个数的和 def calc_add(a, b): return a + b calc_add(1, 2) 但是现在又有新

随机推荐