typescript依賴注入實踐

以前寫過的一篇關於《前端IOC 的簡單實踐》,基於本人是一個前端,設計模式的基礎知識都不完備,因此內容不是太嚴謹,正在學習中! 文章中提到了一個關鍵詞:依賴注入。前端

有小夥伴跟我提說在真實項目中如何使用的,我知道 angular 就是借鑑 springioc ,奈何我沒有用過,下面呢就來講說我以前在nodejs項目上的一些實踐。node

去年,我貼了一個 nodejs 的簡易web框架-- lenneth,基於 koa2 搞的,簡單說就是用裝飾器模仿 spring 來寫 nodejs 的註解(說註解習慣了,就不說裝飾器了),看下示例:git

import {
  Controller,
  Autowired,
  Post,
  Get,
  RequestBody,
  PathVariable,
  Response,
  TResponse,
  UseBefore,
  Description
} from "lenneth";
import { UserService } from "../services";
import { IUserInfo } from "../interface";
import { UserAuth, RuleAuth } from "../middleware";

@Controller("/user")
export class UserController {
  @Autowired()
  userService: UserService;

  @Post("/add")
  @Description("添加會員")
  @UseBefore(UserAuth, RuleAuth)
  async addUser(
    @RequestBody() user: IUserInfo,
    @Response() response: TResponse
  ) {
    response.body = this.userService.addUser(user);
  }

  @Get("/detail/:userId")
  @UseBefore(UserAuth)
  @Description("查詢會員")
  async getUser(
    @PathVariable("userId") userId: string,
    @Response() response: TResponse
  ) {
    response.body = this.userService.getUserInfo(userId);
  }
}
複製代碼

看到這些註解,是否是很眼熟,就是從 spring 抄來的,具體介紹能夠去項目裏看看,下面來重點介紹實現 Autowired 註解的過程,也就是依賴注入的實踐。es6

看上面的實例,這個項目依賴了一個 UserService 類,在這個 UserController 這個方法中會用到這個依賴類的某個方法。github

依賴注入:web

@Autowired()
userService: UserService;
複製代碼

使用:spring

this.userService.addUser(user);
複製代碼

來看下 Autowired 註解的實現:設計模式

import { Metadata } from "@common";
import { descriptorOf, getClassName } from "@utils";

/** * 注入service,類屬性修飾器 * @param params 實例化參數 */
export const Autowired = (params: any = ""): Function => {
  return (target: any, propertyKey: string) => {
    // 獲取該屬性的類型
    let typeClass = Metadata.getType(target, propertyKey);
    const descriptor = descriptorOf(target, propertyKey) || {
      writable: true,
      configurable: true
    };
    // 實例化修飾類
    descriptor.value = params ? new typeClass(params) : new typeClass();
    Reflect.defineProperty(
      (target && target.prototype) || target,
      propertyKey,
      descriptor
    );
  };
};

複製代碼

解讀這段實現以前,先引出了另外一個概念--反射,就是在運行時動態獲取一個對象的一切信息,包括方法/屬性等等,特色在於動態類型反推導。api

Reflect 是ES6新增的api,自己提供了很多靜態方法,不過要使用還須要引入 reflect-metadata 這個庫,爲了使編譯器在設計時將元數據序列化傳給修飾器。bash

經過反射能得到系統提供的metadataKey信息:

  • design:type 修飾目標對象的類型;
  • design:paramtypes 修飾目標對象方法的參數類型;
  • design:returntype 修飾目標對象方法返回值的類型;

來看下案例:

import "reflect-metadata";

const validate = () => {
  return (target: any, propertyKey: string) => {
    // 修飾目標對象的類型
    let type = Reflect.getMetadata("design:type", target, propertyKey);
    // 修飾目標的參數類型
    let paramTypes = Reflect.getMetadata(
      "design:paramtypes",
      target,
      propertyKey
    );
    // 修飾目標的返回值類型
    let returnType = Reflect.getMetadata(
      "design:returntype",
      target,
      propertyKey
    );
    // 全部能經過反射獲取的元數據類型key
    let allKeys = Reflect.getMetadataKeys(target, propertyKey);
    console.log("type", type);
    console.log("paramTypes", paramTypes);
    console.log("returnType", returnType);
    console.log("allKeys", allKeys);
  };
};

class Person {
  private name: string;

  @validate()
  getInfo(tags: string): string {
    return `your name is ${this.name}, tags is ${tags}`;
  }
}

複製代碼

控制檯展現:

type function Function() { [native code] }
paramTypes [ [Function: String] ]
returnType function String() { [native code] }
allKeys [ 'design:returntype', 'design:paramtypes', 'design:type' ]
複製代碼

特別注意:design:returntype 依賴於所修飾方法的是否顯式定義類型了,若是沒有定義類型,那就會默認返回 undefined

咱們也能夠自定義 metadataKey,即在相應的類上定義自定義的元數據。

const service = () => {
  return (target: any) => {
    // 自定義元數據,key 爲 ServiceDecorator
    Reflect.defineMetadata("ServiceDecorator", "your personal value", target);
  };
};

@service()
class Person {
  private name: string;
}

// 在合適的位置獲取以前定義的元數據
// your personal value
console.log(Reflect.getMetadata("ServiceDecorator", Person));
複製代碼

自此,有了這個知識,在看上面的 Autowired 代碼是否是簡單的多了。

Autowired 註解的本質是一個屬性修飾器,主要是考慮到會有參數傳入,因此就寫了一個高階函數。修飾器自己就不作介紹了,能夠看下阮一峯老師的es6教程。

在方法內部,先獲取了被修飾對象的類型,轉換以下:

let typeClass = Reflect.getMetadata("design:type", target, propertyKey);
複製代碼

這個 metadataKey 是系統提供的 design:type,獲取被修飾對象的類型。

@Autowired()
userService: UserService;
複製代碼

那這個 typeClass 的值就是 UserService

// 獲取指定對象屬性的描述對象
const descriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey) || {
      writable: true,
      configurable: true
    };
複製代碼

這裏就是獲取 UserControlleruserService 屬性的描述對象,那這個值有什麼用呢?

Reflect.getOwnPropertyDescriptor 方法其實等同於 Object.getOwnPropertyDescriptor ,它會返回一個object:

{
    value: "value",
    writable: true,
    enumerable: true,
    configurable: true
}
複製代碼

返回的四個字段中value就是這個屬性的值,咱們只要修改這個value字段,就能夠實現注入了。

descriptor.value = params ? new typeClass(params) : new typeClass();
Reflect.defineProperty(
  (target && target.prototype) || target,
  propertyKey,
  descriptor
);
複製代碼

因此,最後修改了這個屬性的描述對象的值,使它指向了所返回類型的實例對象,再從新定義這個屬性的描述對象,這樣編譯後,userService 這個被修飾的屬性就是UserService 的實例對象,可以訪問到UserService內的屬性方法了。

如此,就實現了 Autowired 註解的功能了。

完整示例:

const Autowired = (params: any = ""): Function => {
  return (target: any, propertyKey: string) => {
    // 獲取該屬性的類型
    let typeClass = Reflect.getMetadata("design:type", target, propertyKey);
    const descriptor = Reflect.getOwnPropertyDescriptor(
      target,
      propertyKey
    ) || {
      writable: true,
      configurable: true
    };
    // 實例化修飾類
    descriptor.value = params ? new typeClass(params) : new typeClass();
    Reflect.defineProperty(
      (target && target.prototype) || target,
      propertyKey,
      descriptor
    );
  };
};

class UserService {
  getUserById(id: string) {
    return `user id is ${id}`;
  }
}

class Person {
  @Autowired()
  private userService: UserService;
  
  getUserInfo(id: string) {
    console.log(this.userService.getUserById(id));
  }
}

// user id is 12
console.log(new Person().getUserInfo("12"));
複製代碼

原文地址

相關文章
相關標籤/搜索