[TypeScript] TypeScript를 처음 배우는 JS 개발자를 위해

muz·2021년 4월 8일
0
post-thumbnail

TS for the JS Programmer

TS 타입 시스템은 JS의 기능들을 제공하면서 그 위에 자체 레이어를 추가한다.

JS는 이미 string, number, object, undefined 같은 원시타입을 가지고 있으나 전체 코드베이스에 일관성있게 할당되었는지는 미리 확인해 주지 않는다. TS는 이 레이어로서 동작한다. 잘 동작하는 JS코드는 TS코드이기도 하나 TS의 타입 검사자는 사용자가 생각한 일과 JS가 실제로 하는 일 사이의 불일치를 나타낼 수 있다.

1. 타입 추론(Types by Inference)

TS는 보통 타입을 생성해준다.

let helloWorld = "Hello World";
// 변수 선언과 동시에 값을 할당하면 TS는 타입을 가지는 '타입 시스템'을 구축할 수 있다. 

TS가 helloWorldstring임을 알게 되는 방식이다.

2. 타입 정의하기(Defining Types)

JS는 다양한 디자인 패턴을 가능케하는 동적 언어이다. 몇몇 디자인 패턴은 자동으로 타입을 제공하기 힘들 수 있어 이러한 경우 TS는 JS언어 확장을 지원한다.

ex) name:stringid:number를 포함하는 추론 타입을 가진 객체 생성 예제를 보자.

const user = { 
	name:"Hayes", 
    	id: 0, 
};
  • 이 객체의 형태를 명시적으로 나타내기 위해서는 interface로 선언한다.
interface User {
	name: string;
    	id: number;
}
  • 변수 선언 뒤에 : TypeName의 구문으로 JS 객체가 새로운 interface 형태를 따르고 있음을 선언할 수 있다.
interface User {
	name: string;
    	id: number;
}
...
const user: User = {
	name: "Hayes",
    	id: 0,
};
  • 해당 인터페이스에 맞지 않는 객체 생성 시 TS는 경고한다.
// @errors: 2322
interface User {
	name: string;
    	id: number;
}
const user: User = {
	username: "Hayes",
    	id: 0,
};
  • TS또한 클래스와 객체지향 프로그래밍을 지원한다. 그러므로 인터페이스는 클래스로도 선언이 가능하다.
interface User {
	name: string;
   	 id: number;
}

class UserAccount {
	name: string;
    	id: number;
    
    	constructor(name: string, id: number) {
    		this.name = name;
        	this.id = id;
 	}
}
const user: User = new UserAccount("Murphy", 1);
  • 인터페이스는 함수에서 매개변수, 리턴 값을 명시하는 데에 사용되기도 한다.
interface User {
	name: string;
    	id: number;
}
function getAdminUser(): User {
... }
function deleteUser(user: User) {
... }

JS에서 사용 가능한 원시 타입

  • boolean, bigint, null, number, string, symbol, object, undefined

TS에서는 JS에서 사용 가능한 원시타입에다가 몇 가지를 더 제공함

  • any : 무엇이든 허용함
  • unknown : 이 타입을 사용하는 사람이 타입을 선언했는지를 확인하기
  • never : 이 타입은 발생될 수 없음
  • void : undefined을 리턴하거나, 리턴 값이 없는 함수일 때

타입 구축을 위한 2가지 구문이 있다.
interface를 우선적으로 사용하고, 특정 기능이 필요할 때 type을 사용해야 한다.

3. 타입 구성(Composing Types)

객체들을 조합해 더 크고 복잡한 객체를 만드는 방법처럼, TS에 타입으로 이를 수행하는 도구가 있다. 여러 타입을 이용해 새 타입을 작성하기 위해 일상적인 코드에서 가장 많이 사용되는 코드로는 '유니언(Union)''제네릭(Generic)'이 있다.

1) 유니언(Unions)

유니언은 타입이 여러 타입 중 하나일 수 있음을 선언하는 방법이다.
예를 들어 boolean 타입을 true or false로 설명할 수 있다.

type MyBool = true | false;

에디터에서 위의 코드를 작성한 후, MyBool위에 커서를 갖다대면 boolean으로 분류되는 것을 볼 수 있다. (구조적 타입 시스템의 프로퍼티)

유니언 타입이 가장 많이 사용되는 예제 중 하나는 값이 다음처럼 허용되는 string 또는 number리터럴 집합을 설명하는 것이다.

type WidnowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type OddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;

유니언은 다양한 타입을 처리하는 방법을 제공한다. 예를 들어 array or string을 받는 함수가 있을 수 있다.

function getLength(obj: string | string[]) {
	return obj.length;
}

TS는 코드가 시간에 따라 변수가 변경되는 방식을 이해하며, 검사를 통해 타입을 골라낼 수 있다.

예를 들어 typeof obj === "string"을 이용하여 stringarray를 구분할 수 있고, TS는 객체가 다른 코드 경로에 있음을 알게 된다.

function wrapInArray(obj: string | string[]) {
	if(typeof obj === "string") {
    	return [obj];
// ^?
	} else {
    	return obj;
    }
}

2) 제네릭(Generics)

제네릭은 타입에 변수를 제공하는 방법 이다. 배열이 일반적인 예시이다.

  • 제네릭이 없는 배열 : 어떤 것이든 포함할 수 있음
  • 제네릭이 있는 배열 : 배열 안의 값을 설명할 수 있음
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string };

제네릭을 사용하는 고유 타입을 선언할 수 있다.

// @errors: 2345
interface Backpack<Type> {
  add: (obj: Type) => void;
  get: () => Type;
}

// 이 줄은 TypeScript에 `backpack`이라는 상수가 있음을 알리는 지름길이며
// const backpack: Backpack<string>이 어디서 왔는지 걱정할 필요가 없습니다.
declare const backpack: Backpack<string>;

// 위에서 Backpack의 변수 부분으로 선언해서, object는 string입니다.
const object = backpack.get();

// backpack 변수가 string이므로, add 함수에 number를 전달할 수 없습니다.
backpack.add(23);

4. 구조적 타입 시스템(Structural Type System)

TS의 핵심 원칙 중 하나는 타입 검사가 값이 있는 형태에 집중한다는 것이다. 이를 '덕타이핑(duck typing)' 또는 '구조적 타이핑'이라고 부른다.

구조적 타입 시스템에서 두 객체가 같은 형태를 가지면 같은 것으로 간주된다.

interface Point {
	x: number;
    y: number;
}
function printPoint(p: Point) {
	console.log(`${p.x}, ${p.y}`);
} 
const point = { x: 12, y: 26};
printPoint(point); //"12,26"을 출력한다.

point 변수는 Point 타입으로 선언된 적이 없지만 TS는 타입 검사에서 point의 형태와 Point의 형태를 비교한다. 둘 다 같은 형태이므로 통과한다.

형태 일치에는 일치시킬 객체 필드의 하위 집합만 필요하다.

// @errors: 2345
interface Point {
  x: number;
  y: number;
}

function printPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}
// ---cut---
const point3 = { x: 12, y: 26, z: 89 };
printPoint(point3); // prints "12, 26"

const rect = { x: 33, y: 3, width: 30, height: 80 };
printPoint(rect); // prints "33, 3"

const color = { hex: "#187ABF" };

printPoint(color);

구조적으로 클래스와 객체가 형태를 따르는 방법에는 차이가 없다.

// @errors: 2345
interface Point {
  x: number;
  y: number;
}

function printPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}
// ---cut---
class VirtualPoint {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const newVPoint = new VirtualPoint(13, 56);
printPoint(newVPoint); // prints "13, 56"

객체 or 클래스에 필요한 모든 속성이 존재한다면, TS는 구현 세부 정보에 관계없이 일치하게 본다.


cf. TypeScript 핸드북

profile
Life is what i make up it 💨

0개의 댓글