타입정의하기 updated_at: 2024-12-15 04:01

타입정의하기

angular에서 타입을 정의하는 다양한 방법에 대해 간단한 예제들을 리스트업 해 보았습니다.

any

가장 많이 사용하는 방식으로 object의 타입을 잘 모를때 정의 할 수 있습니다.

private anytype: any;

private anyMethod(anyvalue: any): any {
  return anyValue;
}

두가지 이상 정의할때

변수의 값이 두가지 이상의 타입을 가질때 사용 가능합니다.

private strOrnumber: string | number;
private strOrnumber: string | null;
private anyMethod(anyvalue: string | number): string | number {
  return StringOrNumber;
}

직접 정의

string, number같은 경우는 불편함 없이 바로 사용가능하나 object같은 경우 interface를 설정하여 정의하기를 권장하나 조금 번거러울때 사용하시기를..

private anyMethod(users: Array<{ id: string; email: string }>) { // any를 사용하는 것보다 가독성이 있다.
  .....
}

type을 정의하여 정의

export type Params = [
  anchor: Vector3,
  LocalFrame: Quaternion
];
..........
private method(param: Params) {
  ..........
}

제네릭(Generic) 타입

Generic 은 한글로 번역하자면 일반적이라는 말이다. 말자체로 특정한 타입을 정할 수 없을 경우 사용되는데 any가 좀더 포괄적인것에 반해 Generic는 좀더 제한 적이다.
제네릭을 설명하기 전에 먼저 아래의 3가지 예를 보자

function add(x: number, y: number): number {
  return x + y;
}
add(1, 2); // OK
add('hello', 'typescript') // Argument of type 'string' is not assignable to parameter of type 'number'.
function add(x: string, y: string): string {
  return x + y;
}
add(1, 2); // Argument of type 'number' is not assignable to parameter of type 'string'.
add('hello', 'typescript') // OK
function add(x: any, y: any): any {
  return x + y;
}
add(1, 2); // OK
add('hello', 'typescript') // OK

위에서 보듯이 any로 할 경우는 모든 것을 통과한다.
그럼 왜 any를 사용하지 않고 Generic을 사용하는 것일까 ?
그 물음의 본질은 우리가 왜 굳이 타입을 정의하여 프로그램을 어렵게(?) 만드는 가를 생각해보면 된다
type을 정의하는 가장 큰이유가 연산시 에러 방지용, 혹은 디버깅을 편하게 하지 위함이다. 가령 1+1을 계산하는데 가끔 11 을 출력하는 경우가 있다. 문자열을 넣었기 때문이고 이것은 후에 끔찍한 결과를 가져올 수도 있다.

제네릭이란

그럼 위의 add라는 것을 문자도 되고 숫자도 가능하게 할 수는 없을까? (물론 any 말고..)
이때 사용하는 것이 제네릭이다. 제네릭은 메쏘드(혹은 함수)에 타입을 정해 놓는 것이 아니라 메쏘드를 호출할때 타입을 부여하는 방식이다.
아래 예를 보자

function add<T>(x: T, y: T): T { // 제네릭 : 꺾쇠와 문자 T를 이용해 표현. T는 변수명이라고 보면 된다.
  return x + y; //  Operator '+' cannot be applied to types 'T' and 'T'. 이런 오류가 있지만 이해를 돕기위해..ㅠ.ㅠ
}

add<number>(1, 2); // 제네릭 타입 함수를 호출할때 <number> 라고 정해주면, 함수의 T 부분이 number로 바뀌며 실행되게 된다.
add<string>('hello', 'world');
function consolelog<T, U>(x: T, y: U): U { // T는 형식적인것이고 아무거나 사용하면 된다. 여기서는 number, string 타입을 받아 string type으로 리턴하는 예제이다.
  return x + ',' + y;
}
console.log(consolelog<number, string>(1, 'typescript')); 

위에서 보듯이 generic를 사용하면 메쏘드를 호출하때 우리가 받아야 하는 타입도 정해서 사용하므로 좀더(any 보다) 검증된 프로그램이 가능하며 하나의 메쏘드를 이용하여 다양한 기능이 가능하다.

제네릭 고급 예제

이 부분은 원본은 이곳을 참조

배열형
function toArray<T>(a: T, b: T): T[] {
  return [a, b];
}

// 만약 화살표 함수로 제네릭을 표현한다면 다음과 같이 된다.
const toArray2 = <T>(a: T, b: T): T[] => { ... }

toArray<number>(1, 2); // 숫자형 배열
toArray<string>('1', '2'); // 문자형 배열
toArray<string | number>(1, '2'); // 혼합 배열
function loggingIdentity<T>(arg: T[]): T[] {
   console.log(arg.length); // 배열은 .length를 가지고 있다. 따라서 오류는 없다.
   return arg; // 배열을 반환
}

function loggingIdentity2<T>(arg: Array<T>): Array<T> {
   console.log(arg.length);
   return arg;
}

loggingIdentity([1, 2, 3]);
loggingIdentity2([1, 2, 3]);
인터페이스 활용하는 방법
// 제네릭 인터페이스
interface Mobile<T> { 
   name: string;
   price: number;
   option: T; // 제네릭 타입 - option 속성에는 다양한 데이터 자료가 들어온다고 가정
}

// 제네릭 자체에 리터럴 객테 타입도 할당 할 수 있다.
const m1: Mobile<{ color: string; coupon: boolean }> = {
   name: 's21',
   price: 1000,
   option: { color: 'read', coupon: false }, // 제네릭 타입의 의해서 option 속성이 유연하게 타입이 할당됨
};

const m2: Mobile<string> = {
   name: 's20',
   price: 900,
   option: 'good', // 제네릭 타입의 의해서 option 속성이 유연하게 타입이 할당됨
};
Type Alias를 활용하는 방법
type TG<T> = T[] | T;

const number_arr: TG<number> = [1, 2, 3, 4, 5];
const number_arr2: TG<number> = 12345;

const string_arr: TG<string> = ['1', '2', '3', '4', '5'];
const string_arr2: TG<string> = '12345';
제약 조건 걸기
extends를 활용한 타입의 종류를 제한
type numOrStr = number | string;

// 제네릭에 적용될 타입에 number | string 만 허용
function identity<T extends numOrStr>(p1: T): T {
   return p1;
}

identity(1);
identity('a');

identity(true); //! ERROR
identity([]); //! ERROR
identity({}); //! ERROR
속성제약조건
interface Lengthwise {
  length: number;
}

// 제네릭 T 는 반드시 { length: number } 프로퍼티 타입을 포함해 있어야 한다.
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 이제 .length 프로퍼티가 있는 것을 알기 때문에 더 이상 오류가 발생하지 않습니다.
  return arg;
}

loggingIdentity(3); // 오류, number는 .length 프로퍼티가 없습니다.
loggingIdentity({ length: 10, value: 3 });

Observable

getBookList(): Observable<any> {
  return this.http.get(`${this.api}?q=query`)
  .pipe(map((books: any) => books.items || []));
}

변수초기화

angular에서는 변수설정시 초기값을 넣거나 혹은 any로 정의하거나 혹은 constructor에서 값을 정의하기를 권장합니다.
하지만 그렇지 못할 경우 '!' 이나 '?'을 사용합니다.

private anyvalue: any; // ok
private setvalueonconstrouct: number; // ok, 아래 constructor에서 정의
private mystring: string; // 경고발생, 이때 ! 나 ? 을 사용하여 경고를 없앱니다.
constructor() {
  this.setvalueonconstrouct = 3;
}

!

값이 할당되지 않았지만 프로그램에서 할당 됩니다.

private notdefined_var!: string

?

undefined로 할당됩니다.

private undefined_var?: string

다양한 예제

T 혹은 콜백함수 전달

export function useConst<T>(initialValue: T | (() => T)): any {
  const ref = useRef<{ value: T }>(undefined);
}
callback 1
function doSomething<T>(t: T): void {
    console.log("oh " + t);
}

type Handler = <T>(t: T) => void;
function thisFirst(callback: Handler) {
  callback<string>('boink');
}

thisFirst(doSomething)

const handleSomething =
  <T extends (...args: Parameters<T>) => ReturnType<T>>(callback: T) =>
  (...args: Parameters<T>) =>
    callback(...args)
평점을 남겨주세요
평점 : 5.0
총 투표수 : 1

질문 및 답글