TypeScript

정하람·2023년 10월 27일

Node.js (TS)

목록 보기
1/3

원래 JS밖에 안 써봤었기 때문에 TS로 프로젝트를 하게 돼서 JS에는 없지만 TS에만 있는 기능들을 정리해 보았다.

strongly typed programming language

TypeScript Project m

TypeScript를 사용하는 이유

  • 타입 안정성

선택적으로 타입을 정의

type Person = { // 사용자 정의 타입 생성
	name : string,
	age? : number // age 는 선택적 타입 이므로 type 이 undefined || number 이다.
}

interface vs type 차이

→ 둘 다 사용자 정의 타입을 선언할 때 사용

interface

  1. interface는 주로 객체의 구조를 정의하는 데 사용됩니다. 객체의 속성과 타입을 선언하는 데에 강점이 있습니다.
  2. interface는 상속(extends)을 통해 다른 interface를 확장할 수 있습니다. 즉, 여러 개의 interface를 조합하여 하나의 인터페이스를 만들 수 있습니다.
  3. interface는 동일한 이름의 여러 개를 선언 해도 자동으로 합쳐집니다. 이는 같은 이름의 인터페이스를 여러 곳에서 확장하여 사용할 수 있다는 장점을 제공합니다.
  4. interface는 주로 클래스와의 상호작용에서 사용됩니다.
// Interface로 정의
interface StudentInfo {
    name: string;
    age?: number;
    curriculum?: number;
}

// Interface 상속
interface DetailedStudentInfo extends StudentInfo {
    grade: number;
}

type

  1. type은 객체 뿐만 아니라 유니온, 인터섹션 등 다양한 형태의 타입을 정의할 수 있습니다. 즉, 객체뿐만 아니라 원시 타입, 유니온 타입, 튜플 등 다양한 타입을 정의할 수 있습니다.
  2. typeinterface와 달리 상속이나 확장 기능이 없습니다. 하나의 type 정의가 다른 type에 기반하여 확장되지 않습니다.
  3. type은 조금 더 복잡한 타입을 만들 수 있도록 허용합니다. 조건부 타입과 같은 기능을 이용하여 더 다양한 유형의 타입을 만들 수 있습니다.
  4. type은 객체뿐만 아니라 리터럴 타입 등 다양한 상황에서 사용됩니다.
// Type으로 정의
type StudentInfo = {
    name: string;
    age?: number;
    curriculum?: number;
};

// 복잡한 타입 정의
type Grade = 1 | 2 | 3 | 4 | 5;
type DetailedStudentInfo = StudentInfo & { grade: Grade };
interface User {
    name: string
}

// interface Player extends User{
// }

type Player = User & {}; // %(and) 연산자로 User 꺼를 가져오는 거.
// type 으로 상속관계를 정의하는 법.

const haram: Player = {
    name: "haramjeong"
}

implements ⇒ typescript 에서의 extends

interface 의 상속에선 property 를 private, protected 로 만들 수 없다. → public 만 가능.

주로 객체의 구조 정의 → 인터페이스 → 알아서 속성 합쳐줌.

다양한 타입을 정의하거나 복잡한 조건 활용 → 타입 → 계속 새로 갱신됨.

대부분 상호 교체 가능.

// 얘를 추상클래스가 아닌 인터페이스로 상속받을 수 있게 한다면 파일 크기를 매우 줄일 수 있다 
// -> js 에선 추상클래스가 없으므로 그대로 클래스를 가져오지만 인터페이스는 아예 가볍게 사라진다.

interface User{
    firstName: string,
    lastName: string,
    fullName(): string,
    sayHi(name:string): string
}

interface Human{
    health: number
}

class Player implements User,Human{
    constructor(
        public firstName: string,
        public lastName: string,
        public health: number
    ) {}
    fullName(){
        return `${this.firstName} ${this.lastName}`
    }
    sayHi(name: string){
        return `hi ${name}. my name is ${this.fullName()}`;
    }
}

람다 함수(화살표)

(parameter : input type) : return type ⇒ (return value)

const studentMaker = (name : Name) : StudentInfo => ({name})

// 일반적인 함수 선언
function add(a: number, b: number): number {
    return a + b;
}

// 람다 함수 (화살표 함수) 선언
const addLambda = (a: number, b: number): number => {
    return a + b;
};

// 람다 함수 축약 형태 (단일 표현식의 경우 return 생략 가능)
const addLambdaShort = (a: number, b: number): number => a + b;

readonly

수정 불가능한 속성으로 만들어 줌

const player : readonly string[] = ["1","2"]
player[1] = 3 // error !!

tuple

서로 다른 type을 가진 요소들이 array의 형태로 저장될 수 있음.

const player : [string, number, boolean] = ["haram", 2, false]
player[0] = 9 // error !! -> type : string !!!! not change to int

Type

undefined, null, any

let a = [] // any[]
  • any type 은 typescript 의 강 타입 언어 성질을 무시

void, never, unknown

  • unknown : 데이터의 type을 모를 때 사용
let a : unknown

if (typeof a === "number"){
	let b = a + 1
}
  • void : return 하지 않는 함수의 타입 // 알던거
  • never : 함수가 절대 리턴을 하지 않을 때 사용 // 예외를 처리할 때 사용
function hello():never{
	throw new Error("xxx") // error 발생
}
function hello(name:string|number){
	if (typeof name === "string"){
		name // name 은 string
	} else if (typeof name === "number"){
		name // name 은 number
	} else {
		name // name 은 never !!!!
	}
}

Call Signature

  • Call Signature - 함수의 인자의 타입과 반환의 타입을 알려주는 것.
function add1(a:number, b:number){ 
		// call signature : function add1(a: number, b: number): number
    return a + b;
}

const add2 = (a:number, b:number) : number => a+b
// call signature : const add2: (a: number, b: number) => number
  • Call Signature 생성
    • type 활용 - 람다함수 식으로
// Call Signature 생성
// "add" 라는 call signature 는 number 타입 2개를 입력받고, number 를 리턴함.
type Add = **(a:number, b:number) => number** // call signation

// Call Signature 를 활용한 함수 생성.
const add:Add = (a,b) => a + b // type을 정해주지 않아도 됨.

Overloading

  • Overloading - 함수가 여러 개의 call signature 를 가지고 있을 때 발생시킴.
type Add = {
    (a:number,b:number) : void // type signature 1
    (a:number,b:string) : void // type signature 2
}

const add : Add = (a,b) => {
    if (typeof b == "number") console.log(a+b)
    else console.log("can not add number and string")
}

add(1,3) // 4
add(1,"2") // can not add number and string
  • Call Signature 는 파라미터의 개수도 다르게 설정해줄 수 있음. → 선택속성 ?를 달아줌.
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
    }
    else {
        return a+b
    }
}

console.log(add(1,2)) // 3
console.log(add(1,2,3)) // 6

다형성 : Polymorphism (c++ 의 템플릿 느낌.)

poly - many, morphos - structure

⇒ 여러가지 다른 구조.

Concrete type ? → bool, string, number, …

generic type 사용 → like placeholder → 추론 후 함수 사용 → 들어오는 거

'Generic선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입 만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.’

  • 모든 타입 시그니처를 작성하지 않아도 된다!
type SuperPrint = {
    <T>(arr:T[]) : void // 어떤 타입의 배열이든 알아서 타입 추정.
												// 추가적으로 배열에서 모든 value들의 type이 통일 될 필요 없음.
												// -> 이건 너무 좋다.
}

const superPrint:SuperPrint = (arr) => {
    arr.forEach(i => console.log(i))
}

// all compile
superPrint([1,2,3,4,5,6])
superPrint([1,"2",3,4,5,6])
superPrint([1,2,false,4,5,6])
superPrint([1,2,3,4.3,5,6])
type FirstPrint = {
    <T>(arr : T[]):T
}

const firstPrint : FirstPrint = (arr) => {
    return arr[0]
}

console.log(firstPrint([1,2,3,4,5,6])) //1
console.log(firstPrint([1,"2",3,4,5,6])) //1
console.log(firstPrint([1,2,false,4,5,6])) //1
console.log(firstPrint([1,2,3,4.3,5,6])) //1
// 2개 이상의 generic
type RequireValue = {
    <T,M>(arr: T[], value: M) : T | undefined
}

const requireValue: RequireValue = (arr, value) => {
    const returnValue = arr.find(i => (typeof i === typeof value))
    return returnValue
}

console.log(requireValue([1,2,3,4,'2',1,24,5],'haram')) // '2'
  • but, generic 타입 안 쓰고 패키지를 사용함.

// 콜 시그니쳐를 정의할 때 제네릭 타입을 정의하는 것뿐만 아니라
// 함수 자체에서 한 번에 제네릭 타입을 정의할 수 있다.
// Type Script 에게 타입을 유추하도록 코드를 작성하는 것이 좋다.
function superPrint<T>(arr: T[]){
	return arr[0]
}
  • Array == number[] → 같은 동작을 함.
// Generic type을 확장시켜서 사용할 수 있음.
type **Player**<E> = {
    name: string
    extraInfo: E
}

type **NicoExtra** = {favFood: "kimchi"}

type **NicoPlayer** = **Player**<**NicoExtra**>

const nico: **NicoPlayer** = {
    name: "nico",
    extraInfo: {
        favFood: "kimchi"
    }
}

// 만일 Player 타입이지만 extraInfo의 속성을 가지지 않는 객체의 경우
const lynn: Player<null> = {
    name: "lynn",
    extraInfo: null
}

Object Oriented Programming by using Type Script

  • 추상 클래스 (abstruct class 의 인스턴스는 생성할 수 없이, 상속만 가능하다.)
  • Type Script 는 ‘추상 메소드’를 생성하는 것이 가능하다.
    • 생성할 땐 반드시 추상 클래스 내에 선언된 메소드 내에서 생성해야 함.
    • 추상메소드는 콜 시그니쳐만 작성한다.
abstract class User { // 추상 클래스 생성
    constructor( // TypeScript 에선 생성자를 더 편하게 작성할 수 있다.
    private firstName:string,
    protected lastName:string,
    public nickName:string
    ) {}
//    constructor(firstName, lastName, nickName) {
//        this.firstName = firstName;
//        this.lastName = lastName;
//       this.nickName = nickName;
//    }

    abstract getNickname():void // 추상메소드 -> 콜 시그니쳐만 작성.

    getFullName():string{
        return `${this.firstName} ${this.lastName}` 
				// python 의 f string 을 js 에선 이렇게 사용.       
    }
}

class Player extends User{
    getNickname(){ // 콜 시그니쳐만 사용된 추상 메소드를 자식 클래스에서 생성.
        console.log(this.lastName)
    }
}

const haram = new Player("haram","jeong","haram")
console.log(haram.getFullName())
console.log(haram.lastName) // Error  !! -> property error
  • 생성자 전에 property 를 만들고 → 생성자에서 초기값 생성 가능.
  • 아무 이름의 이 타입을 가진 것을 속성으로 정의
  • 클래스를 type 처럼 쓸 수 있음
// 얘를 추상클래스가 아닌 인터페이스로 상속받을 수 있게 한다면 파일 크기를 매우 줄일 수 있다
// js 에선 추상클래스가 없으므로 그대로 클래스를 가져오지만 인터페이스는 아예 가볍게 사라진다.
// 그래서 여러 개의 인터페이스를 만들어서 상속할 수 있도록 설계한다.

interface User{
    firstName: string,
    lastName: string,
    fullName(): string,
    sayHi(name:string): string
}

interface Human{
    health: number
}

class Player implements User,Human{
    constructor(
        public firstName: string,
        public lastName: string,
        public health: number
    ) {}
    fullName(){
        return `${this.firstName} ${this.lastName}`
    }
    sayHi(name: string){
        return `hi ${name}. my name is ${this.fullName()}`;
    }
}
  • Interface 는 오브젝트의 모양을 결정지을 수 있지만,
    클래스의 모양을 특정 짓기도 함.
  • 인터페이스도 타입으로 지정할 수 있다. → 클래스를 타입으로 쓰는 것처럼 ; 너무당연.
  • 인터페이스는 {타입스크립트 → 자바스크립트}로 컴파일되지 않는다.
    • 추상클래스와 인터페이스의 기능은 비슷하지만 인터페이스를 쓰면 컴파일 시 js 파일의 크기가 매우 작아질 수 있다.
    • 추상클래스 → 자바스크립트로 컴파일 시, 추가 클래스가 생성됨.

‘==’ 비교 연산자와 ‘===’ 비교 연산자의 차이

TypeScript에서 == 연산자와 === 연산자는 값들을 비교하는 데 사용되는 두 가지 다른 연산자.

  1. == 연산자 (동등 연산자):
    • == 연산자는 값의 동등성을 비교함.
    • 값의 형식(데이터 타입)을 무시하고 값 자체만을 비교함.
    • 값이 동일한지 비교하기 전에 필요에 따라 값의 형 변환을 수행함.
    • 예를 들어, 문자열과 숫자를 비교할 때, 문자열을 숫자로 변환하고 비교함.

예시:

// typescript ex
5 == 5    // true
'5' == 5   // true (형변환 이후 비교 => 값 자체는 동일 하므로 true)
'hello' == 'world'  // false
  1. === 연산자 (일치 연산자):
    • === 연산자는 값과 데이터 타입 모두를 비교함.
    • 데이터 타입이 모두 동일한 경우에만 true를 반환함.
    • 형 변환을 수행하지 않기 때문에 데이터 타입이 다르면 false를 반환함.
// typescript ex
5 === 5   // true
'5' === 5  // false

일반적으로 TypeScript 및 JavaScript에서는 === 연산자를 권장 (형 변환이 발생하지 않아 예기치 않은 동작을 방지)

== 연산자는 예기치 않은 형 변환이 발생할 수 있어서 버그의 원인이 됨.

⇒ 값을 비교할 때는 데이터 타입과 값을 모두 고려하는 === 연산자를 사용하는 것이 안전.

0개의 댓글