TypeScript Decorator Practice

TypeScript Decorator Practice

5月 16, 2020 · 2 分钟阅读时长 · 303 字 · -阅读 -评论

Decorators are still an ES stage-2 proposal. In TypeScript/Babel projects we can opt in to experimental features, and frameworks like Angular make heavy use of them. I’d used decorators in Angular, but hadn’t explored them deeply—time to change that.

decorator illustration

What’s a Decorator?

Decorators “wrap” classes/members to add behavior without changing the original interface. Consumers shouldn’t notice a difference.

Types:

  • Class decorators
  • Method decorators
  • Accessor decorators
  • Property decorators
  • Parameter decorators
  • Metadata decorators

Key points:

  1. A decorator is just a function.
  2. In TS they only apply to classes and class elements—not enums, constants, or standalone functions.

Examples

Property decorator — tweak a property value:

function modifyProp(target: any, propertyKey: string) {
  target[propertyKey] = Math.random().toString();
}

Class decorator — modify every property:

function modifyProps(prefix: string) {
  return (constructor: any) => {
    Object.keys(constructor).forEach(
      (key) => (constructor[key] = prefix + key)
    );
    return constructor;
  };
}

Enabling Decorators

Using TypeScript + Babel:

yarn add @babel/plugin-proposal-decorators -D

tsconfig.json:

"experimentalDecorators": true,
"emitDecoratorMetadata": true

(Enable emitDecoratorMetadata if you rely on reflect-metadata.)

babel.config.js (put the plugin first):

plugins: [
  ['@babel/plugin-proposal-decorators', { legacy: true }]
]

Babel transpiles decorators to regular functions, so browser compatibility doesn’t worsen.

Practical Example

Our Redux action types were defined like this:

import { createActionPrefix } from 'redux-actions-helper';

const prefixCreator = createActionPrefix('USER');

export default class UserActionTypes {
  static INIT_USER = prefixCreator('INIT_USER');
  static SET_BUSINESS_UNIT = prefixCreator('SET_BUSINESS_UNIT');
}

Refactor with a class decorator:

import { createActionPrefix } from 'redux-actions-helper';

@actionTypes('USER')
export default class UserActionTypes {
  static INIT_USER = null;
  static SET_BUSINESS_UNIT = null;
}

export function actionTypes(prefix: string) {
  return (constructor: any) => {
    const factory = createActionPrefix(prefix);
    Object.keys(constructor).forEach((key) => {
      constructor[key] = factory(key);
    });
    return constructor;
  };
}

Cleaner and non-invasive.

Final Thoughts

Decorators are just functions, but they encourage a design pattern: extend behavior without cluttering the original class. Use them thoughtfully.

References

Alan H
Authors
开发者,数码产品爱好者,喜欢折腾,喜欢分享,喜欢开源