Typescript

hwisaac·2023년 1월 31일
0

typescript

목록 보기
1/3

타입스크립트 시작하기

컴파일러 설치 : npm 프로젝트 중 (https://www.w3schools.com/typescript/typescript_getstarted.php)

  1. npm install typescript --save-dev : 컴파일러가 node_modules 에 설치된다. (실행: npx tsc)
  2. npx tsc --init : tsconfig.json 파일을 만들어서 추천세팅을 적용한다.
Created a new tsconfig.json with:
TS
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true
{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "./build"
  }
}
  • 이 코드를 tsconfig.json 파일에 추가하면 src/ 에 있는 타입스크립트 파일들이 build/ 폴더로 트랜스파일된다.

기본적인 타입스크립트 프로젝트 만들기

  1. mkdir [폴더명] : 프로젝트 폴더를 만든다.
  2. vscode로 해당 폴더를 연다
  3. npm init -y
  4. npm i -D typescript : 타입스크립트를 설치한다.
  5. touch tsconfig.json : 타입스크립트 설정 파일을 만든다.
  6. src/index.ts : 파일을 만들어서 코드를 작성한다.
  7. package.json 에서 "test" 스크립트는 제거
  8. "build": "tsc" 스크립트 추가 : 해당 스크립트가 실행되면 ts를 js로 컴파일한다.
  9. "start" : "node build/index.js" 스크립트추가. npm run build && npm run start 를 실행하면 ts를 js로 번역하고 프로젝트를 빌드해준다.
    1. 이를 간달하게 하기 위해 npm i -D ts-node ts-node 를 설치하면 빌드 없이 ts 를 실행할 수 있게 해준다.(개발환경에서 사용)
    2. "dev" : "ts-node src/index.ts" 스크립트 추가.
  10. npm i nodemon nodemon 은 저장하면 자동으로 커맨드를 재실행해준다.
    1. 이제 dev 스크립트 앞에 nodemon --exec 을 추가한다
    2. "dev": "nodemon --exec ts-node src/index.ts"

tsconfig.json 옵션

옵션키설명옵션벨류의미
"include"컴파일하고싶은 디렉토리들을 넣는다.["src"]src 폴더 내 모든ts파일을 컴파일
"compilerOptions"js파일이 생성될 디렉토리{"outDir":"build", "target":"ES3"}build폴더에 ts를 ES3버전으로 컴파일한 js파일을 넣는다.
"lib"라이브러리의 정의파일(.d.ts)을 특정해준다["ES6","dom"]ES6를 지원하는 환경에서 실행된다.
브라우저위에서도 실행된다.(lib.dom.d.ts 파일을 읽어와서 document등에 대한 자동완성기능 제공)
"stirct"strict모드를 켤지 말지에 대한 옵션truestrict mode를 켠다
"allowJs"js도 import하게 할지 여부truets파일에서 js파일을 import하기 허용
"esModuleInterop"es6 모듈사양을 준수하여 commonJS 모듈을 가져오는걸 허용할지 여부trueimport abc from "abc" 문법을 사용할수 있게 해준다.
허용하지 않으면 require(~)나 import * as ~ from 을 사용해야된다.
"module"모듈시스템"CommonJs"CommonJs 모듈시스템을 사용한다.

declaration 파일 (d.ts)

  • 대부분의 패키지/프레임워크/라이브러리는 javascript 로 만들어져 있다.
  • 이들을 typescript 에 쓰기 위해서 타입스크립트에게 파일과 모듈을 위한 타입정의를 제공해야 한다. 이때 사용하는 것이 declaration 파일이다.
  • 이 파일에는 call signatures 들로 가득차 있고 타입스크립트는 이 정보를 가져온다.
// /src/myPackage.js
export function init(config) {
  return true;
}
export function exit(coe) {
  return code + 1;
}
// /src/myPackage.d.ts

interface Config {
  url: string;
}

declare module "myPackage" {
  function init(config: Config): boolean;
  function exit(code: number): number;
}
// src/index.ts
imoprt { init, exit } from "myPackage"; // myPackage 에서 init과 exit 을 import
//타입스크립트에 타입 설명을 했으므로 불평하지 않는다.
init({
  url: "abaaa"
})

JSDoc

// src/index.ts
imoprt { init, exit } from "./myPackage"; // myPackage.js 에서 init과 exit 을 import. allowJs 가 꺼져있으면 에러가 발생한다.

// (alias) function init(config: any): boolean   -> ts가 추론한 타입
init({
  url: "abaaa"
})

자바스크립트 파일은 그대로 보존하고, 타입스크립트의 보호를 받고 싶은 경우

allowJs 옵션을 true 로 바꾼뒤,
> JSDoc 문법으로 @ts-check 주석을 달아주면 해결이 된다.
> @ts-check 는 ts파일에게 js 파일을 확인하라는 의미
이 방식으로 js 파일에 선언된 함수도 typescript 의 보호를 받을 수 있게 된다.

// /src/myPackage.js  (자바스크립트 파일)

/**
 * 프로젝트를 초기화한다.
 * @param {object} config
 * @param {boolean} config.debug
 * @param {string} config.url
 * @returns boolean
 */
export function init(config) {
  return true;
}
/**
 * 프로그램을 종료한다.
 * @param {number} code
 * @returns number
 */
export function exit(code) {
  return code + 1;
}

DefinitelyTyped 레포지토리

  • 특정 패키지를 사용하려고 할 때마다, 해당 패키지의 .d.ts 파일을 만들수는 없다.
  • DefinitelyTyped 레포지토리에는 많은 자원봉사자들이 .d.ts 파일을 만들어서 패키지 들에 대한 타입의 정보를 제공하고 있다.
  • 원하는 패키지의 타입을 설치하려면 npm i -D @types/패키지명
  • 만약 nodejs 에 대한 타입을 설치하려면 npm i -D @types/node 를 터미널에 입력하면 된다.

타입들

  1. number
  2. string
  3. boolean
  4. null
  5. undefined
  6. any
  7. void
  8. unknown
  9. number[]
  10. string[]
  11. boolean[]
  12. Tuple

참고) object 타입은 존재하지 않음

문법

선언

  • let a : number = 10 이런식으로 변수 뒤에 : 타입 을 선언해준다.
  • 하지만 타입스크립트가 타입을 추론하게 해주는 게 더 좋다

옵셔널

  • 변수명에 ? 를 붙여준다.
  • age?: number 의미는 age 가 number 거나 undefined 임을 의미한다.

객체의 선언

const playerJohn: {
  name: string;
  age: number;
} = {
  name: "john",
  age: 20,
};
개선하기 : Alias 타입
type Player = {
  name: string;
  age?: number;
};
const playerJohn = {
  name: "john",
  age: 20,
};
  • 타입을 재사용할수 있게 해준다.

함수 리턴값의 타입 정하기

  • parameters 를 담은 괄호 뒤에 명시해주면 된다.
type Player = {
  name: string;
  age?: number;
};

function playerMaker(name: string): Player {
  return {
    name,
  };
}

const john = playerMaker("john");

화살표함수일 경우

type Player = {
  name: string;
  age?: number;
};

const playerMaker = (name: string): Player => ({ name });

const john = playerMaker("john");

readonly 속성

  • playername 요소를 가지고 있다고 해보자
  • name 요소는 readonly(읽기전용) 가 된다고 하자
type Age = number;
type Name = string;
type Player = {
  readonly name: Name;
  age?: Age;
};

const playerMaker = (name: string): Player => ({ name });

const john = playerMaker("john");
  • 이렇게 하면 readonlyname 을 수정할 수 없게된다!

다른 예시

const numbers: readonly number[] = [1, 2, 3, 4];

numbers.push(1); // 에러난다. push 메소드를 readonly number[] 타입에서 제거해버렸기 때문

배열을 바꾸지 않는 filter 나 map 등은 readonly 여도 사용할 수 있다.

Tuple 타입

  • 튜플은 최소한의 길이를 가져야하고,
  • 특정 위치에 특정 타입이 있어야 한다
  • 예) ["John", 12, false] 의 타입을 만들자
const player: [string, number, boolean] = ["John", 12, false];

tuple + readonly

const player: readonly [string, number, boolean] = ["John", 12, false];

어떤타입인지 모르는 변수 : unknown 타입

let a: unknown;
let b = a + 1; // 에러발생. a가 unknown 이므로
if (typeof a === "number") {
  let b = a + 1; // 문제없음
}

void

  • 아무것도 반환하지 않는 함수는 알아서 함수 반환타입이 void 로 적용된다.

never

  • 함수가 절대 반환하지 않는 경우 함수의 반환타입이 never
  • 예) 함수에서 exception(예외) 이 발생
// 에러를 던지는 함수이므로 반환 타입을 never로 설정해야 한다.
function sayHi(): never {
  throw new Error("error!");
}

다른예시

  • never 는 타입이 두가지 일 수 있는 상황에서 발생할 수 있다.
function sayHi(name: string | number) {
  if (typeof name === "string") {
    // name : string
  } else if (typeof name === "number") {
    // name : number
  } else {
    // name : never
  }
}
  • 파라미터인 name 의 타입이 string 과 number 만 들어올 수 있는데, if문으로 두 경우를 모두 처리해줬으니 마지막 else 구문에서는 never 타입이 된다.
  • 절대 실행되지 않아야 하는 부분이기 때문 ( 즉, 이 블록은 에러를 던져야 하는 부분이다. )

Call Signatures

// Bad
function add(a, b) {
  // a와 b에 에러발생. 타입을 모르기 때문에 any 타입이 되기 때문
  return a + b;
}

// Good
function add(a: number, b: number) {
  return a + b;
}
  • a와 b 매개변수에 타입을 선언해주고 싶지 않을 때 : Call signature 를 작성하면 된다
  • call signature 는 함수위에 마우스를 올렸을 때 함수를 설명해주는 부분이다.
  • call signature 는 함수의 매개변수와 반환값에 대한 타입을 설명해준다. (즉 함수의 설명서)
  • 함수에 관한 타입선언과 함수의 정의부분을 분리시켜준다.

Call signature 의 선언

// 방법1
type Add = (a: number, b: number) => number;
// 방법2
type Add = {
  (a: number, b: number): number;
};

// Good 에러없음
const add: Add = (a, b) => a + b;

Polymorphism (다형성)

polymorphism : 여러가지 다른 형태

1. Overloading

  • 실제로 오버로딩된 함수를 작성할 일은 거의 없다.
  • 다른사람들이 만든 외부 라이브러리를 사용할 때 패키지나 라이브러리들이 오버로딩을 사용한다.
  • 오버로딩은 함수가 여러개의 call signatures 를 가질 때 발생한다
type Add = {
  (a: number, b: number): number;
  (a: number, b: string): number;
};
// 에러발생. call signatures가 두개라서 b 가 어떤타입인지 모르기 때문
const add: Add = (a, b) => a + b;

// Good 어떤타입인지 몰라도 작동하는 로직이기 때문
const add: Add = (a, b) => {
  if (typeof b === "string") return a;
  if (typeof b === "number") return a + b;
};

여러 call signatures 가 존재할 때, parameter 의 타입이 달라질 수는 있지만, parameter 의 개수가 동일했다.

parameter 의 개수가 서로 다른 call signatures 가 존재할 경우

  • 옵셔널 변수가 존재하는 경우를 의미
  • 이 때 함수를 선언할때 단독 call signature 만으로 typescript 가 만족하지 않기 때문에, 옵셔널 변수와 타입을 알려줘야 한다
// c 파라미터가 optional 인 경우
type Add ={
  (a:number, b:number) : number
  (a:number, b:number, c:number) : number
}

// add 에서 에러
const add: Add (a,b,c) =>{
  return a+b
}

// c 가 옵셔널인것과 타입을 명시해야 한다.
const add: Add (a,b,c?:number) =>{
  return a+b
}

2. generic

  • 타입스크립트는 generic 을 통해서도 polymorgphism 을 준다.
type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  (arr: string[]): void;
};

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

superPrint([1, 2, 3, 4]);
superPrint([false, false, true, true]);
superPrint(["a", "b", "c"]);
  • 위의 코드는 유효하지만, superPrint(1,2, true, "a"); 와 같은 다양한 방법으로 함수를 이용하고 싶을 때마다 타입을 다시 명시해야 하는 문제가 생긴다.
  • concrete typenumber, boolean, string, void 들을 일일히 적어주는 것보다도 더 좋은 방식이 있다

"Generic 을 사용한다!"

  • generic 은 타입의 placeholder 같은 개념
  • 들어올 parameter 의 정확한 타입을 지정하지 않아도 정상적으로 작동하는 함수를 구현할 수 있을 때 사용한다.
  • generic 이 처음 사용되는 지점을 기반으로 타입을 추론한다.
  • generic 은 입력된 arguments 를 보고 타입을 추론하도록 타입스크립트에게 지시해준다.
type SuperPrint = {
  (arr: number[]): void;
  (arr: boolean[]): void;
  (arr: string[]): void;
};

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

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

generic 사용문법

  • call signature 선언의 '앞'generic 을 사용한다고 알려준다.
  • 또는, 타입명의 바로 뒤에 generic을 사용한다고 알려준다.
// 방법1
type SuperPrint = {
  <T>(arr: T[]): void;
};
// 방법2
type SuperPrint<T> = {
  (arr: T[]): void;
};

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

// const superPrint: <number>(arr: number[]) =>void
superPrint([1, 2, 3, 4]);
// const superPrint: <boolean>(arr: boolean[]) =>void
superPrint([false, false, true, true]);
// const superPrint: <string>(arr: string[]) =>void
superPrint(["a", "b", "c"]);
// const superPrint: <number>(arr: number[]) =>void
superPrint(1, 2, true, "a");
// Bad ( Error: T 에는 .length 를 할수가 없다. )
function echo<T>(text: T): T {
  console.log(text.length);
  return text;
}
// Good : text 가 배열이라는 것을 알려줘서 length를 구할수 있게 한다.
function echo<T>(text: T[]): T {
  console.log(text.length);
  return text;
}

// Good
function echo<T>(text: Array<T>): Array<T> {
  console.log(text.length);
  return text;
}

generic 활용 방법

  • 타입을 상속할 때 제네릭은 타입 뒤에 붙여서 타입의 변수처럼 사용한다.
  • Player<T> 이면 T자리에 또다른 타입을 넣을 수 있다.
  • number[] 타입도 을 이용하면 Array<number> 로 쓸 수 있다. (타입스크립트가 Array 라는 타입을 generic 을 이용해 Array<T> 로 정의하고 있기 때문)
  • React에서 useState<number>() 를 사용하여 call signature 를 정의하는 generic 에 number를 전달할 수있다.
    • 이게 가능한 이유는 useState 함수의 타입을 제네릭을 이용해 정의하기 떄문이다.
type Player<T> = {
  name: string
  extraInfo: T
}
const john = Player<{favFood:string}> = {
  name: "john"
  extraInfo: {
    favFood: "banana"
  }
}
// 타입선언
type Player<T> = {
  name: string
  extraInfo: T
}
type JohnExtra = {
  favFood: string
}
type JohnPlayer<T> = Player<{favFood:string}>

// 타입적용
const john : JohnPlayer = {
  name: "john"
  extraInfo: {
    favFood: "banana"
  }
}
const bob : Player<null> = {
  name: "bob"
  extraInfo: null
}

generic 더 많은 예시

type GenericExample = <T>(a: T[]) => T;
type GenericExample = <T, M>(a: T[], b: M) => T;

interface GenericExample {
  <T>(a: T): T;
}
// 함수 선언시 generic 문법 적용
function echo<T>(a: T): T {
  return a;
}

const greeting = echo("hello"); // good 타입스크립트가 유추하게 해서 좋다.
const greeting = echo<string>("Hello"); // 더 명시적이다. T 에 string 이 대입된다.

인터페이스

// 방법1
interface IEcho {
  <T>(text: T): T;
}
// 방법2
interface IEcho<T> {
  (text: T): T;
}

function echo<T>(text: T): T {
  return text;
}

let myString: IEcho = echo;
let myString: IEcho<string> = echo; // 더 명시적 <string>

클래스

// 클래스
class GenericMath<T> {
  pi: T;
  sum: (x: T, y: T) => T;
}
let math = new GenericMath<number>();

any 와 generic 이 다른점

  • any 는 타입스크립트로의 보호를 벗어난다.

  • generic 은 타입스크립트의 보호를 받을 수 있다.

  • a 의 타입이 generic 에 의해 string|boolean|number|void|undefined 등 모든 타입이 될수 있다고 해보자.

  • a.toUpperCase() 는 에러가 난다. 왜냐면 string 일 경우에만 허용하려고 하기 때문이다.

  • 반면 a 의 타입이 any 인 경우 typescript 의 보호에서 벗어나있어서 에러를 내주지 않는다.

classes

ts 에서 클래스 선언하는 방법

  • ts 에서는 클래스 선언에 대해 더 좋은 문법을 제공해준다.
  • privatepublic 도 제공한다. (property 뿐만 아니라 method 에 대해서도 작동한다.)
  • propertyprivate 를 설정해두면, 해당 클래스를 상속받은 클래스여도 그 property에 접근할수가 없고 인스턴스 밖에서도 접근할 수 없다.
// 타입스크립트에서 클래스 선언
class Player {
  constructor(
    private firstName: string,
    private lastName: string,
    private nickname: string
  ){};
}

const john = new Player("john", "brown", "johnny");
// 자바스크립트 버전
class Player {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
const john = new Player("john", "brown", "johnny");

가상클래스 (Abstract Class)

  • 추상클래스는 다른 클래스가 상속받을 수 있는 클래스이다.
  • 추상클래스는 인스턴스를 만들 수 없다.
abstract class User {
  constructor(
    private firstName: string,
    private lastName: string,
    private nickname: string
  ){};
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

class Player extends User {}

const john = new Player("john", "brown", "johnny");

추상 메소드 Abstract Method

  • 추상 method 를 만들려면, 클래스안에서 구현하지 않고, 타입만 명시해준다.
  • 추상메소드를 가진 추상클래스를 상속하는 클래스는 추상메소드를 구체적으로 구현(implement) 해야만 한다.
abstract class User {
  constructor(
    private firstName: string,
    private lastName: string,
    private nickname: string
  );
  abstract getNickName(): void;
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

class Player extends User {
  getNickName() {
    console.log(this.nickname);
  }
}

const john = new Player("john", "brown", "johnny");
john.getNickName(); // 에러 : nickname 은 private 로 보호받는 property 인데 자식클래스에서 정의된 메소드로 접근하기 떄문
john.getFullName(); // 정상 출력됨. getFullName 은 추상클래스 내에서 정의됐고 & private 함수가 아니기 때문

protected 속성

  • protected 가 적용되면 외부로부터 보호되지만, 다른 자식 클래스에서는 접근할 수 있다! (private 보다 약한 보안)
abstract class User {
  constructor(
    private firstName: string,
    private lastName: string,
    protected nickname: string
  );
  abstract getNickName(): void;
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

class Player extends User {}

const john = new Player("john", "brown", "johnny");

dictionary 클래스 만들어보기

  • 제한된 양의 property 나 key 를 가지는 타입을 정의해줄 때 [whatever:string] 을 사용한다.
  • { [key:number] : string } 는 { 1: "food" , 2 : "apple" } 를 의미한다.
  • 어떤 파라미터가 특정 클래스의 인스턴스라는 것을 typescript 에게 말해줄 때, 클래스명타입처럼 명시해줄 수 있다.
class Word {
  constructor(
    public term: string,
    public def: string
  ){}
}

type Words ={
  [key:string] : string   // Words 타입은 string 만을 property로 갖는 오브젝트
}

class Dict{
  private words : Words // words가 어떻게 생겼는지 초기화를 해줘야 함
  constructor (){
    this.words = {}
  }
  add(word:Word){ // 클래스인 Word 를 마치 타입처럼 넣어줬다!!
    if(this.word[word.term] === undefined){
      this.words[word.term] = word.def;
    }
  }
  def(term:string){
    return this.words[term]
  }
}
{
  "potato": "food"
}



const kimchi = new Word("kimchi", "한국 음식")
const dict = new Dict()
dict.add(kimchi)
dict.def("kimchi") // "한국의 음식"
  • 이 코드에서 Word 클래스의 termdef property 는 public 으로 설정됐다. 왜냐면 Dict 클래스의 add 메소드를 만들 때 words.termword.def 에 접근해야 되기 때문이다.
  • public 일지라도 수정하지는 못하게 하려면 readonly 를 사용하면 된다.
  • public term: string -> public readonly term: string

static 메소드

  • static 메소드는 메소드를 프로퍼티 형태로 직접할당하는 것과 같은 일을 한다.
class Dict {
  private words: Words; // words가 어떻게 생겼는지 초기화를 해줘야 함
  constructor() {
    this.words = {};
  }
  add(word: Word) {
    // 클래스인 Word 를 마치 타입처럼 넣어줬다!!
    if (this.word[word.term] === undefined) {
      this.words[word.term] = word.def;
    }
  }
  def(term: string) {
    return this.words[term];
  }
  static hello() {
    return "hello";
  }
}

interface

type 과 interface 차이

type 은 원하는 타입을 정의할 수 있게 해준다.

// 타입스크립트에게 object가 어떻게 생겼는지 알려준다.
type Player = {
  nickname: string;
  age: number;
};

type Friends = Array<string>; // string[] 과 같은 표현인데, Array 의 generic 을 활용했다는 특징이 있다.

type Food = string; // alias

type Team = "red" | "blue" | "yellow"; // 타입제한
type age = 10 | 20 | 30;

interface클래스 또는 오브젝트의 모양을 설명해주는 방법이다.
interface 는 객체지향프로그래밍(oop) 의 개념으로 디자인 된 개념.

다용도인 type 과 다르게 interface 는 오직 object 에 대해서만 설명할 수 있다.
> interface 는 타입명 뒤에 = 기호를 붙이지 않는다.
> interfaceextends 로 타입을 상속 할 수 있다.
> interfaceimplements로 클래스에 상속시킬수 있다.
> interface 는 js 로 컴파일 되지 않고 사라진다. 이말은 interface 에서 상속 받는 방식으로 클래스를 만들면 .ts 파일내에서 타입스크립트의 보호를 받음과 동시에 js 파일이 가벼워 진다는 의미이다.

// 타입스크립트에게 object가 어떻게 생겼는지 알려준다.
interface Player {
  // Player ={ ... } 형태(type 방식)가 아니라 Player { ... }
  nickname: string;
  age: number;
}
interface Food = string  // 에러. 인터페이스는 alias 로 쓸수도 없고 = 도 붙이지 않는다

인터페이스는 개발자에게 클래스를 다루는 느낌을 줄 수 있다.

// interface 를 사용한 경우
interface User {
  name: string;
}

interface Player extends User {}

const john: Player = {
  name: "john",
};
// 클래스
abstract class User {
  constructor(name: string) {}
}

class Player extends User {}

const john = new Player("john");

타입을 사용하면 위의 둘과는 좀 다르게 보인다.

type User = {
  // 타입을 쓴 경우 = 기호를 붙여야 함
  name: string;
};

type Player = User & {
  // type 을 쓴 경우 extends 키워드를 사용x
};

const john: Player = {
  name: "john",
};

interface 를 사용하면 property 를 축적시킬 수 있다. type 은 불가능

interface User {
  name: string;
}
interface User {
  nickname: string;
}
interface User {
  age: number;
}

const john: User = {
  name: "john",
  nickname: "johnny",
  age: 20,
};
// Bad: type 으로는 객체의 타입을 중첩시키지 못하기 때문에 에러가 발생한다.
type User = {
  name: string;
};
type User = {
  nickname: string;
};

인터페이스를 쓸 때 클래스가 특정 형태를 따르도록 어떻게 강제할까 : "interfaceimplement 한다!"

추상클래스를 인터페이스로 바꿔보자.

abstract class User {
  constructor(protected firstName: string, protected lastName: string) {}
  // abstract 메소드가 선언되었기 때문에 User를 상속하여 만들어지는 클래스는 해당 메소드를 정의해줘야 한다.
  abstract sayHi(name: string): string;
  abstract fullName(): string;
}

class Player extends User {
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  sayHi(name: string) {
    return `Hello ${name}`;
  }
}

const john = new Player("john", "brown");

바꾼후

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

class Player implements User {
  constructor(
    public firstName: string, // User 를 상속할 때는 private 로 만들 수 없다. User 의 형태를 따라 private여야 한다.
    public lastName: string
  ) {}
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  sayHi(name: string) {
    return `Hello ${name}`;
  }
}

const john = new Player("john", "brown");

abstract class 대신 interface 로 작성하면

장점

abstract class 에서 interface 로 바꾸면서 js 코드를 압축할 수 있다.
협업시 interface 를 사용하여 각자의 방식으로 클래스를 상속하면, 객체지향적 프로그래밍을 하기 좋다.
abstract class 를 사용하는 용도가 다른 클래스들이 특정 모양을 따르게 하기 위한 용도로 쓴다면 interface는 완벽한 대체제가 될 수 있다.

단점

private, public property 를 사용할 수 없다. (클래스쓰면 constructor 에서 보호여부를 정할수가 있다는 점과 차이가 있음)

여러 interface 를 상속하는 클래스

interface User {
  firstName: string;
  lastName: string;
  sayHi(name: string): string;
  fullName(): string;
}
interface Human {
  height: number;
}

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

const john = new Player("john", "brown");

인터페이스를 타입으로 사용하기 예시

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

// Bad: User 는 인터페이스로 정의된 녀석이라 클래스사용하듯이 하면 안된다.
function makeUser(user: User) : User{
  return new User {
    firstName: user.firstName,
    lastName: user.lastName,
    sayHi: (name: string) => "hi",
    fullName: ()=> user.firstName + user.lastName
  }
}

//Good : User 처럼 생긴 오브젝트를 넣고, 리턴해야 함
function makeUser(user: User) : User{
  return {
    firstName: user.firstName,
    lastName: user.lastName,
    sayHi: (name: string) => "hi",
    fullName: ()=> user.firstName + user.lastName
  }
}

makeUser({
  firstName: "john",
  lastName: "brown",
  sayHi : (name) => "hi~",
  fullName : () => "john brown"
})

type vs interface

typeinterface
타입정의모든 타입을 정의한다object 타입을 정의한다
타입상속type A = B &{...}interface A extends B{...}
클래스상속Xclass A implements B {}
용도타입정의,제한,콜시그니처객체 타입정의, 클래스 상속
특징누적선언 불가
추상클래스 대체가능
alias타입
객체지향적
누적선언 가능
추상클래스 대체가능

pylymorphism 예제

interface IStorage<T> {
  [key: string]: T;
}
class LocalStorage<T> {
  private storage: IStorage<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 stringStorage = new LocalStorage<string>();
stringStorage.get("aaa");
stringStorage.set("hello", "how are you");

const booleansStorage = new LocalStorage<boolean>();
booleansStorage.get("bbb");
booleansStorage.get("hello", true);

x

Enums

  • typescript 에서 type 은 반복을 줄이고 싶을 때 쓴다
type categories = "TO_DO" | "DOING" | "DONE" ;

export interface IToDo{
  text: string;
  id: number;
  category: categories;
}

export const categoryState = atom<categories>({
  key: "category",
  default: "TO_DO",
})
<select value={category} onInput={onInput}>
  <option value="TO_DO">To Do</option>
  <option value="DOING">Doing</option>
  <option value="DONE">Done</option>
</select>

Enum 사용

export enum Categories {
  "TO_DO",
  "DOING",
  "DONE",
}
export interface IToDo{
  text: string;
  id: number;
  category: Categories;
}
export const categoryState = atom<Categories>({
  key: "category",
  default: Categories.TO_DO,
})
<select value={category} onInput={onInput}>
  <option value={Categories.TO_DO}>To Do</option>
  <option value={Categories.DOING}>Doing</option>
  <option value={Categories.DONE}>Done</option>
</select>
//
  • type 대신 enum 을 쓰면 뭐가 좋은가?
    1. 훨씬 더 잘 보호받을 수 있다.
    2. enum 에 선언된 애들에는 각각 순서 (번호)가 할당되어 있다.
    3. enum 은 프로그래머를 도와주기 위해 숫자를 문자로 표현해주는 것이다.
    4. 이 예제에서 모두가 같은 enum 같은 값을 참조하기만 하면 이름은 원하는 대로 지어도 되기 때문에 사용할수 있는 방법이다.
export enum Categories{
  "TO_DO",  //0
  "DOING",  //1
  "DONE",   //2
}

category !== Categories.DOING  // category !== 2 랑 똑같다.
<button name = {Categories.TO_DO}>  // 문제발생 (name은 스트링이어야됨)
<button name = {Categories.TO_DO + "" }>  // 문제 해결. name 에 "0" 이 들어간다.
  • 실제로 값을 숫자가 아닌 값(예를들어 string)으로 줄 수도 있다.
export enum Categories{
  "TO_DO" = "TO_DO",  //TO_DO
  "DOING" = "DOING",  //DOING
  "DONE" = "DONE",    //DONE
}
<button name = {Categories.TO_DO}>  // 문제 없음.

0개의 댓글