Injectable装饰器

阅读须知

本系列教程的开发环境及开发语言:

基础知识

装饰器是什么

  • 它是一个表达式
  • 该表达式被执行后,返回一个函数
  • 函数的入参分别为 targe、name 和 descriptor
  • 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象 

装饰器的分类

  • 类装饰器 (Class decorators)
  • 属性装饰器 (Property decorators)
  • 方法装饰器 (Method decorators)
  • 参数装饰器 (Parameter decorators)

TypeScript 类装饰器

类装饰器声明:

------- ---- -------------- - ---------- ---------- -------------------- ---------- --    
  --------- - ----

类装饰器顾名思义,就是用来装饰类的。它接收一个参数:

  • target: TFunction - 被装饰的类

看完第一眼后,是不是感觉都不好了。没事,我们马上来个例子:

-------- --------------- ---------- ---- -
  ---------------------- - -------- --- ---- -
    ----------------------
  -
-

--------
----- -------- -
  ------------- - -- ---- -
-

--- ---------- - --- -----------
------------------- -- ------- ------- ---------

上面的例子中,我们定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法,来使用装饰器。

Injectable 类装饰器使用

------ - ---------- - ---- ----------------

-------------
----- ----------- --

Injectable 装饰器

在介绍 Injectable 装饰器前,我们先来回顾一下 HeroComponent 组件:

------------
  --------- -----------
  --------- -
    ----
      --- ----------- ---- -- -------
        --- ----------- - ----- -------------
      -----
    -----
  -
--
------ ----- ------------- ---------- ------ -
  ------ ------- ------ ---------- -------- --------- ------

  ------------------- ------------ ------------
    ------- -------------- -------------- - -

  ---------- -
    -------------------------------- -----------
    ---------- - ----------------------------
  -
-

</{>

HeroComponent 组件的 ngOnInit 生命周期钩子中,我们在获取英雄信息前输出相应的调试信息。其实为了避免在每个应用的组件中都添加 log 语句,我们可以把 log 语句放在 getHeros() 方法内。

更新前 HeroService 服务

------ ----- ----------- -
    ------ ------- ------ ---------- -------- --------- ----- - -
        - --- --- ----- ---- ----- --
        - --- --- ----- ------- --
        - --- --- ----- ---------- --
        - --- --- ----- ----------- --
        - --- --- ----- --------- --
        - --- --- ----- ----------- --
        - --- --- ----- -------- --
        - --- --- ----- --- --- --
        - --- --- ----- ------- --
        - --- --- ----- --------- -
    --

    ---------- -
        ------ -----------
    -
-

</{>

更新后 HeroService 服务

------ - ------------- - ---- -------------------

------ ----- ----------- -
    ------------------- -------------- -------------- - -

    ------ ------- ------ ---------- -------- --------- ----- - -
        - --- --- ----- ---- ----- --
        - --- --- ----- ------- --
        - --- --- ----- ---------- --
        - --- --- ----- ----------- --
        - --- --- ----- --------- -
    --

    ---------- -
        -------------------------------- -----------
        ------ -----------
    -
-

</{>

当以上代码运行后会抛出以下异常信息:

-------- ------ ----- ------- --- ---------- --- ------------ ----

上面异常信息说明无法解析 HeroService 的所有参数,而 HeroService 服务的构造函数如下:

------ ----- ----------- -
   ------------------- -------------- -------------- - -
-

该构造函数的输入参数是 loggerService 且它的类型是 LoggerService 。在继续深入研究之前,我们来看一下 HeroService 最终生成的 ES5 代码:

--- ----------- - ----------- -
   -------- -------------------------- -
     ------------------ - --------------
     ---------- - ------- -----
   -
   ------------------------------ - ---------- -
     -------------------------------- -----------
     ------ -----------
   --
   ------ ------------
-----

我们发现生成的 ES5 代码中,HeroService 构造函数中是没有包含任何类型信息的,因此 Angular Injector (注入器) 就无法正常工作了。那么要怎么保存 HeroService 类构造函数中参数的类型信息呢?相信你已经想到了答案 — 当然是使用 Injectable 装饰器咯。接下来我们更新一下 HeroService

------ - ---------- - ---- ----------------
------ - ------------- - ---- -------------------

-------------
------ ----- ----------- -
  -- ---
-

更新完上面的代码,成功保存后,在 http://localhost:4200/ 页面,你将看到熟悉的 "身影":

--- -- - ----- --- ----
--- -- - ----- -----
--- -- - ----- --------
--- -- - ----- ---------
--- -- - ----- -------

现在我们再来看一下 HeroService 类生成的 ES5 代码:

--- ----------- - ----------- -
     -------- -------------------------- -
       ------------------ - --------------
       ---------- - ------- -----
     -
     ------------------------------ - ---------- -
       -------------------------------- -----------
       ------ -----------
     --
     ------ ------------
-----
----------- - ----------------------------------
  ------------------------------------------------- ---------- --
----- ------------------------------- ------ -------------

__decorate 函数

--- ---------- - ----- -- ---------------- -- -------------------- ------- ---- ----- ------

__metadata 函数

--- ---------- - ----- -- ---------------- -- ----------- -- -
  -- ---- ------- --- -------- -- --- ---------------- --- -----------
    ------ ------------------- ---
--

我们发现相比未使用 Injectable 装饰器,HeroService 服务生成的 ES5 代码多出了 HeroService = __decorate(...) 这些代码。简单起见,我稍微介绍一下,通过 Injectable 装饰器,在编译时会把 HeroService 服务构造函数中参数的类型信息,通过 Reflect API 保存在 window['__core-js_shared__'] 对象的内部属性中。当 Injector 创建 HeroService 对象时,会通过 Reflect API 去读取之前已保存的构造函数中参数的类型信息,进而正确的完成实例化操作。

有兴趣的读者,可以查看 Angular 4.x 修仙之路Decorator(装饰器) 章节的相关文章。

我有话说

@Injectable() 是必须的么?

如果所创建的服务不依赖于其他对象,是可以不用使用 Injectable 类装饰器。但当该服务需要在构造函数中注入依赖对象,就需要使用 Injectable 装饰器。不过比较推荐的做法不管是否有依赖对象,在创建服务时都使用 Injectable 类装饰器。


上一篇:FactoryProvider配置依赖对象
下一篇:ValueProvider的使用