Search

TypeScript

Date
2024/07/04
Category
WEB
Tag
Typescript
목차

왜 JS가 아닌 TS인가?

타입 안정성

코드에 버그가 줄어듦
런타임 에러가 줄어듦
생선성 증가
JS는 에러를 보여주지 않음
ex) [1, 2, 3, 4] + false ⇒ ‘1,2,3,4false’
fucntion divide(a, b) { return a/b } divide(”xxxx”) // NaN
JavaScript
복사
허용되어서는 안되는 코드가 실행됨

런타임 에러

const nico = {name:"nico"} nico.hello() // 코드 실행 시에 에러가 발생함
JavaScript
복사
다른 언어에서는 컴파일 실행조차 되지 않지만 JS는 코드 실행할 때까지 에러를 확인 못함

변수

타입스크립트를 컴파일 하면 자바스크립트로 변환됨
타입스크립트에서 컴파일 되기 전에 에러를 잡기 때문에 JS에서의 런타임 에러는 발생하지 않음

Type System

TS의 변수 선언

1.
데이터와 변수의 타입을 명시적으로 정의
2.
JS처럼 변수만 생성 → 타입 추론을 통해 변수의 타입을 정함
let a = "hello" // a의 타입은 string으로 설정됨 let b : boolean = true // 명시적으로 타입 정의 let c : number[] = [] // 명시해주지 않을 경우 number가 아닌 다른 타입이 들어갈 수 있음
TypeScript
복사

Optional

player라는 변수에 name과 age가 있을 때 name을 가지는 것은 필수이지만 age는 선택적이게 할 떄
const player : { name : string, age? : number } = { name : "wooseok" }
TypeScript
복사

Alias

코드 재사용성을 높이기 위해 사용
type Player = { name:string, age?:number } const woo : Player = { name:"woo" } const seok : Player = { name:"seok" age:1 }
TypeScript
복사

return

type Player = { name:string, age?:number } function playerMaker(name:string) : Player { // Player 객체를 만든다는 것을 명시 return { name // name : name } } const woo = palyerMaker("woo") woo.age = 1
TypeScript
복사

Arrow function

화살표 함수 사용 이유
function 키워드 제거
함수의 매개변수가 1개라면 괄호() 생략 가능
함수 바디가 표현식 하나라면 중괄호와 return 문을 생략 가능
const playerMaker = (name:string) : Player => ({name})
TypeScript
복사

readonly

객체의 속성값을 바꾸지 못하게 할 수 있음
type Player = { readonly name:string age?: number } const player : Player = { name : "woo" } player.name = "seok" // 에러 발생
TypeScript
복사

Tuple

array를 생성
최소한의 길이를 가지며, 특정 위치에 특정 타입이 있어야 함
const player : [string, number, boolean] = ["player", 1, true] // 3개의 값을 하며, 순서에 맞는 타입을 가져야 함
TypeScript
복사

any

빈 값. 아무 타입이나 사용할 수 있음
TS가 아닌 JS처럼 사용할 수 있음 (TS의 보호장치 비활성화)
const a : any[] = [1, 2, 3, 4] const b : any = true a + b // 에러 발생하지 않음
TypeScript
복사

void

아무것도 리턴하지 않을 때 사용
명시적으로 작성할 필요가 없을 때가 많음
function hello() { // function hello() : void {} console.log('x') }
TypeScript
복사

never

함수가 절대 return하지 않을 때 사용. 예외를 발생시킬 때
function hello() : never { throw new Error("xxx") } function hello(name:string|number) { if (typeof name === "string") { name } else if (typeof name === "number"){ name } else { name // name의 타입은 never } }
TypeScript
복사

unknown

어떤 타입인지 모르는 경우 (API로부터 응답을 받는데 타입을 모를 때)
let a:unknown; if(typeof a === 'number') { let b = a + 1 } if (typeof a === 'string'){ let b = a.toUpperCase() }
TypeScript
복사

Function

Call Signature

함수 위에 마우스를 올렸을 때 보게 되는 것 (arguments, 함수의 return 타입)
type Add = (a:number, b:number) => number; // type Add = { // (a:number, b:number) : number // } const add:Add = (a, b) => a+b // const add = (a:number, b:number) => a + b
TypeScript
복사

Overloading

함수가 여러개의 call signagure를 가지고 있을 때 발생
사용할 일이 많진 않음
type Add = { (a:number, b:number) : number (a:number, b:string) : number } const add:Add = (a, b) => { if(typeof b === 'number'){ return a + b } else { return a } }
TypeScript
복사
type Add = { (a:number, b:number) :number, (a:number, b:number, c:number) :number } const add:Add = (a, b, c?:number) => { if (c) return a + b + c return a + b }
TypeScript
복사

Polymorphism (다형성) #1

type SuperPrint = { // (arr: number[]):void // (arr: boolean[]):void // (arr: string[]):void // -> 일일이 다 써줄 수 없음 또한 여러 타입이 같이 쓰는 경우를 해결 못함 // (arr: (number|boolean)[]):void // 다른 타입이 올 경우 대처하지 못함 <TypePlaceholder>(arr: TypePlaceholder[]):void } const superPrint:SuperPrint = (arr) => { arr.forEach(i => console.log(i)) } superPrint([1, 2, true, false, "hello"])
TypeScript
복사
type SuperPrint = { <T>(arr: T[]) :T } // type SuperPrint = <T>(arr: T[] => T const superPrint:SuperPrint = (arr) => arr[0] const a = superPrint([1, 2, true, false, "hello"])
TypeScript
복사

Generic type

call signature를 작성할 때, 파라미터의 확실한 타입을 모를 때 사용
TS의 placeholder
concrete type을 사용하는 것 대신 사용
concrete type
number, string, boolean, void, …
any는 타입 안정성을 보장받지 못하기 때문에 Generic을 사용해야함

Generic 사용 사례

call signature 외에 사용
function superPrint<T>(a: T[]){ return a[0] }
TypeScript
복사
type Plaer<E> = { name:string extraInfo:E } const woo: Player<{favFood:string}> = { name:"woo", extraInfo: { favFood: "Ramen" } }
TypeScript
복사
type Plaer<E> = { name:string extraInfo:E } type WooExtra = { favFood:string } type WooPlayer = Player<WooExtra> const woo: WooPlayer = { name:"woo", extraInfo: { favFood: "Ramen" } }
TypeScript
복사

Class

class Player { constructor( private firstName:string, private lastName:string, public nickname:string ) {} } const woo = new Player("woo", "lee", "lewis"); woo.firstName // private라서 컴파일되지 않음
TypeScript
복사

Abstract

다른 클래스가 상속받을 수 있는 클래스
직접 새로운 인스턴스를 만들지는 못함
abstract class User { constructor( private firstName:string, private lastName:string, public nickname:string ) {} } class Player extends User { }
TypeScript
복사

Method

클래스 안에 존재하는 함수
abstract class User { constructor( private firstName:string, private lastName:string, public nickname:string ) {} getFullName(){ return `${this.firstName} $this.lastName}` } class Player extends User { } const woo = new Player("woo", "lee", "lewis"); woo.getFullNmae()
TypeScript
복사

Abstract Method

추상 클래스 안에서는 추상 메소드가 존재할 수 있음
메소드를 구현(implement)하면 안됨
메소드의 call signature만 작성
abstract class User { constructor( protected firstName:string, // private일 경우 접근할 수 없음 private lastName:string, public nickname:string ) {} abstract getFistName():void getFullName(){ return `${this.firstName} $this.lastName}` } class Player extends User { getFisrtName() { console.log(this.firstName) // firstName이 private이었다면 에러 발생 } } const woo = new Player("woo", "lee", "lewis"); woo.getFullNmae()
TypeScript
복사
private는 인스턴스나 메소드에서 접근할 수 있는데 abstract class는 인스턴스를 가질 수 없기 때문에 자식 클래스에서 사용하려면 protected를 사용해야 한다

사용 예제 - HashMap

type Words = { [key:string]: string } class Dict { priavte words: Words constructor(){ this.words = {} } add(word:Word) { // class를 타입처럼 사용 가능 if (this.words[word.term] === undefined){ this.words[word.term] = word.def; } } def(term:string){ return this.words[term] } } class Word { constructor( public term:string, public def :string ) } const kimchi = new Word("kimchi", "Korean Food") const dict = new Dict() dict.add(kimchi); dict.def("kimchi")
TypeScript
복사
term과 def가 public이기 때문에 kimchi.def = “xxx” 같은 코드도 수행되기 때문에 이를 방지하기 위해 private, protected를 사용할 수 있지만 readonly도 사용 가능하다
class Word { constructor( public readonly term:string, public readonly def :string ) }
TypeScript
복사

Interfaces

오브젝트의 모양을 설명할 때만 사용
interface보다 type의 쓰임이 더 많음
대신 클래스의 특성을 가지기 때문에 다름
type Team = "red" | "blue" | "yellow" type Health = 1 | 5 | 10 interface Player { nickname:string, team:Team, health:Health } // type Player = { // nickname:string, // team:Team, // health:Health // }
TypeScript
복사
property 축적이 가능함
interface User { name:string } interface User { lastname: string } interface User { health: number } const woo: User = { name: "woo", lastname: "lee", health: 5 }
TypeScript
복사

Interface vs. Class

인터페이스는 JS로 컴파일되지 않음
추상 클래스는 컴파일시 JS의 클래스로 컴파일 됨 (TS → JS)
파일의 크기가 커짐
추가 클래스가 생성됨
abstract class User { constructor( protected firstName: string, protected lastName: string ) {} abstract fullName(): string abstract sayHi(name: string): string } class Player extends User { fullName() { return `${this.firstName} ${this.lastName}` } sayHi(name: string) { return `Hello ${name}. My name is ${this.fullName()}` } }
TypeScript
복사
interface User { firstName: string, lastName: string fullName(): string sayHi(name: string): string } interface Human { health: number } class Player implements User { consturctor( public firstName:string, // interface를 상속받을 시에는 property를 private으로 만들지 못함 public lastName:string, public health:number ) {} fullName() { return `${this.firstName} ${this.lastName}` } sayHi(name: string) { return `Hello ${name}. My name is ${this.fullName()}` } }
TypeScript
복사

Interface vs. Type

둘 모두 추상 클래스를 대체할 수 있음
type은 새 property를 추가하기 위해 상속이 필요함
interface는 새 property 추가를 위해 상속, 재선언을 사용 가능
클래스나 오브젝트의 모양을 정의할 때는 interface
다른 모든 경우에는 type
type PlayerA = { name: string } type PlayerAA = PlayerA & { // type의 상속 lastName: string } const playerA: PlayerAA = { name: "woo", lastName: "lee" } interface PlayerB { name: string } interface PlayerBB extends PlayerB { // interface의 상속 lastName: string } // interface PlayerB { // 상속받지 않고 재선언으로 property를 추가할 수 있음 // lastName: string // } const playerB: PlayerBB = { name: "woo", lastName: "lee" }
TypeScript
복사

Polymorphism #2

interface SStorage<T> { // Storage라는 이름의 interface가 TS에 정의되어 있어 이름 다르게 설정 [key:string]: T // Storage로 만들면 override 됨 } class LocalStorage<T> { // 제네릭을 클래스로 보내고 private storage: SStorage<T> = {} // 클래스는 제네릭을 인터페이스로 보냄 set(key:string, value:T) { this.storage[key] = value } remove(key:string){ delete this.storage[key] } get(key:string):T { return this.storage[key] } clear(){ this.storage = {} } } const stringsStorage = new LocalStorage<string>() stringsStorage.get("key") stringsStorage.set("hello", "how are you") const booleansStorage = new LocalStorage<boolean>() booleansStorage.get("xxx") booleansStorage.set("hello", true)
TypeScript
복사

Project

Next.js, Nest.js, Create-React-App(CRA)을 사용하면 수동으로 프로젝트 설정을 할 필요가 없음

Package

tsconfig.json

vscode가 타입스크립트로 작업한다는 것을 확인함
{ "include": [ // TS 파일이 있는 디렉토리, 자바스크립트로 컴파일할 파일의 위치 "src" ], "compilerOptions": { "outDir": "build", // JS 파일이 생성될 디렉토리 지정 "target": "ES6" // 어떤 버전의 JS로 컴파일할지 설정 } // 대부분의 nodeJS와 브라우저가 ES6를 지원하기 때문에 ES6 권장 }
TypeScript
복사

lib

어떤 API를 사용할 것인지 어떤 환경에서 코드를 실행하는지 확인함
자동완성 제공
{ "include": [ // TS 파일이 있는 디렉토리, 자바스크립트로 컴파일할 파일의 위치 "src" ], "compilerOptions": { "outDir": "build", // JS 파일이 생성될 디렉토리 지정 "target": "ES6", // 어떤 버전의 JS로 컴파일할지 설정. 대부분의 nodeJS와 브라우저가 ES6를 지원 "lib": ["ES6", "DOM"] // DOM은 브라우저를 뜻함 } }
TypeScript
복사

Declaration Files

자바스크립트 코드의 모양을 타입스크립트에 설명해주는 파일
d.ts 파일을 통해 자바스크립트 모듈 정의를 해줌
// index.ts import {init, exit} from "myPackage"; init({ url:"true" }) exit(1)
TypeScript
복사
// myPackage.js export function init(config) { return true; } export function exit(code) { return code + 1; }
TypeScript
복사
// myPackage.d.ts interface Config{ url: string } declare module "myPackage" { function init(config:Config): boolean function exit(code:number): number }
TypeScript
복사

JSDoc

JS 코드를 사용하면서 TS의 보호를 받고 싶을 때 사용
tsconfig.json 파일에 “allowJs”: true 추가
// @ts-check 추가
/** */ 코멘트를 달아 TS 문법이 아니더라도 TS의 보호를 받게 함
// myPackage.js // @ts-check /** * Initializes the project * @param {object} config * @param {boolean} config.debug * @param {string} config.url * @returns boolean */ export function init(config) { return true; } /** * Exits the program * @param {number} code * @returns number */ export function exit(code) { return code + 1; }
TypeScript
복사
// tsconfig.json { "include": [ "src" ], "compilerOptions": { "outDir": "build", "target": "ES6", "lib": ["ES6", "DOM"], "strict": true, "allowJs": true } }
TypeScript
복사

ts-node

npm i -D ts-node
ts-node를 사용하면 빌드 없이 타입스크립트를 실행 가능
프로덕션에서는 사용하지 않고 개발 환경에서만 사용
package.json에 ts-node로 dev환경 사용할 것 정의
"scripts": { "build": "tsc", "dev": "ts-node src/index", // 확장자는 없어도 됨 "start": "node build/index.js" },
JSON
복사

nodemon

매번 빌드하지 않도록 돕는 패키지
npm i nodemon
"scripts": { "build": "tsc", "dev": "nodemon --exec ts-node src/index.ts", "start": "node build/index.js" },
JSON
복사

DefinitelyTyped

TS로 만들어지지 않은 패키지를 받았는데 타입정의가 하나도 없을 때
사용하려는 라이브러리나 패키지마다 정의 파일을 만들 수 없기 때문에 사용
TS에게 패키지 않의 타입에 대해 설명하고 보호장치를 사용하기 위해 사용