# 데코레이터 함수

## 데코레이터 함수

> TS v5.0 에서 데코레이터 함수가 정식으로 추가됨

* **클래스의 기능을 증강하는 함수**로 여러 함수에서 공통으로 수행되는 부분을 데코레이터로 만들어두면 좋다.
* 데코레이터를 여러개 붙힐 수 도 있다.
* 클래스 데코레이터의 경우 export나 export default 앞이나 뒤에 데코레이터를 붙일 수 있다.
  * 단 앞과 뒤 동시에 붙이는건 안됨

```typescript
@Log export class C {

export @Log class C {

@Log
export class C {
```

```typescript
// eat, work, sleep이 start와 end를 로깅하는 console.log가 중복됨
// 이렇게 중복이 있는 경우 데코레이터를 통해 중복을 제거
class A {
  eat() {
    console.log('start');
    console.log('Eat');
    console.log('end');
  }
  
  work() {
    console.log('start');
    console.log('Work');
    console.log('end');
  }
  
  sleep() {
    console.log('start');
    console.log('Sleep');
    console.log('end');}
}
```

* 데코레이터를 통해 **중복을 제거**

```typescript
function startAndEnd(originMethod: any, context: any) {
  function replacementMethod(this: any, ...args: any[]) {
    console.log('start');
    const result = originalMethod.call(this, ...args);
    console.log('end');
    return result;
  }
  return replacementMethod;
}

class A {
  @startAndEnd
  eat() {
    console.log('Eat');
  }
  
  @startAndEnd
  work() {
    console.log('Work');
  }
  
  @startAndEnd
  sleep() {
    console.log('Sleep');
  }
}
```

* `originalMethod` 매개변수 eat, work, sleep 같은 기존 메서드
* 이 메서드가 대체 메서드(replacementMethod)로 바뀐다고 생각
* any 타이핑을 제거해보자.

```typescript
function startAndEnd<This, Args extends any[], Return>(
  originalMethod: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
  function replacementMethod(this: This, ...args: Args): Return {
    console.log('start');
    const result = originalMethod.call(this, ...args);
    console.log('end');
    
    return result;
  }
  return replacementMethod;
}
```

### Context

> 데코레이터 정보를 갖고 있는 매개변수

* startAndEnd 데코레이터의 경우 클래스의 메서드를 장식하고 있으므로 context는 **`ClassmethodDecoratorContext`**&#xAC00; 된다.
* 어떤 문법을 장식하냐에 따라 context의 타입을 교체하면 된다.

```typescript
// context 객체 타입
type Context = {
  kind: string; // 데코레이터 유형, ClassDeceoratorContext라면 class 
  name: string | symbol; // 장식 대상의 이름
  access: { // has, get, set 등의 접근자를 모아둔 객체
    get?(): unknown;
    set?(value: unknown): void;
    has?(value: unknown): boolean;
  };
  private?: boolean; // private 여부
  static?: boolean; // static 여부
  addInitializer?(initializer: () => void): void; // 초기화(인스턴스 생성)힐 때 실행되는 메서
}
```

#### Context의 종류

* **`ClassDecoratorContext`**: 클래스 자체를 장식할 때
* **`ClassMethodDecoratorContext`**: 클래스 메서드를 장식할 때
* **`ClassGetterDecoratorContext`**: 클래스의 getter를 장식할 때
* **`ClassSetterDecoratorContext`**: 클래스의 setter를 장식할 때
* **`ClassMemberDecoratorContext`**: 클래스의 멤버를 장식할 때
* **`ClassAccessorDecoratorContext`**: 클래스 accessor를 장식할 때
* **`ClassFiledDecoratorContext`**: 클래스 필드를 장식할 때

### 데코레이터도 함수이다.

> 함수이므로 매개변수를 가질 수 있다.
>
> 다만 **고차함수**를 활용해야 한다.

```typescript
function startAndEnd(start = 'start', end = 'end') {
  return function RealDecorator<This, Args extends any[] , Return>(
    originalMethod: (this: This, ...args: Args) => Return,
    context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
  ) {
      function replacementMethod(this: This, ...args: Args): Return {
      console.log(contenxt.name, start);
      const result = originalMethod.call(this, ...args);
      console.log(context.name, end);
      
      return result;
    }
    return replacementMethod;
  }
}

class A {
  @sttartAndEnd('시작', '끝')
  sleep() {
    console.log('Sleep');
  }
}
```

#### 또 다른 예시

```typescript
function log<Input extends new (...args: any[]) => any> (
  value: Input,
  context: ClassDecoratorContext
) {

  if (context.kind === 'class') {
    return class extends value {
      constructor(...args: any[]) {
        super(args);
      }
      log(msg: string): void {
        console.log(msg);
      }
    };
    return value
  }
}

function bound(originalMethod: unknown, context: ClassMethodDecoratorContext<any>) {
  const methodName = context.name;
  if (context.kind === 'method') {
    context.addInitializer(function () {
      this[methodName] = this[methodName].bind(this);
    })
  }
}

@log
export class C {
  @bound
  @startAndEnd()
  eat() {
    console.log('Eat');
  }
  
  @bound @startAndEnd() work() {
    conosle.log('Work');
  }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/typescript/undefined/undefined-11.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
