타입스크립트 2번째

객체: 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,
};
  • :(colon), ,(comma) 빼고 사용
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');

클래스타입

    1. 인터페이스로 클래스를 정의하는 경우, 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
    1. 그런데 만약 정의한 클래스를 인수로 사용하는 경우 다음과 같은 문제가 발생할 수 있습니다. 다음 예제에서 인터페이스 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);
    1. 이를 위해 구성 시그니처(Construct signature)를 제공할 수 있습니다. 구성 시그니처는 위에서 살펴본 호출 시그니처와 비슷하지만, new 키워드를 사용해야 합니다.
1
2
3
interface IName {
	new(PARAMETER: PARAM_TYPE): RETURN_TYPE; // Construct signature
}
    1. (위에 내용 수정하면) 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/ 이분의 블로그로 공부한 흔적입니다.