코드잇 Codeit/Front-End

[코드잇] TypeScript 기본기

꼽파 2024. 1. 24. 18:40


◆ TypeScript 시작하기

◆ Enum

◆ Interface

◆ 그 밖의 타입들

◆ 제네릭

◆ tsconfig.json


TypeScript 시작하기

TypeScript 프로젝트 만들기

 

Node.js 프로젝트 생성

npm init

 

타입스크립트 개발모드로 설치

npm install --save-dev typescript

 

타입스크립트 사용에 필요한 설정 파일 설치

npx tsc --nit


· npx : node 모듈을 시행하는 명령어
· tsc : 타입스크립트에서 제공하는 타입스크립트 컴파일러 모듈 (TS를 JS로 바꿔줌)
· --init : 초기 파일 생성

 

package.json 파일에서 스크립트 추가

  "scripts": {
    "build": "tsc"
  },

 

main.ts 파일 생성

console.log('안녕 타입스크립트!');

 

터미널에서 타입스크립트 컴파일러 실행

npm run build

 

main.js

"use strict";
console.log('안녕 타입스크립트!');

 

단축 명령어 설정

  "scripts": {
    "build": "tsc",
    "start": "node main.js"
  },

TypeScript가 실행되는 과정

타입스크립트는 자바스크립트의 상위집합(superset)

컴파일(compile)
- 한 프로그래밍 언어에서 다른 프로그래밍 언어로 번역하는 것
- 소스코드를 컴퓨터가 실행할 수 있는 코드나 기계어로 바꾸는 것을 의미함.
- 웹개발에서는 특별히 Transpile(트랜스파일)이라고 함.

 

타입스크립트 컴파일러(TSC)
- 타입스크립트 코드를 자바스크립트 코드로 트랜스파일 해주는 프로그램
- 타입스크립트 컴파일러는 트랜스파일 하기 전에 타입 검사(Type Check)를 함.
- TSconfig.js파일에서 컴파일 버전을 설정할 수 있음.

Deno : 타입스크립트 사용, 자체적으로 타입검사, 트랜스파일링을 해주어서 TS쓰기 위해 프로젝트 세팅을 할 필요가 없음.


타입을 정하는 법

정적 타이핑 언어 : 변수에 타입이 정해지면 반드시 타입을 지켜야 함.

let size = 100;
size = "L";

타입을 추측할 수 있는 경우에는 따로 타입을 적어주지 않아도 타입이 추론됨.

size 변수가 숫자형으로 정해졌기 때문에 문자열을 할당하면 타입오류가 남.

VSCode에서 보여주는 타입오류는 스크립트 컴파일러가 주는 것과 동일하다.

 

직접 타입을 정하기

 

TypeScript

let size: number = 100;
size = 105;

 

JavaScript

"use strict";
let size = 100;
size = 105;

타입은 타입 검사에만 쓰이고, 실제 코드 실행은 영향을 주지 않음.


타입 오류 이해하기

let product = {
    id: 'c001',
    name: '라이트 윈드 브레이커',
    price: 129000,
    sizes: ['M', 'L', 'XL'],
  };
  
  // ...
  
  const newProduct = {
    id: 'c002',
    name: '다크 윈드 브레이커',
    price: 139000,
    sizes: [90, 95, 100, 105, 100],
  };
  
  // ...
  
  product = newProduct;

product와 newProduct의 타입이 맞지 않다는 뜻임.


기본형

let itemName: string = '코드잇 블랙 후드';
let itemPrice: number = 129000;
let membersOnly: boolean = true;
let owner: undefined = undefined;
let seller: null = null;

let num = 2/0;  // Infinity
let num2 = 0/0;  // NaN
// Number.is 로 Number인지 검사

배열과 튜플

  • 배열 : 타입[] (ex. string[])
  • 튜플 : [타입, 타입] (개수가 정해져 있음) (ex. [number, string])

배열도 자료형 지정 가능

// 문자형 배열
const cart: string[] = [];
cart.push('c001');
cart.push('c002');
cart.push(3);

// 2차원 배열
const carts: string[][] = [
    ['c001', 'c002'],
    ['c003'],
];

// 배열의 크기는 정해져 있지 않음
let mySize: number[] = [167, 28];
mySize = [167, 28, 255];
mySize = [255];
mySize = [];

 

배열이지만 개수를 명확하게 쓰고 싶으면 튜플을 쓰면 됨.
튜플은 배열에 값을 정하듯이 쓰면 됨. 요소자리에 타입 지정하기

// 튜플
let mySize: [number, number, string] = [167, 28, 'M'];

// 개수가 다름
mySize = [167, 28];
mySize = [255];
mySize = [];

// 자료형이 다름
mySize = [167, '28inch', 34]

객체 타입

프로퍼티의 개수를 알 수 없거나, 개수를 정해놓고 싶지 않은 경우에 프로퍼티 값의 타입만 지정할 수 있음.

옵셔널 프로퍼티 : 객체 내 있어도 되고 없어도 되는 속성, '?'로 표시

let product: {
    id: string;
    name: string;
    price: number;
    membersOnly?: boolean;
    sizes: string[];
} = {
    id: 'c001',
    name: '코드잇 블랙 후디',
    price: 129000,
    sizes: ['M', 'L', 'XL'],
};

if (product.membersOnly) {
    console.log('회원 전용 상품');
} else {
    console.log('일반 상품');
}

 

인덱스 시그니처(Index Signature)
{ [키타입: 키타입명]: 값타입 }

let field = 'field name';
let obj = {
    [field]: 'field value',
};

// 상품의 재고 숫자만 만드는 객체
let stock: {
    [productId: string]: number;
} = {
    c001: 3,
    c002: 0,
    c003: 1,
    c004: 'codeit',  // 타입 에러
}

any

객체 product를 any로 지정하면 타입오류가 발생하지 않음.

되도록 사용하지 않는 것이 좋음.

자동으로 타입을 알 수 없는 경우 불가피하게 any가 됨. → as콜론을 쓰기

const product: any = {
    id: 'c001',
    name: '코드잇 블랙 후디',
    price: 129000,
    sizes: ['M', 'L', 'XL'],
};

console.log(product.reviews[2]);

JSON.parse 함수에서는 어떤 문자열이 어떤 객체타입을 될지 알 수 없어서 결과값이 any 타입임.
이때 any타입을 그대로 쓰기보다는 타입을 정의해주는 것이 좋음.

const parsedProduct : {
    name: string;
    price: number;
} = JSON.parse(
    '{ "name": "코드잇 토트백", "price": 12000 }'
);

 

as 키워드 활용

const parsedProduct = JSON.parse(
    '{ "name": "코드잇 토트백", "price": 12000 }'
) as {
    name: string;
    price: number;
};

 

문법적인 문제로 꺽쇄는 잘 쓰지 않음.

const parsedProduct = <{
    name: string;
    price: number;
}> JSON.parse(
    '{ "name": "코드잇 토트백", "price": 12000 }'
);

함수에 타입 정의하기

함수 타입을 선언하지 않으면 암묵적으로 Any를 선언하는 것이라 타입오류가 남.

"strict": true,
"noImplicitAny": true
→ 암묵적으로 Any를 선언하는 것을 금지함.


파라미터 옆에 콜론과 자료형을 적어준다.
리턴값이 추론되는 경우(return false) 굳이 자료형 안 적어줘도 됨.

const stock: { [id: string]: number } = {
    c001: 3,
    c002: 1,
};

const cart: string[] = [];

function addToCart(id: string, quantity: number): boolean {
    if (stock[id] < quantity) {
        return false;
    }

    stock[id] -= quantity;
    for (let i = 0; i < quantity; i++) {
        cart.push(id);
    }

    return true;
}

 

함수에 기본값 정하기

quantity 파라미터를 쓸 수도 있고, 안 쓸 수도 있다고 하자.
파라미터를 전달하지 않으면 1로 처리

function addToCart(id: string, quantity?: number): boolean {
    if (typeof quantity === 'undefined') {
        quantity = 1;
    }
function addToCart(id: string, quantity: number = 1): boolean {

 

함수 자체의 타입을 정하기

const codeitmall: {
    stock: { [id: string]: number };
    cart: string[];
    addToCart: (id: string, quantity?: number) => boolean;
    addManyToCart: (...ids: string[]) => void;
} = {
    stock: {
        c001: 3,
        c002: 1,
    }, 
    cart: [],
    addToCart,
    addManyToCart,
};

function addToCart(id: string, quantity?: number) {
    if (!quantity) {
        quantity = 1;
    }

    if (codeitmall.stock[id] < quantity) {
        return false;
    }

    codeitmall.stock[id] -= quantity;
    for (let i = 0; i < quantity; i++) {
        codeitmall.cart.push(id);
    }

    return true;
}

// rest 파라미터 : 배열로 지정
// 아무것도 리턴하지 않으면 void로 적기
function addManyToCart(...ids: string[]) {
    for (const id of ids) {
        addToCart(id);
    }
}

console.log(codeitmall);

 

 

함수를 값으로 사용하는 경우 화살표 함수처럼 작성

(id: string, quanity: number) => boolean

 

Rest 파라미터는 배열 타입으로 작성

함수가 값을 리턴하지 않는 경우 리턴 타입을 void로 설정

(...ids: string[]) => void;

Enum

사이즈가 S, M, L 밖에 없는데 자료형이 string으로 지정되어 있어서 광범위한 느낌이 있음.
enum의 기본값은 0부터 시작하므로 주의해야함.

enum Size {
    S,
    M,
    L,
    XL, 
}

function findProduct(size?: Size) {
    if (!size) {
        console.log('전체 사이즈로 검색');
        return;
    }

    console.log('특정 사이즈로 검색');
}

findProduct(Size.M);  // 특정 사이즈로 검색
findProduct(Size.S);  // 전체 사이즈로 검색
findProduct();  // 전체 사이즈로 검색

Size.S가 0이므로 falsy값으로 처리되어 값이 없는 경우와 똑같이 처리된 것을 확인할 수 있음.

 

enum을 시작할 때는 값을 정해놓고 쓰는 것이 좋음.

enum Size {
    S = 'S',
    M = 'M',
    L = 'L',
    XL = 'XL', 
}

function findProduct(size?: Size) {
    if (!size) {
        console.log('전체 사이즈로 검색');
        return;
    }

    console.log('특정 사이즈로 검색');
}

findProduct(Size.M);  // 특정 사이즈로 검색
findProduct(Size.S);  // 특정 사이즈로 검색
findProduct();  // 전체 사이즈로 검색

Interface

객체타입을 재사용하기 좋도록 하는 문법

interface 대문자
interface 하위인터페이스 extends 상위인터페이스 (프로퍼티가 상속됨)

enum Size {
    S = 'S',
    M = 'M',
    L = 'L',
    XL = 'XL',
  }

  interface Product {
    id: string;
    name: string;
    price: number;
    membersOnly?: boolean;
  }

  // 하위(상속받음) extends 상위1, 상위2
  interface ClothingProduct extends Product {
    sizes: Size[];
  }
  
  const product1: ClothingProduct = {
    id: 'c001',
    name: '코드잇 블랙 후드 집업',
    price: 129000,
    sizes: [Size.S, Size.M],
    membersOnly: true,
  };
  
  const product2: ClothingProduct = {
    id: 'd001',
    name: '코드잇 텀블러',
    price: 25000,
    sizes: [Size.M]
  };

  // 인터페이스로 함수 타입 지정
  interface PrintProductFunction {
    (product: Product): void;
  }
  
 const printProduct: PrintProductFunction = (product) => {
    console.log(`${product.name}의 가격은 ${product.price}원입니다.`)
  }
  
  printProduct(product1);
  printProduct(product2);

그밖의 타입들

리터럴 타입 : 값을 곧 타입으로 하는 타입

let productName1 = '코드잇 블랙 후드';  // string
const productName2 = '코드잇 텀블러';  //  '코드잇 텀블러' 문자열 자체

let small = 95;  // number
const large = 100;  // 100 자체

 

문자 리터럴 타입은 문자형 타입에 포함됨.
숫자 리터럴 타입은 숫자형 타입에 포함됨.

function printSize(size: number) {
    console.log(`${size} 사이즈입니다.`);
}

printSize(small);  // 95 사이즈입니다.
printSize(large);  // 100 사이즈입니다.

size: number로 숫자형 타입을 지정해주면 100이 할당된 large도 제대로 출력됨.

 

특정 자료형의 값만 쓰면 오류가 남.

function printSize(size: 100) {
    console.log(`${size} 사이즈입니다.`);
}

printSize(small); // 에러
printSize(large);


타입 별칭

 

type alias (타입 별칭) : 타입도 변수처럼 이름을 붙여줄 수 있음.

const cart: string[] = [
    'c001', 
    'c001',
    'c002',
];

interface User {
    username: string;
    email: string;
    cart: string[];
}

const user: User = {
    username: 'codeit',
    email: 'typescript@codeit.kr',
    cart,
}

 

이 코드에서 cart: string[]이 불필요하게 반복됨.

 cart: string[]

 

type Cart를 선언한 다음 string[]에는 다 Cart로 대체해줌.

type Cart = string[];

type CartResultCallback = (result: boolean) => void;

interface Product {
    id: string;
    name: string;
}

const cart: Cart = [
    'c001', 
    'c001',
    'c002',
];

interface User {
    username: string;
    email: string;
    cart: Cart;
}

const user: User = {
    username: 'codeit',
    email: 'typescript@codeit.kr',
    cart,
}

Union 타입

  • Union : A | B, A타입이거나 B타입이다.
  • Intersection : A & B, A타입이랑 B타입을 합친 것이다.

Union일 때는 ClothingProduct와 ShoeProduct일 떄를 모두 고려하기 때문에 공통된 프로퍼티만 참조할 수 있음.
특정 타입에 대해서 뭘하고 싶으면 if in을 쓰면 됨.

interface ClothingProduct extends Product {
    sizes: ClothingSize[];
    color: string;
  }
  
 interface ShoeProduct extends Product {
    sizes: ShoeSize[];
    handmade: boolean;
  }
  
  // ClothingProuduct와 ShoeProduct 둘 중 하나만 받고 싶은 경우
  function printSizes(product: ClothingProduct | ShoeProduct) {
    const availableSizes = product.sizes.join(', ');
    console.log(`구매 가능한 사이즈는 다음과 같습니다: ${availableSizes}`);

    // if문에서 특정 타입만 처리하도록
    if ('color' in product) {
        console.log(`색상: ${product.color}`);
    }

    if ('handmade' in product) {
        console.log(
            product.handmade
            ? '이 상품은 장인이 직접 만듭니다.'
            : '이 상품은 공장에서 만들어졌습니다.'
        );
    }
  }

Intersection 타입

여러 객체 타입을 합칠 때 사용, '&'로 표시

interface Id {
    id: string;
}

interface Timestamp {
    createdAt: Date;
    updatedAt: Date;
}

type Product = Id & {
    name: string;
    price: number;
    membersOnly?: boolean;
}
  
type User = Id & Timestamp & {
    username: string;
    email: string;
}
  
type Review = Id & Timestamp & {
    productId: string;
    userId: string;
    content: string;
}

 

interface 상속

interface Entity {
    id: string;
}

interface TimestampEntity extends Entity {
    createdAt: Date;
    updatedAt: Date;
}

interface Product extends Entity {
    name: string;
    price: number;
    membersOnly?: boolean;
}
  
interface User extends TimestampEntity {
    username: string;
    email: string;
}
  
interface Review extends TimestampEntity {
    productId: string;
    userId: string;
    content: string;
}

타입스크립트에서 타입은 Structural Subtyping(Structural Type System)
구조가 같은 타입이면 같은 타입이라고 판단함.


keyof와 typeof 연산자

keyof  연산자

객체 타입에서 객체의 키 값들을 숫자나 문자열 리터럴 유니언을 생성

interface Product {
    id: string;
    name: string;
    price: number;
    salePrice: number;
    membersOnly?: boolean;
}

type ProductProperty = 'id' | 'name' | 'price' | 'salePrice' | 'membersOnly';

const productTableKeys: ProductProperty[] = ['name', 'price', 'membersOnly', 'salePrice'];
interface Product {
    id: string;
    name: string;
    price: number;
    salePrice: number;
    membersOnly?: boolean;
}

type ProductProperty = keyof Product;

const productTableKeys: (keyof Product)[] = ['name', 'price', 'membersOnly', 'salePrice'];

 

typeof 연산자

존재하는 타입을 가져와서 타입을 정리할 때 사용

컴파일 타임에 변수, 객체 또는 함수의 유형을 출력함.

// TS : 결과가 타입스크립트의 타입으로 나옴.
// 존재하는 타입을 가져와서 타입을 정리할 때 사용
let product2: typeof product;

// JS : 어떤 값의 타입을 문자열로 출력해주는 연산자
console.log(typeof product);  // object
const person = {
  name: "John",
  age: 30,
};

type PersonType = typeof person;
// PersonType is { name: string, age: number }

 

  • Enum vs 타입 별칭
enum UserType {
  Admin = 'admin',
  User = 'user',
  Guest = 'guest',
}

const role = UserType.Admin;
console.log(role === UserType.Guest);
type UserType = 'admin' | 'user' | 'guest'

const role: UserType = 'admin';
console.log(role === 'guest');

Enum은 별도의 자바스크립트 객체를 만들어서 사용하고, 타입 별칭은 단순히 값만 사용한다.

 

  • Interface vs 타입 별칭
interface Entity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

interface User extends Entity {
  username: string;
  email: string;
}
type Entity = {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

type User = Entity & {
  username: string;
  email: string;
}

Interface의 목적에 맞으면 되도록 Interface를 사용하자.


제네릭

타입을 마치 함수의 파라미터처럼 사용하는 것

제네릭에서는 보통 T, U, V로 임의의 타입을 표현함.

// T는 타입 파라미터
function printArray<T>(items: T[]) {
    for (const item of items) {
        console.log(item);
    }
}
printArray<boolean>(shoeSizes);
printArray(clothingSizes);
interface SizeProduct<T> extends Product {
    sizes: T[];
}
type Pair<T> = [T, T];
// 길게 쓰고 싶으면 이름 앞에 'T' 붙이기
// type Pair<TItem> = [TItem, TItem];

const point: Pair<number> = [1, 2];
const fullname: Pair<string> = ['김', '코드잇'];

const map = new Map<string, Product>();

tsconfig.json

모듈 만들기

· import, export 문법은 자바스크립트와 동일하다.

· export default는 enum 앞에서는 불가능하다.

· 기본적으로 CommonJS 모듈 문법으로 바뀌어져서 나오는데, 설정을 통해 변경 가능함.

 

outDir

빌드된 파일은 따로 폴더에 모이면 좋겠음.
→ tsconfig.json 파일에 설정

빌드한 파일을 모아놓을 경로를 정할 수 있음.

 

아무것도 지정하지 않는 경우("./") : 소스파일과 트랜스파일링된 파일이 같은 경로에 있음.

    "outDir": "./dist",

dist라는 폴더 안에 JS 파일이 모여 있음.

 

include/exclude

타입스크립트 처리할 때 어떤 파일을 포함/제외할 것인지 설정

배열 안에는 경로패턴을 문자열로 적어주면 됨.

{
	"compilerOptions": {
	},
  	"include" : ["src/**/*"],   // src 폴더 안에 있는 모든 경로의 파일을 포함
  	"exclude" : ["src/test2.ts"]
}

 

rootDir

컴파일 하는 파일들의 공통된 조상 파일을 찾아서 그 경로를 기본값으로 함.

"rootDir": "./",

tsconfig.json 파일이 있는 폴더를 최상위 폴더로 해서 폴더 구조를 그대로 옮겨 놓음.

 

target 어떤 ECMAScript 버전으로 변환할 것인지
기본적으로 ES7(ES2016)으로 변환하도록 되어 있음.
module 어떤 방식으로 모듈을 만들지
ESM - es6, es2020 / CJS - commonjs
esModuleInterop ES 모듈을 안전하게 사용
안전하게 사용하려면 true로 해놓는 것을 권장함.
forceConsistentCasingInFileNames 파일의 대소문자 구분하기
ex. Main.ts, main.ts
strict 엄격한 규칙들 켜기
· noImplicitAny : implicit any = 기존 자바스크립트 코드처럼 타입 없이 사용
· strictNullChecks : null이 될 가능성이 있다면 반드시 처리하는 옵션
stkipLibCheck node_modules 폴더에 설치한 패키지의 타입 검사하지 않기
rootDir 최상위 폴더
outDir 자바스크립트 파일을 생성할 폴더
(값을 지정하지 않으면 소스코드 파일과 같은 폴더에 만듦)
resolveJsonModule JSON 파일 임포트하기
include / exclude **/* : 아래의 모든 폴더, 모든 파일 (Glob 패턴)

https://www.typescriptlang.org/tsconfig

 

TSConfig Reference - Docs on every TSConfig option

From allowJs to useDefineForClassFields the TSConfig reference includes information about all of the active compiler flags setting up a TypeScript project.

www.typescriptlang.org

 

728x90