装饰器使用小栗子

96 阅读1分钟

装饰器定义函数

decorate.js

function isDescriptor (desc) {
  if (!desc || !desc.hasOwnProperty) {
    return false;
  }
  const keys = ['value', 'initializer', 'get', 'set'];
  for (let i = 0, l = keys.length; i < l; i++) {
    if (desc.hasOwnProperty(keys[i])) {
      return true;
    }
  }

  return false;
}

export const decorate = function (handleDescriptor) {
  return function (...entryArgs) {
    if (isDescriptor(entryArgs[entryArgs.length - 1])) {
      return handleDescriptor(...entryArgs);
    } else {
      return function () {
        handleDescriptor(...arguments, ...entryArgs);
      };
    }
  };
};

export const runBefore = decorate(function (target, name, descriptor, handle) {
  const raw = descriptor.value;
  descriptor.value = async function () {
    try {
      let flag = await target[handle](arguments);
      return await raw.apply(target, [flag, ...arguments]);
    } catch (err) {
      console.log(err);
    }
  };
});

export const runAfter = decorate(function (target, name, descriptor, handle) {
  const raw = descriptor.value;
  descriptor.value = async function () {
    try {
      let currentReturn =  await raw.apply(target, arguments);
      return await target[handle]([currentReturn, ...arguments]);
    } finally {
      // target[handle]();
    }
  };
});

装饰器使用例子

前端eslint配置要求使用驼峰,而后端java同学采用下划线来定义对象,同时采用下划线返回。
api.js 用来配置 ajax 接口, 发送前,将驼峰转小写;回来后,将小写转驼峰。
其实这里,类似于pipe管道模式

import {runAfter, runBefore} from './decorate';

type SubjectQueryModel = {
    pageNum: number;
    pageSize: number;
    keyword?: string;
}

export class MyApi{
  private httpClient: IHttpClient;

  constructor(httpClient: IHttpClient, authToken?: string) {
    if (authToken) {
      httpClient.setAuthToken(authToken);
    }
    this.httpClient = httpClient;
  }
  
   @runBefore('toUnderScore')
  @runAfter('toCamelcase')
  public async getSubjectList(condition: SubjectQueryModel, ...args: any[]): Promise<SubjectModel[]> {
    console.log('current arguments', args);
    const currentParams = args[0];
    console.log('currentParams', currentParams);

    return {
      total: 0,
      records: [],
      size: 10,
      department_ids: [1, 2, 3],
    };
    // return this.httpClient.restPost<SubjectQueryModel, SubjectModel[]>('/subjects/listfororg', condition, 3, 10000);
  }
  
  // 装饰器 下划线 转驼峰
  public toCamelcase () {
    // toHump
    console.log('after arguments', arguments);
    let res = arguments[0][0];

    return this.convertObjectKeysToCamelcase(res);
  }
  // 装饰器 驼峰转 下划线
  public toUnderScore () {
    console.log('before arguments', arguments);
    let params = arguments[0][0];

    let newParam = this.convertObjectKeysToUnderscore(params);
    console.log('newParams', newParam);

    return newParam;
  }

  // 实际驼峰转 下划线
  public convertObjectKeysToUnderscore(obj: any) {
    const result = {};
    for (const [key, value] of Object.entries(obj)) {
      let newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
      if (typeof value === 'object' && value !== null) {
        result[newKey] = this.convertObjectKeysToUnderscore(value);
      } else {
        result[newKey] = value;
      }
    }

    return result;
  }

  // 实际下划线 转 驼峰
  public convertObjectKeysToCamelcase(obj: any) {
    const result = {};
    for (const [ key, value ] of Object.entries(obj)) {
      let newKey = key.replace(/[_][a-z]/g, (match) => match[1].toUpperCase());
      if (typeof value === 'object' && value !== null) {
        result[newKey] = this.convertObjectKeysToCamelcase(value);
      } else {
        result[newKey] = value;
      }
    }

    return result;
  }
}

结果: 使用时参数传入

    {
        pageNum: 1,         // 实际发送时  pageNum =>  page_num
        pageSize: 10,       // pageSize => page_size
    }

调用返回后

    {
        total: 100,
        size: 10,
        records: subjectModel[],
        departmentIds: [],     // 这里将 ajax返回后,装饰器将department_ids  => departmentIds
    }

类似的做法,可以做日志,错误监听;

log.js

import {decorate} from './decorate.js'; 
export const log =  decorate(function (target, name, descriptor) {
  const raw = descriptor.value
  descriptor.value = async function () {
    console.log(`[log]-${ target.constructor.name }.${ name }:input`, ...arguments)
    const ret = await raw.apply(this, arguments)
    console.log(`[log]-${ target.constructor.name }.${ name }:output`, ret)
    return ret
  }
})

handleError.js

import {decorate} from './decorate.js';
export const handleError = decorate(function (target, name, descriptor, {success, error} = {}) {
  const raw = descriptor.value
  descriptor.value = async function () {
    try {
      const ret = await raw.apply(this, arguments)
      return ret
    } catch (err) {
      // 调用 logger.error(err)
      return null/err
    }
  }
})

pipe.js


export default decorate(function (target, name, descriptor, pipes = []) {
  pipes.reverse().forEach(handle => handle(target, name, descriptor))
  const raw = descriptor.value
  descriptor.value = function () {
    return new Promise(async (resolve, reject) => {
      try {
        resolve(await raw.apply(this, arguments))
      } catch (err) {
        reject(err)
      }
    })
  }
})