资讯专栏INFORMATION COLUMN

在 Web 应用中使用 ES7 装饰器(Decorator)初体验

ivan_qhz / 2761人阅读

摘要:前言今天闲来时看了看中的新标准之一,装饰器。过程中忽觉它和中的注解有一些类似之处,并且当前版本的中已经支持它了,所以,就动手在一个应用中尝鲜初体验了一番。另外,由于装饰器目前还是中的一个提案,其中具体细节可能还会更改。

前言

今天闲来时看了看ES7中的新标准之一,装饰器(Decorator)。过程中忽觉它和Java中的注解有一些类似之处,并且当前版本的TypeScript中已经支持它了,所以,就动手在一个Web应用Demo中尝鲜初体验了一番。

(装饰器中文简介:这里)

最终效果:

// UserController.ts
"use strict"
import {router, log, validateQuery} from "./decorators"
import {IContext} from "koa"

export class UserController {
  @router({
    method: "get",
    path: "/user/login"
  })
  @validateQuery("username", "string")
  @log
  async login(ctx: IContext, next: Function) {
    ctx.body = "login!"
  }

  @router({
    method: "get",
    path: "/user/logout"
  })
  async logout(ctx: IContext, next: Function) {
    ctx.body = "logout!"
  }
}
实现 router装饰器 包个外壳

由于我们需要一个集中存储和挂载这些被装饰的路由的地方。所以,我们先来给koa包个外壳:

// Cover.ts
"use strict"
import * as Koa from "koa"
const router = require("koa-router")()

class Cover {
  static __DecoratedRouters: Map<{target: any, method: string, path: string}, Function | Function[]> = new Map()
  private router: any
  private app: Koa

  constructor() {
    this.app = new Koa()
    this.router = router
    this.app.on("error", (err) => {
      if (err.status && err.status < 500) return
      console.error(err)
    })
  }

  registerRouters() {
    // ...
  }

  listen(port: number) {
    this.app.listen(port)
  }
}

export default Cover

其中,__DecoratedRouters是我们存储被修饰后的路由的地方,而registerRouters则是真实挂载它们的方法。

实现装饰器

现在实现下装饰器,来把路由信息和处理函数保存起来:

// decorators.ts
"use strict"
import Cover from "./Cover"
import {IContext} from "koa"

export function router (config: {path: string, method: string}) {
  return (target: any, name: string, value: PropertyDescriptor) => {
    Cover.__DecoratedRouters({
      target: target,
      path: config.path,
      method: config.method
    }, target[name])
  }
}

感觉TypeScript中的类型已经把代码解释得差不多了...

挂载

最后实现一下把所有存起来的路由挂载上的方法,就大功告成了:

// Cover.ts
"use strict"
import * as Koa from "koa"
const router = require("koa-router")()

class Cover {
  // ...

  registerRouters() {
    for (let [config, controller] of Cover.__DecoratedRouters) {
      let controllers = Array.isArray(controller) ? controller : [controller]
      controllers.forEach((controller) => this.router[config.method](config.path, controller))
    }
    this.app.use(this.router.routes())
    this.app.use(this.router.allowedMethods())
  }

  // ...
}

export default Cover

// UserController.ts
"use strict"
import {router} from "./decorators"
import {IContext} from "koa"

export class UserController {
  @router({
    method: "get",
    path: "/user/login"
  })
  async login(ctx: IContext, next: Function) {
    ctx.body = "login!"
  }

  @router({
    method: "get",
    path: "/user/logout"
  })
  async logout(ctx: IContext, next: Function) {
    ctx.body = "logout!"
  }
}

用起来:

// app.ts
"use strict"
import Cover from "./Cover"
export * from "./UserController"

const app = new Cover()
app.registerRouters()

app.listen(3000)

写第三行代码:export * from "./UserController" 的意图为空执行一下该模块内的代码(可否有更优雅的办法?)。

普通的koa中间件装饰器

普通的koa中间件装饰器则更为简单,不需额外的存储挂载过程,直接定义就好,以下为两个简单的中间件装饰器:

// decorators.ts
"use strict"
import Cover from "./Cover"
import {IContext} from "koa"

export function validateQuery (name, type) {
  return (target: any, name: string, value: PropertyDescriptor) => {
    if (!Array.isArray(target[name])) target[name] = [target[name]]
    target[name].splice(target[name].length - 1, 0, validate)
  }

  async function validate (ctx: IContext, next: Function) {
    if (typeof ctx.query[name] !== type) ctx.throw(400, `${name}"s type should be ${type}"`)
    await next()
  }
}

export function log (target: any, name: string, value: PropertyDescriptor) {
  if (!Array.isArray(target[name])) target[name] = [target[name]]
  target[name].splice(target[name].length - 1, 0, middleware)

  async function middleware (ctx: IContext, next: Function) {
    let start = Date.now()
    ctx.state.log = {
      path: ctx.path
    }

    try {
      await next()
    } catch (err) {
      if (err.status && err.status < 500) {
        Object.assign(ctx.state.log, {
          time: Date.now() - start,
          status: err.status,
          message: err.message
        })
        console.log(ctx.state.log)
      }
      throw err
    }

    let onEnd = done.bind(ctx)

    ctx.res.once("finish", onEnd)
    ctx.res.once("close", onEnd)

    function done () {
      ctx.res.removeListener("finish", onEnd)
      ctx.res.removeListener("close", onEnd)

      Object.assign(ctx.state.log, {
        time: Date.now() - start,
        status: ctx.status
      })
      console.log(ctx.state.log)
    }
  }
}

装饰上:

// UserController.ts
"use strict"
import {router, log, validateQuery} from "./decorators"
import {IContext} from "koa"

export class UserController {
  @router({
    method: "get",
    path: "/user/login"
  })
  @validateQuery("username", "string")
  @log
  async login(ctx: IContext, next: Function) {
    ctx.body = "login!"
  }

  // ...
}

一个需要注意的地方是,中间的经过顺序是由下至上的,故上面的例子中,会先进入log中间件,然后是validateQuery

最后

以上例子仅是初体验时写的Demo代码,部分地方可能略有粗糙。另外,由于装饰器目前还是ES7中的一个提案,其中具体细节可能还会更改。个人感觉来说,它的确可以帮助代码在某种程度上更为简洁清晰。不过,由于它可以通过target参数直接取得被修饰类本身,在TypeScript中可能还好,若在JavaScript里,如果大量混合使用各种第三方装饰器,一个类是否可能会被改的面目全非?最佳实践可能还有待大家的一同探索。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78540.html

相关文章

  • ES7-Decorator-装饰者模式

    摘要:装饰者要实现这些相同的方法继承自装饰器对象创建具体的装饰器,也是接收作对参数接下来我们要为每一个功能创建一个装饰者对象,重写父级方法,添加我们想要的功能。 装饰模式 仅仅包装现有的模块,使之 更加华丽 ,并不会影响原有接口的功能 —— 好比你给手机添加一个外壳罢了,并不影响手机原有的通话、充电等功能; 使用 ES7 的 decorator ES7 中增加了一个 decorator 属性...

    zhangxiangliang 评论0 收藏0
  • 5 分钟即可掌握的 JavaScript 装饰者模式与 AOP

    摘要:下装饰者的实现了解了装饰者模式和的概念之后,我们写一段能够兼容的代码来实现装饰者模式原函数拍照片定义函数装饰函数加滤镜用装饰函数装饰原函数这样我们就实现了抽离拍照与滤镜逻辑,如果以后需要自动上传功能,也可以通过函数来添加。 showImg(https://segmentfault.com/img/bVbueyz?w=852&h=356); 什么是装饰者模式 当我们拍了一张照片准备发朋友...

    chunquedong 评论0 收藏0
  • es6/es7Decorator装饰

    摘要:装饰器顾名思义就是装饰某种东西的方法,可以用来装饰属性变量函数类实例方法本质上是个函数。以符开头,函数名称自拟。爱吃苹果装饰器装饰类爱吃苹果结果是这个类本身就可以通过修改类的属性增加属性被装饰的对象可以使用多个装饰器。 @Decorator 装饰器是es7的语法,这个方法对于面向切面编程有了更好的诠释,在一些情境中可以使用,比如路人A的代码实现了一需求,路人B希望用A的方法来实现一个新...

    yanest 评论0 收藏0
  • MobX详解(二):ES7 装饰 decorator

    摘要:在学习装饰器语法之前,需要先温习一下的一些基础知识。函数最后必须返回。使用时也很简单,如下在方法前面加上,就是装饰器语法。装备了,攻击更强了。职业的基本攻击穿上了,移动速度更快了。 在学习ES7装饰器语法之前,需要先温习一下ES5的一些基础知识。 假设有对象如下:(便于理解) var person = { name: TOM } 在ES5中,对象中的每个属性都有一个特性值来描述...

    Keagan 评论0 收藏0

发表评论

0条评论

ivan_qhz

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<