IT/Typescript

타입스크립트 정리

라임웨일 2022. 9. 8. 15:32
반응형

📖 타입스크립트를 사용해야 하는 이유

⭐ Javascript

  • 자바스크립트는 매우 유연한 언어라 에러를 사전에 보여주지 않기 때문에 코드에서 오류가 발생할 가능성이 높아집니다.
// 예시) 🚫 숫자 배열 + false
[1,2,3,4] + false → '1,2,3,4false' // 배열이 사라지고 string으로 바뀜
// 🚫 함수의 인자가 잘못 들어가도 실행됨
function add(a, b) {
  return a + b
}
add(1) → NaN //return값이 NaN일 뿐, 에러가 나지 않음
const a = { a: "A" };
a.hello();
// 선언 되지 않은 함수를 호출할 때: 실행 시 에러 발생(실행 전에 에러 감지 불가)

⭐ Tyescript

  • 타입 안정성과 사전에 에러를 발생시켜 오류(버그)를 감소시킵니다.
  • TypeScript는 JavaScript에 추가적인 구문을 추가하여 editor와의 단단한 통합을 지원합니다. editor에서 초기에 오류를 잡을 수 있습니다.
  • TypeScript 코드는 JavaScript가 실행되는 모든 곳(브라우저, Node.js 또는 Deno 및 앱 등)에서 JavaScript로 변환될 수 있습니다.
  • TypeScript는 JavaScript를 이해하고 타입 추론(type inference)을 사용하여 추가 코드 없이도 훌륭한 도구를 제공합니다.

타입스크립트 코드 테스트

https://www.typescriptlang.org/play

타입스크립트 핸드북

https://typescript-kr.github.io/pages/basic-types.html

타입스크립트 Type 정의

  • ✅ 배열: 자료형[]
  • ✅ 숫자: number
  • ✅ 문자열: string
  • ✅ 논리: boolean
  • ✅ optional

명시적 정의(변수 선언 시 타입 정의)

let a: boolean = "x"
// 🚫 boolean 타입에 string타입 할당 불가 알림

변수만 생성(타입 추론)

let b = "hello"; // b가 string 타입이라고 추론
b = 1 // string으로 선언한 변수에 number값 할당
// 🚫 string 타입에 number타입 할당 불가 알림

Alias(별칭) 타입, optional로 선언

type-aliases

→ 타입값에 ?로 값을 주면 optional

❗ ?를 :앞에 붙이면 optional

// ✅ Alias(별칭) 타입
type Player = {
    name: string,
    age?:number
}

const player : Player = {
    name: "nico"
}

⭐ 함수에서는 어떻게 쓸까
type Player = {
    name: string,
    age?:number
}
// ❌ player.age가 undefined일 가능성 알림
if(player.age < 10) {
}

// ⭕ player.age가 undefined일 가능성 체크
if(player.age && player.age < 10) {
}

함수에서 사용시

function playerMaker1(name:string) : Player {
    return {
        name
    }
}

const playerMaker2 = (name:string) : Player => ({name})

const nico = playerMaker1("nico")
nico.age = 12

readonly

JavaScript에서는 mutability(변경 가능성)이 기본값이지만 타입스크립트에서는 readonly를 통해 읽기 전용으로 만들 수 있습니다.

interface Pizza {
  readonly x: number;
}

let pizza: Pizza = { x: 1 };
pizza.x = 12; // error


const numbers: readonly number[] = [1, 2, 3, 4]
🚫 numbers.push(1)

// ❗ readonly가 있으면 최초 선언 후 수정 불가 ⇒ immutability(불변성) 부여
// but, javascript에서는 그냥 배열

readonly-and-const

Tuple

정해진 개수와 순서에 따라 배열 선언

const player: [string, number, boolean] = ["nico", 1, true]
// ❗ readonly도 사용가능 ⇒ readonly [...] 형태

undefined, null, any

  • any: 아무 타입
  • undefined: 선언X 할당X
  • null: 선언O 할당X

unknown

unknown타입은 모든 값을 나타냅니다. 이것은 any타입과 비슷하지만 any보다 unknown이 더 안전합니다. 이유는 unknown값으로 작업을 수행하는 것은 합법적이지 않기 때문입니다.

// 예시1
let a:unknown

if(typeof a === 'number'){
  let b = a + 1
}
if(typeof a === 'string'){
  let b = a.toUpperCase()
}
🚫 let b = a + 1

// 예시2
function hello(a: any) {
  a.b(); // OK
}

function hello2(a: unknown) {
  a.b(); // 에러: Object is of type 'unknown'.
}

void

void는 값을 반환하지 않는 함수의 반환 값을 나타냅니다. 함수에 return 문이 없거나 해당 return 문에서 명시적 값을 반환하지 않을 때 항상 유추되는 타입입니다.

function hello() {
  console.log('x')
}

function noop() {
  return;
}

never

일부 함수는 값을 반환하지 않습니다. 이는 함수가 예외를 throw하거나 프로그램 실행을 종료함을 의미합니다.

function fail(msg: string): never {
  throw new Error(msg);
}

function temp(name:string|number):never {
  if(typeof name === "string"){
    name
  } else if(typeof name === "number"){
    name
  } else {
    name
  }
}
  • if 안에서는 string형의 name 반환합니다.
  • else if 안에서는 number형의 name 반환합니다.
  • else 안에서는 never형의 name 반환합니다.
  • ⇒ 즉, 제대로 인자가 전달되었다면 else로 올 수 없습니다.

Function

Call Signatures

프로퍼티로 호출 가능한 것을 설명하려면 객체 타입에 Call Signature을 작성할 수 있습니다. Call(=Function) Signature란 함수의 매개변수와 반환 값의 타입을 모두 type으로 미리 선언하는 것을 말하며 Call Signatures는 다음과 같이 함수의 매개 변수(parameter)와 반환 타입을 지정합니다.

type Add = {
  (a: number, b: number): number;
}

const add: Add = (a, b) => a + b

주의

const add:Add = (a,b) => a+b
// 위의 Arrow함수를 일반 함수로 풀면 다음과 같게 됩니다.
function add(a, b) {
  return (a+b)
}

const add:Add = (a,b) => {a+b}
// 위의 Arrow함수를 일반 함수로 풀면 다음과 같게 됩니다.
function add(a, b) {
  a+b;
}

// 애로우함수에서 {}를 사용하게 되면 그 안의 값은 반환이 아니라 함수 내부 내용으로 처리되기에 반환값이 없는 void로 처리됩니다.
// 이에 따라 위에서 미리 선안한 Add자료형의 반환값은 number라고정해놓은 내용과 충돌하기에 에러가 발생합니다.
type PizzaFunction = {
  pizza: string;
  (args: number): boolean;
};

function hello(fn: PizzaFunction) {
  console.log(fn.pizza, fn(6));
}

call-signatures

Overloads

하나의 함수가 복수의 Call Signature를 가질 때 발생합니다. 동일한 이름에 매개 변수와 매개 변수 타입 또는 리턴 타입이 다른 여러 버전의 함수를 만드는 것을 말합니다. TypeScript에서는 오버로드 signatures을 작성하여 "다양한 방식으로 호출할 수 있는 함수"를 지정할 수 있습니다.

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;
}

add(1,2)
add(1,2,3)

overloads

polymorphism(다형성)

다형성이란, 여러 타입을 받아들임으로써 여러 형태를 가지는 것을 의미합니다. 인자들과 반환값에 대하여 형태(타입)에 따라 그에 상응하는 형태(타입)를 갖을 수 있습니다.

타입스크립트에서 다형성을 가지기 위해선 제너릭을 사용합니다. 제네릭은 C#이나 Java와 같은 언어에서 재사용 가능한 컴포넌트를 만들기 위해 사용하는 기법으로 단일 타입이 아닌 다양한 타입에서 작동할 수 있는 컴포넌트를 생성할 수 있습니다.(구체적인 타입을 지정하지 않고 다양한 인수와 리턴 값에 대한 타입을 처리할 수 있습니다.) 타입스크립트에서 제네릭을 통해 인터페이스, 함수 등의 재사용성을 높일 수 있습니다. 제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이라 정리할 수 있습니다.

재너릭을 사용하지 않았을 때

// 모든 타입을 다 적어주어야 함
type SuperPrint = {
  (arr: number[]): void,
  (arr: string[]): void,
  (arr: boolean[]): void,
  (arr: (number|string|boolean)[]): void
}

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

superPrint([1, 2, 3])
superPrint(["a", "b", "c"])
superPrint([true, false, true])
superPrint([1, "b", true])

위와 같이 다양한 경우를 커버하는 함수를 작성할 때, 모든 조합의 Call Signature를 concrete type으로 적어주는 일은 번거롭습니다.

재너릭을 사용할 때

type SuperPrint = {
  (arr: T[]): T
}

const superPrint: SuperPrint = (arr) => arr[0]

// (arr: number[]) => number
superPrint([1, 2, 3])

// (arr: string[]) => string
superPrint(["a", "b", "c"])

// (arr: boolean[]) => boolean
superPrint([true, false, true])

// (arr: (string | number | boolean)[]) => string | number | boolean
superPrint([1, "b", true])

위의 코드처럼 type에 Generic을 할당하면 호출된 값으로 concrete type을 가지는 Call Signature를 역으로 보여주는 다형성(Polymorphism)을 가지게 됩니다

함수에서 제너릭을 사용할 때

function identity< Type >(arg: Type): Type {
  return arg;
}

// 제네릭 화살표 함수 (tsx기준)
const identity=< Type extends {} >(arg: Type):Type => {
  return arg;
}

let output = identity< string >("myString"); // 첫 번째 방법
let output = identity("myString"); // 두 번째 방법
// 두 번째 방법은 type argument inference(타입 인수 유추)를 사용합니다. 즉, 컴파일러가 전달하는 인수 유형에 따라 자동으로 Type 값을 설정하기를 원합니다.

any를 넣는 것과 Generic의 차이는 무엇일까요?

// Any
type SuperPrint = {
  (arr: any[]): any
}

const superPrint: SuperPrint = (arr) => arr[0]

let a = superPrint([1, "b", true]);

// pass
a.toUpperCase(); // any를 사용하면 위와 같은 경우에도 에러가 발생하지 않습니다.

// Generics
type SuperPrint = {
  (arr: T[]): T
}

const superPrint: SuperPrint = (arr) => arr[0]

let a = superPrint([1, "b", true]);

// error
a.toUpperCase();
// Generic의 경우 에러가 발생해 보호받을 수 있다
// 이유는 Call Signature를 concrete type으로 하나씩 추가하는 형태이기 때문입니다.

type SuperPrint = {
  (arr: T[], x: M): T
}

const superPrint: SuperPrint = (arr, x) => arr[0]

let a = superPrint([1, "b", true], "hi");

// 위와 같이 복수의 Generic을 선언해 사용할 수도 있습니다

'any'를 사용하는 것은 어떤 타입이든 받을 수 있다는 점에서 'generics'과 같지만 함수를 반환하는데 있어 'any'는 받았던 인수들의 타입을 활용하지 못합니다. 정리하면 any와의 차이점은 해당 타입에 대한 정보를 잃지 않는다는 차이점으로 any는 any로서 밖에 알 수 없지만 generics는 타입 정보를 알 수 있습니다.

Generics

classes And Interfaces

classes

추상(abstract) 클래스

추상 클래스는 오직 다른 클래스가 상속받을 수 있는 클래스로 직접 새로운 인스턴스를 만들 수는 없습니다.

abstract class User {
  constructor(
    private firstname:string,
    private lastname:string,
    public nickname:string
  ){
    abstract getNickname():void
  }
}

class Player extends User {
// 추상 메서드는 추상 클래스를 상속받는 클래스들이 반드시 구현(implement)해야하는 메서드입니다.
  getNickname(){
    console.log(this.nickname)
  }
}

접근 가능한 위치 정리

구분 선언한 클래스 내상속받은 클래스 내인스턴스

private
protected
public

public: 모든 클래스에서 접근 가능 private: 해당 클래스 내에서만 접근 가능 (자식 클래스에서도 접근 불가) protected: 해당 클래스와 자식 클래스에서 접근 가능

예시: 사전 구현하기

type Words = {
  [key: string]: string;
};

class Dict {
  private words: Words;
  constructor() {
    this.words = {};
  }

  add(word: Word) {
    if (this.words[word.term] === undefined) {
      this.words[word.term] = word.def;
    }
  }

  def(term: string) {
    return this.words[term];
  }

  update(word: Word) {
    if (this.words[word.term] !== undefined) {
      this.words[word.term] = word.def;
    }
  }

  del(term: string) {
    if (this.words[term] !== undefined) {
      delete this.words[term];
    }
  }
}

class Word {
  constructor(public term: string, public def: string) {}
}

const kimchi = new Word("kimchi", "super cool food");
const pizza = new Word("pizza", "super nice piazza");
const dict = new Dict();

dict.add(kimchi);
dict.add(pizza);
console.log("KIMCHI:", dict.def("kimchi"));
console.log("PIZZA:", dict.def("pizza"));

dict.update(new Word("kimchi", "very incredible super food"));
console.log("UPDATE KIMCHI:", dict.def("kimchi"));
console.log("NOT UPDATE PIZZA:", dict.def("pizza"));

dict.del("pizza");
console.log("DELETE PIZZA", dict.def("pizza"));
console.log("NOT DELETE KIMCHI:", dict.def("kimchi"));

classes

Static Members

클래스에는 static 멤버가 있을 수 있습니다. 이 멤버는 클래스의 특정 인스턴스와 연결되지 않습니다. 클래스 생성자 객체 자체를 통해 액세스할 수 있습니다. static 멤버는 동일한 public, protected 및 private 과 함께 사용할 수도 있습니다. static Methods는 클래스를 생성안해도 사용이 가능합니다.

class MyClass {
  static x = 0;
  static printX() {
    console.log(MyClass.x);
  }
}
console.log(MyClass.x);
MyClass.printX();

static-members

Interfaces

객체의 모양을 특정해주기 위해 사용합니다. 여기서는 firstName 및 lastName 필드가 있는 객체를 설명하는 인터페이스를 사용합니다.

interface Person {
  firstName: string;
  lastName: string;
}

interfaces

implements

implements을 사용하여 클래스가 특정 인터페이스를 충족하는지 확인할 수 있습니다. 클래스를 올바르게 구현하지 못하면 오류가 발생합니다. implements절은 클래스가 인터페이스 유형으로 처리될 수 있는지 확인하는 것입니다. 클래스의 유형이나 메서드는 전혀 변경하지 않습니다. 또한 클래스는 여러 인터페이스를 구현할 수도 있습니다.

// 클래스 C는 A, B를 구현합니다.
class C implements A, B { }
interface Pingable {
  ping(): void;
}

// Sonar클래스는 Pingable인터페이스를 implement했기 때문에 Pingable가 가진 ping메서드를 구현해줘야 합니다.
class Sonar implements Pingable {
  ping() {
    console.log("ping!");
  }
}

implements

Type Aliases과 Interfaces의 차이점

Type Aliases과 인터페이스는 매우 유사하며 많은 경우 자유롭게 선택할 수 있습니다. 인터페이스의 거의 모든 기능은 type에서 사용할 수 있으며, 주요 차이점은 type을 다시 열어 새 속성을 추가할 수 없는 것입니다. 반면 인터페이스는 항상 확장 가능합니다.

differences-between-type-aliases-and-interfaces

@ts-check

JavaScript 파일에서 오류를 활성화하려면 // @ts-check를 .js 파일의 첫 번째 줄에 추가하여 TypeScript가 오류를 발생시키도록 합니다. TypeScript는 �여러 오류를 제공할 수 있습니다. 이러한 오류를 무시하고 싶다면 // @ts-ignore 또는 // @ts-expect-error를 추가하여 특정 줄의 오류를 무시할 수 있습니다. ts-check

JSDoc Reference

JSDoc 주석을 사용하여 JavaScript 파일에 type 정보를 제공할 수 있습니다. (자바스크립트 파일에서 타입 정보를 제공할 수 있습니다.) jsdoc-supported-types

@param, @returns

/**
* @param {string} p1 - A string param.
* @param {string=} p2 - An optional param (Google Closure syntax)
* @param {string} [p3] - Another optional param (JSDoc syntax).
* @param {string} [p4="test"] - An optional param with a default value
* @returns {string} This is the result
*/
function stringsStringStrings(p1, p2, p3, p4) {
  // 코드...
}
반응형

'IT > Typescript' 카테고리의 다른 글

Styled Components & TypeScript  (0) 2022.05.24
Typescript에서 React location의 state값 선언  (0) 2022.05.19
Object is possibly 'null' 오류 해결  (0) 2022.05.19
광고차단(애드블록)프로그램이나 브라우저를 사용중이시면 프로그램을 비활성화이나 블로그 주소를 예외 처리 해주시면 광고가 노출됩니다. 귀찮고 번거롭겠지만 광고 클릭은 저에게는 큰 힘이 됩니다. 🙇🏻‍♂️🙇🏻‍♀️
제 블로그의 모든 글은 제가 직접 작성 하고 다른 글을 참고할 때는 이전 글보다 읽기 편하게 수정해서 작성하고 있습니다. 커피 한잔 사먹고 더 열심히 좋은글로 보답하겠습니다.
오늘도 제 블로그에 와 주셔서 감사합니다. :)
//