在 Web 运用中运用 ES7 装潢器(Decorator)初体验

媒介

本日闲来时看了看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://segmentfault.com/a/1190000004357419
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞