타입정의하기
타입정의하기
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)