객체: Object
- 기본적으로 typeof 연산자가 object 로 반환하는 모든 타입을 나타냅니다.
컴파일러 옵션에서 엄격한 타입 검사(strict)를 true로 설정하면, null은 포함하지 않습니다.
1
2
3
4
5
| let obj: object = {};
let arr: object = [];
let func: object = function () {};
let nullValue: object = null;
let date: object = new Date();
|
- 정확하게 타입 지정을 하기 위해 다음과 같이 객체 속성(Properties)들에 대한 타입을 개별적으로 지정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
| let userA: { name: string, age: number } = {
name: 'yurihaha',
age: 123,
};
let userB: { name: string, age: number } = {
name: 'yurihaha',
age: false, // Error
email: 'gkdbfl1004@gmail.com', // Error
};
|
- 반복적인 사용을 원하는 경우, interface나 type을 사용하는 것을 추천합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| interface IUser {
name: string;
age: number;
}
let userA: IUser = {
name: 'yurihaha',
age: 29,
};
let userB: IUser = {
name: 'yurihaha',
age: false, // Error
email: 'gkdbfl1004@gmail.com', // Error
};
|
Null과 Undefined
- 기본적으로 Null과 Undefined는 모든 타입의 하위 타입 (서로의 타입에도 할당 가능)
1
2
3
4
5
6
7
| let num: number = undefined;
let str: string = null;
let obj: { a: 1, b: false } = undefined;
let arr: any[] = null;
let und: undefined = null;
let nul: null = undefined;
let voi: void = null;
|
이는 컴파일 옵션 "strictNullChecks": true을 통해 엄격하게 Null과 Undefined 서로의 타입까지 더 이상 할당할 수 없게 합니다. 단, Void에는 Undefined를 할당할 수 있습니다.
1
| let voi: void = undefined; //가능
|
Void
- Void는 일반적으로 값을 반환하지 않는 함수에서 사용합니다.
: void 위치는 함수가 반환 타입을 명시하는 곳
1
2
3
4
5
6
7
8
9
10
11
12
| function hello(msg: string): void {
console.log(`Hello ${msg}`);
}
// 이경우는 undefined
const hi: void = hello('world'); // Hello world
console.log(hi); // undefined
// Error - TS2355
function hello(msg: string): undefined {
console.log(`Hello ${msg}`);
}
|
Union 유니언
- 2개 이상의 타입을 허용하는 경우, 이를 유니언(Union)이라고 합니다.
|를 통해 타입을 구분하며, ()는 선택 사항입니다.
1
2
3
4
| let union: string | number;
union = 'Hello type!';
union = 123;
union = false; // Error - TS2322: Type 'false' is not assignable to type 'string | number'.
|
함수(Function)
- 화살표 함수를 이용해 타입을 지정할 수 있고, 인수의 타입과 반환 값의 타입을 입력합니다.
1
2
3
4
5
6
7
8
9
10
11
| // myFunc는 2개의 숫자 타입 인수를 가지고, 숫자 타입을 반환하는 함수.
let myFunc: (arg1: number, arg2: number) => number;
myFunc = function (x, y) {
return x + y;
};
// 인수가 없고, 반환도 없는 경우.
let yourFunc: () => void;
yourFunc = function () {
console.log('Hello world~');
};
|
타입 추론(Inference)
- 명시적으로 타입 선언이 되어있지 않은 경우, 타입스크립트는 타입을 추론해 제공합니다.
- 변수 num을 초기화하면서 숫자 12를 할당해 Number 타입으로 추론되었고, 따라서 ‘Hello type!’이라는 String 타입의 값은 할당할 수 없기 때문에 에러가 발생합니다.
1
2
| let num = 12;
num = 'Hello type!'; // TS2322: Type '"Hello type!"' is not assignable to type 'number'.
|
타입 단언(Assertions)
1
2
3
4
5
6
| function someFunc(val: string | number, isNumber: boolean) {
// some logics
if (isNumber) {
val.toFixed(2); // Error - TS2339: ... Property 'toFixed' does not exist on type 'string'.
}
}
|
- 따라서 우리는 isNumber가 true일 때 val이 숫자임을 다음과 같이 2가지 방식으로 단언할 수 있습니다.
두 번째 방식(val)은 .tsx 파일에서는 전혀 사용할 수 없습니다.
1
2
3
4
5
6
7
8
9
10
| function someFunc(val: string | number, isNumber: boolean) {
// some logics
if (isNumber) {
// 1. 변수 as 타입
(val as number).toFixed(2);
// Or
// 2. <타입>변수
// (<number>val).toFixed(2);
}
}
|
Non-null 단언 연산자
!를 사용하는 Non-null 단언 연산자(Non-null assertion operator)를 통해 피연산자가 Nullish(null이나 undefined) 값이 아님을 단언할 수 있는데, 변수나 속성에서 간단하게 사용할 수 있기 때문에 유용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // Error - TS2533: Object is possibly 'null' or 'undefined'.
function fnA(x: number | null | undefined) {
return x.toFixed(2);
}
// if statement
function fnD(x: number | null | undefined) {
if (x) {
return x.toFixed(2);
}
}
// 타입단언 (Type assertion)
function fnB(x: number | null | undefined) {
return (x as number).toFixed(2);
}
function fnC(x: number | null | undefined) {
return (<number>x).toFixed(2);
}
// Non-null assertion operator
function fnE(x: number | null | undefined) {
return x!.toFixed(2);
}
|
타입 가드(Guards)
- 다음 예제와 같이 val의 타입을 매번 보장하기 위해 타입 단언을 여러 번 사용하게 되는 경우가 있습니다.
1
2
3
4
5
6
7
8
9
10
| function someFunc(val: string | number, isNumber: boolean) {
if (isNumber) {
(val as number).toFixed(2);
isNaN(val as number);
} else {
(val as string).split('');
(val as string).toUpperCase();
(val as string).length;
}
}
|
- 타입 가드는 NAME is TYPE 형태의 타입 술부(Predicate)를 반환 타입으로 명시한 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 위에 예제를 타입가드를 사용함
function isNumber(val: string | number): val is number {
return typeof val === 'number';
}
function someFunc(val: string | number) {
if (isNumber(val)) {
val.toFixed(2);
isNaN(val);
} else {
val.split('');
val.toUpperCase();
val.length;
}
}
|
타입가드 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| // 기존 예제와 같이 `isNumber`를 제공(추상화)하지 않아도 `typeof` 연산자를 직접 사용하면 타입 가드로 동작합니다.
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/typeof
function someFuncTypeof(val: string | number) {
if (typeof val === 'number') {
val.toFixed(2);
isNaN(val);
} else {
val.split('');
val.toUpperCase();
val.length;
}
}
// 별도의 추상화 없이 `in` 연산자를 사용해 타입 가드를 제공합니다.
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/in
function someFuncIn(val: any) {
if ('toFixed' in val) {
val.toFixed(2);
isNaN(val);
} else if ('split' in val) {
val.split('');
val.toUpperCase();
val.length;
}
}
// 역시 별도의 추상화 없이 `instanceof` 연산자를 사용해 타입 가드를 제공합니다.
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/instanceof
class Cat {
meow() {}
}
class Dog {
woof() {}
}
function sounds(ani: Cat | Dog) {
if (ani instanceof Cat) {
ani.meow();
} else {
ani.woof();
}
}
|
인터페이스
- 인터페이스(Interface)는 타입스크립트 여러 객체를 정의하는 일종의 규칙이며 구조입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| interface IUser {
name: string;
age: number;
isAdult: boolean;
}
let user1: IUser = {
name: 'Neo',
age: 123,
isAdult: true,
};
// Error - TS2741: Property 'isAdult' is missing in type '{ name: string; age: number; }' but required in type 'IUser'.
let user2: IUser = {
name: 'Evan',
age: 456,
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| interface IUser {
name: string,
age: number
}
// Or
interface IUser {
name: string;
age: number;
}
// Or
interface IUser {
name: string
age: number
}
|
- 다음과 같이 속성에
? 를 사용하면 선택적 속성으로 정의할 수 있습니다. 선택적 속성(Optional properties)은 간단하게 표현하면 ‘필수가 아닌 속성으로 정의’하는 방법을 말합니다.
1
2
3
4
5
6
7
8
9
10
11
| interface IUser {
name: string;
age: number;
isAdult?: boolean; // Optional property
}
// `isAdult`를 초기화하지 않아도 에러가 발생하지 않습니다.
let user: IUser = {
name: 'Neo',
age: 123,
};
|
읽기 전용 속성(Readonly properties)
readonly 키워드를 사용하면 초기화된 값을 유지해야 하는 읽기 전용 속성을 정의할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
| interface IUser {
readonly name: string,
age: number
}
// 초기화
let user: IUser = {
name: 'Neo',
age: 36
};
user.age = 85; // Ok
user.name = 'Evan'; // Error - TS2540: Cannot assign to 'name' because it is a read-only property.
|
- 만약 모든 속성이
readonly일 경우, 유틸리티(Utility)나 단언(Assertion) 타입을 활용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // All readonly properties
interface IUser {
readonly name: string,
readonly age: number
}
let user: IUser = {
name: 'Neo',
age: 36
};
user.age = 85; // Error
user.name = 'Evan'; // Error
// Readonly Utility
interface IUser {
name: string,
age: number
}
let user: Readonly<IUser> = {
name: 'Neo',
age: 36
};
user.age = 85; // Error
user.name = 'Evan'; // Error
// Type assertion
let user = {
name: 'Neo',
age: 36
} as const;
user.age = 85; // Error
user.name = 'Evan'; // Error
|
함수타입
- 함수 타입을 인터페이스로 정의하는 경우, 호출 시그니처(Call signature)라는 것을 사용합니다.
- 호출 시그니처는 다음과 같이 함수의 매개 변수(parameter)와 반환 타입을 지정합니다.
1
2
3
| interface IName {
(PARAMETER: PARAM_TYPE): RETURN_TYPE; // Call signature
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| interface IUser {
name: string;
}
interface IGetUser {
(name: string): IUser;
}
// 매개 변수 이름이 인터페이스와 일치할 필요가 없습니다.
// 또한 타입 추론을 통해 매개 변수를 순서에 맞게 암시적 타입으로 제공할 수 있습니다.
const getUser: IGetUser = function (n) {
// n is name: string
// Find user logic..
// ...
return user;
};
getUser('yurihaha');
|
클래스타입
-
- 인터페이스로 클래스를 정의하는 경우,
implements 키워드를 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| interface IUser {
name: string,
getName(): string
}
class User implements IUser {
constructor(public name: string) {}
getName() {
return this.name;
}
}
const neo = new User('Neo');
neo.getName(); // Neo
|
-
- 그런데 만약 정의한 클래스를 인수로 사용하는 경우 다음과 같은 문제가 발생할 수 있습니다. 다음 예제에서 인터페이스 ICat은 호출 가능한 구조가 아니기 때문입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
| interface ICat {
name: string
}
class Cat implements ICat {
constructor(public name: string) {}
}
function makeKitten(c: ICat, n: string) {
return new c(n); // Error - TS2351: This expression is not constructable. Type 'ICat' has no construct signatures.
}
const kitten = makeKitten(Cat, 'Lucy');
console.log(kitten);
|
-
- 이를 위해 구성 시그니처(Construct signature)를 제공할 수 있습니다. 구성 시그니처는 위에서 살펴본 호출 시그니처와 비슷하지만, new 키워드를 사용해야 합니다.
1
2
3
| interface IName {
new(PARAMETER: PARAM_TYPE): RETURN_TYPE; // Construct signature
}
|
-
- (위에 내용 수정하면) ICatConstructor라는 구성 시그니처를 가지는 호출 가능한 인터페이스를 정의함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| interface ICat {
name: string
}
interface ICatConstructor {
new (name: string): ICat;
}
class Cat implements ICat {
constructor(public name: string) {}
}
function makeKitten(c: ICatConstructor, n: string) {
return new c(n); // ok
}
const kitten = makeKitten(Cat, 'Lucy');
console.log(kitten);
|
인덱싱 가능 타입(Indexable types)
- 수많은 속성을 가지거나 단언할 수 없는 임의의 속성이 포함되는 구조에서는 기존의 방식만으론 한계가 있습니다. 이런 상황에서 유용한 인덱스 시그니처
1
2
3
4
5
6
7
| interface IItem {
[itemIndex: number]: string; // Index signature
}
let item: IItem = ['a', 'b', 'c']; // Indexable type
console.log(item[0]); // 'a' is string.
console.log(item[1]); // 'b' is string.
console.log(item['0']); // Error - TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'.
|
1
2
3
4
5
6
7
| interface IItem {
[itemIndex: number]: string | boolean | number[];
}
let item: IItem = ['Hello', false, [1, 2, 3]];
console.log(item[0]); // Hello
console.log(item[1]); // false
console.log(item[2]); // [1, 2, 3]
|
출처: https://heropy.blog/2020/01/27/typescript/ 이분의 블로그로 공부한 흔적입니다.