浅入浅出Typescript Decorators

简介: 临时起的兴趣,想写一篇关于ts decorator的文章,就花小半天整理了一下... 这东西,在ES2017里好像也有... 文档的话看这里。因为临时,也没想写太多文字介绍,带少许文字说明直接开撸代码吧。

临时起的兴趣,想写一篇关于ts decorator的文章,就花小半天整理了一下...
这东西,在ES2017里好像也有... 文档的话看这里
因为临时,也没想写太多文字介绍,带少许文字说明直接开撸代码吧。

本文通过ts编译后的decorator代码解释一番装饰器是什么?能做什么?有什么好处?

实现代码

编译后代码是这样的,带注释:

var __decorate =
  (this && this.__decorate) ||
  function(decorators, target, key, desc) {
    // c 参数长度
    // r ? c < 3 则是target,否则先判断desc为null的话则将desc取target的key属性的描述,再否则便是desc了
    // d 预留使用
    var c = arguments.length,
      r =
        c < 3
          ? target
          : desc === null
          ? (desc = Object.getOwnPropertyDescriptor(target, key))
          : desc,
      d;
    // 下面文字解释,这仅是个甩锅的行为
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
      r = Reflect.decorate(decorators, target, key, desc);
    // 循环 decorators  并每次赋值给 d,并且判断值
    else
      for (var i = decorators.length - 1; i >= 0; i--)
        if ((d = decorators[i]))
          // c < 3 ,用 r 作为 decorators[i] 的入参执行;
          // c > 3 ,target, key, r 作为 decorators[i] 的入参执行;
          // c === 3,target, key 作为 decorators[i] 的入参执行。
          // 如果执行无返回结果, r = r;。
          r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    // 如果 c > 3 && r , 修改 target ,返回 r
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };

从代码里可以看出,最终结果要么是用decorator执行target,从而改变一些什么东西;要么就是使用Object.defineProperty来对target来做操作,代码就几行,用处确不小...具体的执行过程结合下面的两个例子会更容易理解。

值得一提的是,关于代码里的Reflect原本以为是这个 sec-reflect-object 里的方法,但可惜不是;

然后猜测是Typescript的实现,翻了Typescript/tsc.js的代码(如果打不开链接就从 node_modules 下看吧),发现也不是,再去查 stackoverflow 的解释,是这样的 what-is-reflect-decorate-in-js-code-transpiled-from-ts

大致说是ts希望把这个锅甩给ES来补,到时候ts的else里的代码便是polyfill了

案例

以下面的 decorator 和 class 作为例子解释

// ts 代码
function show(target: any) {
  console.log(target);
  target.prototype.showMe = (name: string) => {
    console.log("show me :", name);
  };
}

interface IShow {
  showMe?(name: string): any;
}

@show
class Show implements IShow {
  showMe(name: string) {}
}

const shoow = new Show();
shoow.showMe("ys");

// 编译后的js
// decorator ,简单的打印,并且修改方法
function show(target) {
  console.log(target);
  target.prototype.showMe = function(name) {
    console.log("show me :", name);
  };
}

// class Shoow
var Shoow = (function() {
  function Shoow() {}
  Shoow.prototype.showMe = function(name) {};
  // decorators 为[show],target 为 Shoow
  Shoow = __decorate([show], Shoow);
  return Shoow;
})();

var shooow = new Shoow();
shooow.showMe("ys");

// output : show me : ys

理解一下执行步骤:

  1. decorators = [show],target = Shoow,
  2. c = 2,r = target{Shoow},d = undefined
  3. 不存在 Reflect,走循环,只循环一次
  4. d = show,r = show(target{Shoow}),r 没返回结果,所以 r 还是 r , r = target{Shoow}
  5. return 结果: c = 2, 所以返回 false
  6. 执行后无返回值,但是在执行show(target{Shoow})的时候将showMe方法改掉了,于是执行结果符合预期

一个不够?再来一个?这次在里面返回一个函数试试?

// ts代码
function logger1(config?) {
  return function(target, key: string, descriptor: PropertyDescriptor) {
    const _value = descriptor.value;
    if (typeof _value === "function") {
      descriptor.value = (...args) => {
        console.log(`logger1-begin : ${config.level}`);
        const res = _value.apply(target, args);
        console.log(`logger1-end`);
        return res;
      };
    }
    return descriptor;
  };
}

function logger2(config?) {
  return function(target, key: string, descriptor: PropertyDescriptor) {
    const _value = descriptor.value;
    if (typeof _value === "function") {
      descriptor.value = (...args) => {
        console.log(`logger2-begin : ${config.level}`);
        const res = _value.apply(target, args);
        console.log(`logger2-end`);
        return res;
      };
    }
    return descriptor;
  };
}

interface IShow {
  showMe?(name: string): any;
}

class Show implements IShow {
  @logger1({ level: "info" })
  @logger2({ level: "error" })
  showMe(name: string) {
    console.log("show me :", name);
  }
}

const shoow = new Show();
shoow.showMe("ys");

// output 这里手动加个缩进,这时候showMe方法已经经过多次包裹
// logger1-begin : info
//   logger2-begin : error
//     show me : ys
//   logger2-end
// logger1-end

再来看看执行步骤:

  1. decorators = [logger1, logger2],target = Shoow,key = "showMe",desc = null 注意,这里是为null,不是为undefined
  2. c = 4,r = target{Shoow},d = undefined
  3. 不存在 Reflect,走循环,只循环一次
  4. 第一次循环取 d = logger1,r = logger1(target, key, r),因为 return 存在值,r = logger1 的返回函数,这时候 descriptor.value 被第一次重写
  5. 第二次循环取 d = logger2,r = logger2(target, key, r),又因为 return 存在值,r = logger2 的返回函数,这时候 descriptor.value 被第二次重写
  6. return 结果: 因为 c > 3,r 存在值,执行 Object.defineProperty(target, key, r)来重写对象属性并且返回 r (r为重写的结果)
  7. 经过 2 次重写 showMe 属性值,执行结果符合预期

欢乐

装饰器给你带来什么欢乐?简单列几个最明显的优点,其他在运用中各自提取每日份的快乐去吧...

  1. 业务和功能之间的解耦(比如日志)
  // 日常
  dosomething(){
    consol.log('start')
    // some thing
    console.log('end')
  }

  // 使用装饰器
  @logger(logConfig?)
  dosomething();
  1. 代码结构清晰(特别针对多层HOC后的React组件)
  // 日常多层HOC
  class MyComponent extends Component{
    // ..
  }
  connect(mapFn)(
    MyHoc(someMapFn)(
      Form.create(fieldsMapFn)(MyComponent)
    )
  )

  // 使用装饰器
  @connect(mapFn)
  @MyHoc(someMapFn)
  @FormFields(fieldsMapFn)
  class MyComponent extends Component{
    // ..
  }
  export default MyComponent;

最后

AOP,了解一下

相关文章
|
JavaScript 前端开发 编译器
TypeScript Decorators 的使用说明
TypeScript Decorators 的使用说明
136 0
TypeScript Decorators 的使用说明
|
17天前
|
JavaScript 编译器
TypeScript中类型守卫:缩小类型范围的艺术
【4月更文挑战第23天】TypeScript中的类型守卫是缩小类型范围的关键技术,它帮助我们在运行时确保值的精确类型,提升代码健壮性和可读性。类型守卫包括`typeof`(检查原始类型)、`instanceof`(检查类实例)和自定义类型守卫。通过这些方法,我们可以更好地处理联合类型、泛型和不同数据源,降低运行时错误,提高代码质量。
|
3月前
|
JavaScript 前端开发 安全
Apollo与TypeScript:强大类型检查在前端开发中的应用
Apollo与TypeScript:强大类型检查在前端开发中的应用
|
10天前
|
JavaScript 安全 前端开发
【TypeScript技术专栏】TypeScript中的类型推断与类型守卫
【4月更文挑战第30天】TypeScript的类型推断与类型守卫是提升代码安全的关键。类型推断自动识别变量类型,减少错误,包括基础、上下文、最佳通用和控制流类型推断。类型守卫则通过`typeof`、`instanceof`及自定义函数在运行时确认变量类型,确保类型安全。两者结合使用,优化开发体验,助力构建健壮应用。
|
10天前
|
JavaScript 前端开发 开发者
【TypeScript技术专栏】TypeScript类型系统与接口详解
【4月更文挑战第30天】TypeScript扩展JavaScript,引入静态类型检查以减少错误。其类型系统包括基本类型、数组等,而接口是定义对象结构的机制。接口描述对象外形,不涉及实现,可用于规定对象属性和方法。通过声明、实现接口,以及利用可选、只读属性,接口继承和合并,TypeScript增强了代码的健壮性和维护性。学习和掌握TypeScript的接口对于大型项目开发至关重要。
|
10天前
|
JavaScript 前端开发
TypeScript基础类型
TypeScript基础类型
|
10天前
|
JavaScript 前端开发
typescript 混合类型
typescript 混合类型
|
16天前
|
JavaScript 前端开发 开发者
类型检查:结合TypeScript和Vue进行开发
【4月更文挑战第24天】TypeScript是JavaScript超集,提供类型注解等特性,提升代码质量和可维护性。Vue.js是一款高效前端框架,两者结合优化开发体验。本文指导如何配置和使用TypeScript与Vue:安装TypeScript和Vue CLI,创建Vue项目时选择TypeScript支持,配置`tsconfig.json`,编写`.tsx`组件,最后运行和构建项目。这种结合有助于错误检查和提升开发效率。
|
17天前
|
JavaScript 编译器 开发者
TypeScript中的类型推断机制:原理与实践
【4月更文挑战第23天】TypeScript的类型推断简化编码,提高代码可读性。编译器基于变量初始值或上下文推断类型,若新值不兼容则报错。文章深入探讨了类型推断原理和实践,包括基本类型、数组、函数参数与返回值、对象类型的推断,并提醒注意类型推断的限制,如非万能、类型兼容性和适度显式指定类型。了解这些能帮助更好地使用TypeScript。
|
17天前
|
JavaScript 前端开发 编译器
TypeScript中的高级类型:联合类型、交叉类型与条件类型深入解析
【4月更文挑战第23天】探索TypeScript的高级类型。这些特性增强类型系统的灵活性,提升代码质量和维护性。