[TS] TypeScript 다시 공부하기 1

약 3개월 전 TypeScript를 공부해 놓고 계속 JavaScript만 썼더니 거의 다 잊어버렸다,,, ^^ 그래서 스터디 준비할 겸 다시 꼼꼼히 공부해 보자

참고 링크 - 타입스크립트 핸드북


Fundamentals

1. 기본 타입

  • Boolean, Number, String, Object, Array, Tuple, Enum, Any, Void, Null, Undefined, Never
  • Type Annotation: :를 이용하여 타입을 명시하는 방법

(1) String

let text: string = 'hello';

(2) Number

let num: number = 17;

(3) Boolean

let isDone: boolean = false;

(4) Object

let obj: 

(5) Array

let arr: number[] = [1, 2, 3];
let arr: Array<number> = [1, 2, 3]; // 제네릭 사용

(6) Tuple

배열의 길이가 고정되고 요소의 타입이 지정되어 있는 배열

let arr: [string, number] = ['hello', 10];

(7) Enum

특정 상수 값들의 집합

enum Avengers {Capt, IronMan, Thor}
let hero: Avengers = Avengers.Capt;

아래처럼 enum의 인덱스를 마음대로 변경 가능

enum Avengers {Capt, IronMan, Thor}
let hero: Avengers = Avengers[2]; // Capt
let hero: Avengers = Avengers[4]; // Thor

(8) Any

let arr: any = ['a', 1, true];

(9) Void

변수에는 undefinednull만 할당
함수에는 반환값 설정 불가

let unuseful: void = undefined;

function notuse(): void {
	console.log('sth');
}

(10) Never

함수 끝까지 실행되지 않는다는 의미

function neverEnd(): never {
	while (true) {
    
    }
}

2. 함수

함수를 사용할 때에는 크게 3가지에 대한 타입을 정의해야 한다.

  • 매개변수 타입
  • 반환 타입
  • 구조 타입

(1) 함수의 기본적인 타입 선언

매개변수와 반환값에 타입 추가하기
함수 반환값에 타입을 정하지 않을 때에는 void 사용

function sum(a: number, b: any): number {
	return a + b;
}

(2) 함수의 인자

정의된 매개변수 값과 개수만큼만 인자를 받는다.
만약 정의된 매개변수 개수만큼 넘기고 싶지 않다면 ?를 이용한다.

function sum(a: number, b?: number): number {
	return a + b;
}
                                               
sum(10); // okay
sum(10, 20, 30); // error: too many parameters

3. Interface

아래 범주에 대한 약속을 정의

  • 객체의 속성과 속성 타입
  • 함수의 파라미터
  • 함수의 파라미터, 반환타입
  • 배열과 객체 접근법
  • 클래스

(1) readonly 속성

인터페이스로 객체를 처음 생성할 때에만 값을 할당하고 그 이후에는 변경 불가

interface CraftBeer {
	readonly brand: string;
}
let arr: ReadonlyArray<number> = [1, 2, 3]; // 읽기 전용 배열

(2) 객체 선언과 타입 체킹

interface로 선언한 객체의 key 값이 다를 경우 에러 발생
위 같은 에러를 무시하고 싶다면... as

interface CraftBeer {
	brand?: string;
}

function brewBeer(beer: Craftbeer){

}

let myBeer = {brandon: 'what'};
brewBeer(myBeer as CraftBeer);

(3) 함수 타입

interface login {
	(username: string, password: string): boolean;
}

let loginUser: login;

loginUser = function(id: string, pw: string){
	return true;
}

4. Enums

숫자형 이넘과 문자형 이넘이 존재
그런데 이넘에 대해 더 찾아보다가 이런 글을 발견했다. 아래 글에서는 enum보다는 Union Types를 사용할 것을 권장한다.
TypeScript enum을 사용하지 않는 게 좋은 이유를 Tree-shaking 관점에서 소개합니다.

(1) 숫자형 이넘

enum Direction {
	Up = 1,
  	Down,
  	Left,
 	Right
}

위처럼 초깃값을 주면 차례대로 1씩 부여된다.
초깃값이 없으면 0부터 1씩 증가한다.

(2) 숫자형 이넘 사용하기

enum Response {
	No = 0,
  	Yes = 1
}

function respond(recipient: string, message: Response): void {

}

respond("Me", Response.Yes);

(3) 문자형 이넘

문자형 이넘은 이넘 값 전부 특정 문자 또는 다른 이넘 값으로 초기화해야 한다.
숫자형 이넘과 다르게 auto-incrementing이 없다.

enum Direction {
	Up = "UP",
  	Down = "DOWN",
  	Left = "LEFT",
  	Right = "RIGHT"
}

(4) 복합 이넘 (Heterogeneous Enums)

이넘에 문자와 숫자를 혼합하여 사용할 수는 있지만 권고하진 않는다.

enum BooleanLikeHeterogeneousEnum {
	No = 0,
  	Yes = "YES"
}

5. 연산자를 이용한 타입 정의

(1) Union Type

JavaScript의 OR 연산자(||)와 같은 의미의 타입

function logText(text: string | number){
	// ...
}

(2) Intersection Type

여러 타입을 모두 만족하는 하나의 타입

interface Person {
	name: string;
  	age: number;
}

interface Developer {
	name: string;
  	skill: number;
}

type Capt = Person & Developer;

위처럼 하면 Capt의 타입은 아래와같이 된다.

{
	name: string;
  	age: number;
  	skill: number;
}

(3) Union Type을 쓸 때 주의할 점

interface Person {
    name: string;
  	age: number;
}

interface Developer {
	name: string;
  	skill: number;
}

function introduce(someone: Person | Developer) {
	someone.name; //  Ok
  	someone.age; // Type error
  	someone.skill; // Type error
}

위와 같은 에러가 발생하는 이유는...
introduce() 함수를 호출하는 시점에 Person 타입이 올지 Developer 타입이 올지 알 수 없기 때문에 어느 타입이 오든 오류가 나지 않는 방향으로 타입을 추론한다.

const capt: Person = {name: 'capt', age: 100};
introduce(capt); // someone.skill 속성 접근 시 에러

const tony: Developer = {name: 'tony', skill: 100};
introduce(tony); // someone.age 속성 접근 시 에러

PersonDeveloper 두 타입에 공통적으로 들어 있는 속성name만 접근 가능한 것이다!


6. Generic

(1) Generic의 사전적 정의

재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다. 특히 여러 가지 타입에서 동작하는 컴포넌트를 생성하는 데 사용된다.

(2) Generic의 한 줄 정의와 예시

Generic은 타입을 함수의 파라미터처럼 사용하는 것을 의미한다.

function getText<T>(text: T): T {
	return text;
}

위 형식은 제네릭 기본 문법이 적용된 형태이고, 이 함수를 호출할 때에 타입을 넘겨 줄 수 있다.

getText<string>('hi');
getText<number>(10);
getText<boolean>(true);

(3) Generic을 사용하는 이유

function logText<T>(text: T): T {
	return text;
}

이렇게 함수를 선언하면 아래와같이 함수를 호출할 수 있다.

// 1
const text = logText<string>("Hello Generic");
// 2
const text = logText("Hello Generic");

보통 두 번째 방법이 가독성이 좋아서 더 많이 사용된다. 하지만 복잡한 코드에서 두 번째 방법으로 타입 추정이 되지 않는다면 첫 번째 방법을 사용하자.

(4) Generic 타입 변수

만약 함수의 인자로 받은 값의 length를 확인하려 한다면 다음과 같은 에러가 뜬다.

function logText<T>(text: T): T {
	console.log(text.length); // Error: T doesn't have .length
  	return text;
}

따라서 이런 경우에는 아래와같이 제네릭에 타입을 부여할 수 있다.

function logText<T>(text: T[]): T[] {
	console.log(text.length); // 제네릭 타입이 배열이라 length를 허용
  	return text;
}

or

function logText<T>(Text: Array<T>): Array<T> {
	console.log(text.length);
  	return text;
}

이런 식으로 제네릭을 사용하면 유연하게 함수의 타입을 정의할 수 있다.

(5) Generic 타입

function logText<T>(text: T): T {
	return text;
}

let str: <T>(text: T) => T = logText; // 1
let str: {<T>(text: T): T} = logText; //2 

위 코드에서 1번과 2번은 같은 의미이다.

interface GenericLogTextFn {
	<T>(text: T): T;
}

function logText<T>(text: T): T {
	return text;
}

let myString: GenericLogTextFn = logText;

위에처럼도 작성할 수 있고, 만약 인터페이스에서 인자 타입을 강조하고 싶다면 아래처럼도 쓸 수 있다.

interface GenericLogTextFn<T> {
	(text: T): T;
}

function logText<T>(text: T): T {
	return text;
}

let myString: GenericLogTextFn<string> = logText;

이런 식으로 제네릭 인터페이스뿐만 아니라 클래스도 생성할 수 있지만, 이넘과 네임스페이스는 제네릭으로 생성할 수 없다.

(6) Generic 제약 조건

function logText<T>(text: T): T {
	console.log(text.length); // Error: T doesn't have .length
  	return text;
}

이때 에러가 발생한느데, 타입을 지정하지 않고도 length 속성 정도는 허용하려면 아래와같이 작성하면 된다.

Interface LengthWise {
	length: number;
}

function logText<T extends LengthWise>(text: T): T {
	console.log(text.length);
  	return text;
}

위와같이 작성하면 length에 대해 동작하는 인자만 넘겨 받을 수 있게 된다.

또한 두 객체를 비교할 때에도 제네릭 제약 조건을 사용할 수 있다.

function getProperty<T, O extends keyof T> (obj: T, key: O) {
	return obj[key];
}

let obj = {a: 1, b: 2, c: 3};

getProperty(obj, "a"); // Ok
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.

위 코드에서 <O extends keyof T> 부분이 첫 번째 인자로 받은 객체에 없는 속성을 접근할 수 없게끔 제한하는 코드다.


7. Type Inference

타입 추론: 타입스크립트가 코드를 해석해 나가는 동작

타입 체크는 갑스이 형태에 기반하여 이루어져야 한다.
Duck Typing: 객체의 변수 및 메서드의 집합이 객체의 타입을 결정 (동적 타이핑의 한 종류)
Structural Subtyping: 객체의 실제 구조나 정의에 따라 타입을 결정


8. Type Compatibility

(1) 구조적 타이핑 예시

코드 구조 관점에서 타입이 서로 호환되는지의 여부를 판단

interface Avengers {
	name: string;
}

let hero: Avengers;
let capt = {name: "Capt", location: "Seoul"};
hero = capt;

capthero 타입에 호환될 수 있는 이유는 capt의 속성 중에 name이 있기 때문이다. 함수를 호출할 때에도 마찬가지이다.

function assemble(a: Avengers) {
	console.log("어벤저스", a.name);
}

assemble(capt);

capt 변수에 location 속성도 있기 때문에 assemble 함수의 호출 인자로 넘길 수 있다.

(2) Soundness

TypeScript는 컴파일 시점에 타입을 추론할 수 없는 특정 타입에 대해서 일단 안전하다고 보는 특성이 있다.

(3) Enum 타입 호환 주의 사항

Enum 타입은 number 타입과 호환되지만, Enum 타입끼리는 호환되지 않는다.

enum Status {Ready, Waiting};
enum Color {Red, Blue, Green};

let status = Status.Ready;
status = Color.Green; // Error

(4) Generics

제네릭은 제네릭 타입 간 호환 여부를 판단할 때 타입 인자 <T>가 속성에 할당되었는지를 기준으로 한다.

interface Empty<T> {
}

let x: Empty<number>;
let y: Empty<string>;

x = y; // Ok, because y matches structure of x

위 인터페이스는 속성(멤버 변수)이 없기 때문에 xy는 같은 타입으로 간주된다.

하지만 아래와 같이 속성이 있어서 제네릭의 타입 인자가 속성에 할당되면 xy는 다른 타입으로 간주된다.

interface NotEmpty<T> {
	data: T;
}

let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y; // Error, because x and y are not compatible

9. Type Aliases

타입 별칭은 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미한다.

// string 타입을 사용할 때
const name: string = 'capt';

// type aliases를 사용할 때
type MyName = string;
const name: MyName = 'capt';
type Developer = {
	name: string;
  	skill: string;
}

type User<T> = {
	name: T
}

(1) 타입 별칭의 특징

새로운 타입값을 생성하는 게 아니라 정의한 타입에 대해 쉽게 참고할 수 있도록 이름을 부여하는 것이다.

(2) type vs. interface

이 둘의 가장 큰 차이점은 확장 가능 여부이다.
type은 확장이 불가능하고, interface는 확장 가능하다. 따라서 가능한 interface 사용을 추천한다.


어우 힘들어
타입스크립트 핸드북

좋은 웹페이지 즐겨찾기