데코레이터로 메서드 반환 재정의

데코레이터는 TypeScript의 실험적 기능이므로 작성하고 의존하는 것은 좋지 않을 수 있습니다. Google조차도 using them에 대해 경고합니다. 당신은 지금 경고를 받았습니다.

자, 이제 API 응답을 수정하는 데 어떻게 사용할 수 있는지 살펴보겠습니다. 이 문서의 목적을 위해 우리는 Star Wars apiAngular 을 사용할 것입니다. 이미 데코레이터 더미를 자체적으로 사용하고 있으므로...

id로 api에서 영웅을 요청하는 서비스를 추가하고 응답을 한 눈에 보고 인터페이스로 변환해 보겠습니다. 다음과 같습니다.

// src/app/models/hero.model.ts
interface Hero {
  name: string;
  height: string;
  mass: string;
  hair_color: string;
  skin_color: string;
  eye_color: string;
  birth_year: string;
  gender: string;
  homeworld: string;
  films: string[];
  species: string[];
  vehicles: string[];
  starships: string[];
  created: string;
  edited: string;
  url: string;
}


따라서 서비스는 다음과 같이 표시됩니다.

// src/app/services/star-wars-api/star-wars-api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import { Hero } from 'src/app/models/hero.model';

@Injectable({
  providedIn: 'root',
})
export class StarWarsApiService {
  private readonly api = 'https://swapi.dev/api';

  constructor(private http: HttpClient) {}

  public getHero(id: number): Observable<Hero> {
    const url = `${this.api}/people/${id}`;
    return this.http.get<Hero>(url);
  }
}


좋아 보이지만 어떤 이유로 Datecreated 필드에 edited 유형을 갖고 싶다고 상상해 봅시다.

개체를 순회하고 문자열을 날짜로 변환할 수 있는 함수를 빌드해 보겠습니다.

// src/app/utils/convert-string-to-date-recursively.util.ts
export function convertStringToDateRecursively<T>(
  obj: any,
  checkDate: (s: string) => boolean
): T {
  for (const k in obj) {
    if (!Object.prototype.hasOwnProperty.call(obj, k)) {
      continue;
    }

    const value = obj[k];

    if (Array.isArray(value)) {
      value.map((el) => convertStringToDateRecursively(el, checkDate));
      continue;
    }

    if (typeof value === 'string' && checkDate(value)) {
      obj[k] = new Date(value) as any;
      continue;
    }

    if (typeof value === 'object') {
      obj[k] = convertStringToDateRecursively(value, checkDate);
    }
  }
  return obj;
}


따라서 우리는 모든 개체를 수락하고 해당 필드를 반복하고 string에서 checkDate까지 날짜 문자열로 확인된 모든 Date를 변환합니다. 문자열이 날짜인지 확인하는 별도의 기능을 제공하면 관심사를 더 잘 분리할 수 있으므로 변환기는 변환만 처리합니다.

이 함수는 HerocreatededitedDate 유형으로 갖는 다른 인터페이스로 변환하기 위해 파이프에서 사용할 수 있지만 위험하므로 데코레이터를 사용하여 즉시 변환을 수행합니다. Hero 인터페이스에서 해당 필드의 유형을 직접 변경하기만 하면 됩니다. 엔터프라이즈 응용 프로그램에는 좋은 생각이 아니지만 애완 동물 프로젝트 또는 데코레이터와의 장난에는 적합합니다.

메서드 데코레이터는 기본적으로 target , propertyKeydescriptor 를 인수로 받아들이고 이들을 사용하여 무엇이든 수행할 수 있는 함수이므로 기존 클래스 메서드의 동작을 재정의하는 데 사용할 수 있습니다. 우리의 목적을 위해 문자열 날짜를 Observable 유형으로 변환하기 위해 원래 메서드에서 반환된 map와 우리가 만든 convertStringToDateRecursively 함수로 파이프할 것입니다. 문자열이 날짜인지 확인하기 위해 Date 패키지의 함수를 사용할 것입니다. 이 함수는 단순히 문자열이 유효한 ISO-8601 날짜인지 확인하고 부울을 반환하므로 우리의 필요에 맞습니다.

// src/app/decorators/map-strings-to-dates.decorator.ts
import { map, Observable } from 'rxjs';

import { isValidISODateString } from 'iso-datestring-validator';

import { convertStringToDateRecursively } from '../utils/convert-string-to-date-recursively.util';

export function mapStringsToDates(
  target: unknown,
  propertyKey: string,
  descriptor: PropertyDescriptor
): void {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: unknown[]) {
    return (<Observable<unknown>>originalMethod.apply(this, args)).pipe(
      map((r) => convertStringToDateRecursively(r, isValidISODateString))
    );
  };
}


아주 간단하게, 남은 것은 방법을 장식하는 것입니다.

// src/app/services/star-wars-api/star-wars-api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import { Hero } from 'src/app/models/hero.model';
import { mapStringsToDates } from 'src/app/decorators/map-strings-to-dates.decorator';

@Injectable({
  providedIn: 'root',
})
export class StarWarsApiService {
  private readonly api = 'https://swapi.dev/api';

  constructor(private http: HttpClient) {}

  @mapStringsToDates
  public getHero(id: number): Observable<Hero> {
    const url = `${this.api}/people/${id}`;
    return this.http.get<Hero>(url);
  }
}


이제 iso-datestring-validator 인터페이스를 업데이트하고 HeroDate 필드의 유형으로 created를 입력할 수 있습니다.

그게 전부입니다. 이제 불을 가지고 놀 수 있습니다 :D

추신 repo with code :)

좋은 웹페이지 즐겨찾기