우아한 타입스크립트 요약 정리

🤔 타입스크립트를 사용하는 법

1. 작성자와 사용자

타입시스템

  • 컴파일러에게 사용하는 타입을 명시적으로 지정하는 시스템
  • 컴파일러가 자동으로 타입을 추론하는 시스템

타입스크립트의 타입시스템

  • 타입을 명시적으로 지정할 수 있다.

  • 타입을 명시적으로 지정하지 않으면, 타입스크립트 컴파일러가 자동으로 타입을 추론

    타입이란 해당 변수가 할 수 있는 일을 결정합니다.

함수 사용법에 대한 오해를 야기하는 자바스크립트

// JavaScript

// f2 실행의 결과가 NaN을 의도한 것이 아니라면
// 이 함수의 작성자는 매개변수 a 가 number 타입이라는 가정으로 함수를 작성했습니다.
function f2(a) {
	return a;
}

// 사용자는 사용법을 숙지하지 않은 채, 문자열을 사용하여 함수를 실행했습니다.
console.log(f2(10)); // 380
console.log(f2('Mark')); // NaN

noImplicitAny 옵션

타입을 명시적으로 지정하지 않은 경우, 타입스크립트가 추론 중 'any' 라고 판단하게 되면, 컴파일 에러를 발생시켜 명시적으로 지정하도록 유도한다.

strictNullChecks 옵션

모든 타입에 자동으로 포함되어 있는 'null'과 'undefined'를 제거해준다.

noImplicitReturns 옵션

함수 내에서 모든 코드가 값을 리턴하지 않으면, 컴파일 에러를 발생시킨다.

object literal type

function f7(a:{name:string; age: number}):string {
	return `이름은 ${a.name}이고 연령대는 ${Math.floor(a.age/10) * 10}대 입니다.`;
}

console.log(f7({name:'Mark',age:38})); // 이름은 Mark이고, 연령대는 30대 입니다.
console.log(f7('Mark')); // error TS2345: Argument of type 'string' is not assignable to parameter of type '{name:string; age:number;}'.

2. interface 와 type alias

structural type system - 구조가 같으면 같은 타입

interface IPerson{
	name: string;
	age: number;
	speak(): string;
}

type PersonType = {
	name: string;
	age: number;
	speak(): string;
};

let personInterface: IPerson = {} as any;
let personType: PersonType = {} as any;

personInterface = personType;
personType = personInterface;

nominal type system - 구조가 같아도 이름이 다르면 다른 타입

type PersonID = string & { readonly brand: unique symbol};

function PersonID(id:string):PersonID{
	return id as PersonID;
}

function getPersonById(id:PersonID){}

getPersonById(PersonID('id-aaaaaa'));
getPersonById('id-aaaaaa'); // error TS2345: Argument of type 'string' is not assignable to 
// parameter of type 'PersonID'. Type 'string' is not assignable to type '{readonly brand:unique symbol;}'.

Function

// type alias
type EatType = (food:string) => void;

// interface
interface IEat {
	(food:string): void;
}

Array

// type alias
type PersonList = string[];

// interface
interface IPersonList{
	[index:number]:string;
}

Intersection

interface ErrorHandling {
	success:boolean;
	error?:{ message: string };
}

interface ArtistsData {
	artists: { name: string }[];
}

// type alias
type ArtistsResponseType = Artists & ErrorHandling {}

// interface
interface IArtistsResponse extends ArtistsData, ErrorHandling{}

let art : ArtistsResponseType;
let iar : IArtistsResponse;

Union Types

interface Bird {
	fly(): void;
	layEggs(): void;
}

interface Fish {
	swim(): void;
	layEggs():void;
}

type PetType = Bird | Fish;

interface IPet extends PetType {} // error TS2312: An interface can only extend an object type or 
//intersection of object types with statically known members;

class Pet implements PetType {} // error TS2422: A class can only implement an object type or 
//intersection of object types with statically known members

Declation Merging - interface

interface MergingInterface {
	a: string;
}

interface MergingInterface {
	b: string;
}

let mi: MergingInterface = { a: 'hello', b: 'world' };

다른 모듈/라이브러리의 타입을 확장하고 싶을 때 global.d.ts 생성 후 namespace 안에서 사용

[ ❓ type alias 와 interface는 언제 사용하는가 ? ]

  • type alias : 기존 타입에 별칭, union type, intersection type
  • interface : 새로운 타입 생성

3. 서브 타입과 슈퍼 타입

Case 1

let sub1: 1 = 1;
let sup1: number = sub1;
sub1 = sup1; // error Type 'number' is not assignable to type '1';

let sub2: number[] = [1];
let sup2: object = sub2;
sub2 = sup2; // error Type 'number[]' is not assignable to type '{number,number}'

let sub3: [number,number] = [1,2];
let sup3: number[] = sub3;
sub3 = sup3; // error Type 'number[]' is not assignable to type '[number,number]'.
// Target requires 2 element(s) but source may have lower.

Case 2

let sub4: number = 1;
let sup4: any = sub4;
sub4 = sup4;

let sub5: never = 0 as never;
let sup5: number = sub5;
sub5 = sup5; // error Type 'number' is not assignable to type 'never'

class SubAnimal {}
class SubDog extends SubAnimal{
	eat(){}
}

let sub6: SubDog = new SubDog();
let sup6: SubAnimal = sub6;
sub6 = sup6; // error Property 'eat' is missing type 'SubAnimal' but required in type 'SubDog'.

공변 : 같거나 서브타입인 경우, 할당이 가능하다.

// primitive type
let sub7: string = '';
let sup7: string | number = sub7;

// object - 각각의 프로퍼티가 대응하는 프로퍼티와 같거나 서브타입이어야 한다.
let sub8 : { a: string; b: number } = { a: '', b: 1 };
let sup8 : { a: string | number; b : number } = sub8;

// array - object와 마찬가지
let sub9 : Array<{ a: string; b: number }> = [{a: '', b: 1 }];
let sup9 : Array<{ a: string | number ; b: number }> = sub9;

반병 : 함수의 매개변수 타입만 같거나 슈퍼타입인 경우, 할당이 가능하다.

class Person {}
class Developer extends Person {
	coding() {}
}
class StartupDeveloper extends Developer{
	burning() {}
}

function tellme(f: (d: Developer)=> Developer) {}

// Developer 에다가 Developer 할당하는 경우
tellme(function dToD(d:Devleopr):Developer {
	return new Developer();
});

// Developer 에다가 Person 할당하는 경우
tellme(function pToD(d:Person):Developer{
	return new Developer();
});

// Developer 에다가 StartupDeveloper 할당하는 경우 (타입에러는 나지 않지만 기대하는 결과X)
tellme(function sToD(d:StartupDeveloper):Developer {
	return new Developer();
});

strictFunctionTypes 옵션

함수의 매개변수 타입만 같거나 슈퍼타입의 경우가 아닌 경우, 에러를 통해 경고한다.

any 대신 unknown

// 입력은 마음대로, 
// 함수 구현은 문제 없도록
function funknown(a: unknown): number | string | void {
	a.toString(); // error! Object is of type 'unknown'
	
	// 명확하게 타입별로 처리하도록 유도 
	if(typeof a === 'number'){
		return a * 38;
	}else if(typeof a === 'string'){
		return `Hello ${a}`;
	}
}

4. 타입 추론 이해하기

let a = 'Mark'; // string
const b = 'Mark'; // 'Mark' => literal type

let c = 38; // number
const d = 38; // 38 => literal type

let e = false; // boolean
const f = false; // false => literal type

let g = ['Mark', 'Haeun']; // string[]
const h = ['Mark','Haeun']; // string[]

const f = ['Mark','Haeun'] as const; // readonly ['Mark','Haeun']

Best common type


let j = [0,1,null]; // (number|null)[]
const k = [0,1,null]; // (number|null)[]

class Animal{}
class Rhino extends Animal{}
class Elephant extends Animal{}
class Snake extends Animal{}

let i = [new Rhino(),new Elephant(),new Snake()]; // (Rhino | Elephant|Snake)
const m = [new Rhino(),new Elephant(),new Snake()]; // (Rhino | Elephant|Snake)

const n = [new Animal(),new Rhino(),new Elephant(),new Snake()]; // Animal[]
const o: Animal[] = [new Rhino(),new Elephant(),new Snake()]; // Animal[]

Contextual Typing - 위치에 따라 추론이 다름


// Parameter 'e' implicitly has an 'any' type.
const click = (e) => {
	e; // any
};

document.addEventListener('click',click);
document.addEventListener('click',(e)=>{
	e; // MouseEvent
});

5. Type Guard로 안전함을 파악하기

typeof Type Guard - 보통 Primitive 타입일 경우

function getNumber(value: number | string):number{
	value; // number | string
	if(typeof value === 'number'){
		value; // number
		return value;
	}
	value; // string
	return -1;
}

instanceof Type Guard

interface IMachine {
	name: string;
}

class Car implements IMachine {
	name: string;
	wheel: number;
}

class Boar implements IMachine {
	name: string;
	motor: number;
}

function getWhellOrMotor(machine: Car | Boat): number{
	if(machine instanceof Car){
		return machine.wheel; // Car
	}else {
		return machine.motor; // Boat
	}
}

instanceof Type Guard - Error 객체 구분에 많이 쓰인다.

class NegativeNumberError extends Error {}

function getNumber(value: number): number | NegativeNumberError {
	if(value < 0) return new NegativeNumberError();
	return value;
}

function main(){
	const num = getNumber(-10);

	if(num instanceof NegativeNumberError){
		return;
	}

	num; // number
}

in operator Type Guard - object의 프로퍼티 유무로 처리하는 경우

interface Admin {
	id: string;
	role: string;
}

interface User {
	id: string;
	email: string;
}

function redirect(user: Admin | User) {
	if("role" in user){
		routeToAdminPage(user.role);
	}else{
		routeToHomePate(user.email);
	}
}

literal Type Guard - object의 프로퍼티가 같고, 타입이 다른 경우

interface IMachine {
	type: string;
}

class Car implements IMachine {
	type: 'CAR';
	wheel: number;
}

class Boat implements IMachine {
	type: 'BOAT';
	motor: number;
}

function getWheelorMotor(machine: Car | Boat): number {
	if(machine.type === 'CAR'){
		return machine.wheel;
	}else {
		return machine.motor;
	}
}

custom Type Guard

function getWheelOrMotor(machine:any):number{
	if(isCar(machine)){
		return machine.wheel;
	}else if(isBoat(machine)){
		return machine.motor;
	}else{
		return -1;
	}
}

function isCar(arg:any):arg is Car {
	return arg.type === 'CAR';
}

function isBoat(arg:any):arg is Boat {
	return arg.type === 'BOAT';
}

6. Class를 안전하게 만들기

Class Property의 타입을 명시적으로 지정해야한다

class Square {
	area: number;
	sideLength: number;
}

const square = new Square();
console.log(square.area); // compile time - number, runtime - undefined
console.log(square.sideLength); // complie time - number, runtime - undefined

strictPropertyInitialization 옵션

class의 property 가 생성자 혹은 선언에서 값이 지정되지 않으면, 컴파일 에러를 발생시켜 주의를 준다.

Class Property 가 선언에서 초기화

class Square {
	area: number = 0;
	sideLength: number = 0;
}

Class Property가 생성자에서 초기화

class Square {
	area: number;
	sideLength: number;

	constructor(sideLength: number){
		this.sideLength = sideLength;
		this.area = sideLength ** 2;
	}
}

Class Property의 타입추론

// 생성자에 의해 property 타입이 추론됨
class Square {
	area;
	sideLength;
	
	constructor(sideLength: number){
		this.sideLength = sideLength;
		this.area = sideLength ** 2;
	}
}

여전히 생성자를 벗어나면 추론되지 않는다

class Square {
	sideLength!: number; // !로 의도를 표현해야 한다.
	
	constructor(sideLength: number){
		this.initialize(sideLength);
	}

	initialize(sideLength: number){
		this.sideLength = sideLength;
	}

	get area(){
		return this.sideLength ** 2;
	}
}

좋은 웹페이지 즐겨찾기