TypeScript

Taehun Jeong·2023년 4월 18일
0
post-thumbnail

TypeScript

JavaScript which scales

TypeScript는 Microsoft에서 개발한 JavaScript의 오픈소스 슈퍼셋(SuperSet) 프로그래밍 언어이다. 슈퍼셋 언어는 특정한 언어의 기능을 포함하며, 더 나아가 확장된 기능을 제공할 수 있는 언어를 의미한다. 즉, JavaScript의 기능과 함께 추가 기능을 사용할 수 있다.

정적 타입 시스템

TypeScript는 기존 JavaScript에 type 시스템을 추가함으로써 코드 유지보수와 개선에 편의성을 추가하고자 개발되었다. JavaScript는 동적 타입 지정 언어이므로 런타임 중에 변수의 타입이 결정된다.

let x = 1;    // x 변수의 타입은 숫자(number)로 결정됨
x = "hello";  // x 변수의 타입은 문자열(string)로 변경됨

때문에 개발자는 타입에 대해 크게 관여하지 않는다. 이는 프로젝트 규모가 커질수록 가독성을 떨어뜨리고 확장을 어렵게하는 요인이 될 수 있다.

var name: string = "Danger"
console.log("Hello, " + name)

TypeScript는 정적 타입 시스템을 채용하여 코드의 독자들이 변수의 type과 예상되는 동작, 역할을 쉽게 파악할 수 있도록 한다. 또한, 런타임 단계에서 TypeError를 검출하는 것보다 개발 중 IDE를 통해 정적인 에러를 발견하기 수월해진다. 이를 통해 코드를 수정하거나 유지보수할 때 예상치 못한 문제를 방지하고, 코드 변경에 대한 예측이 더욱 용이해진다.


TypeScript의 구성

소스코드

TypeScript는 JavaScript의 문법을 그대로 활용할 수 있으면서 타입 어노테이션 (Type Annotations), 인터페이스 (Interface) 등 추가적인 기능을 위한 문법을 사용할 수 있다.

컴파일러

컴파일러는 TypeScript 소스코드를 JavaScript 코드로 변환하는 역할을 한다. tsc 커맨드를 통해 컴파일러를 실행할 수 있다. 컴파일러의 구성은 코드를 읽고 구문을 해석하는 파서(parser), 타입 정보에 모순이 없는지 검사하는 타입 검사기(type checker), 그리고 브라우저가 실행 가능한 자바스크립트 파일을 뱉어내는 에미터(emitter) 등이 있다.


Practice

  • 설치
npm install -g typescript
tsc --init
  • 실행
tsc practice.ts
node practice.js

TypeScript PlayGround


Types for TypeScript

JavaScript에서 주로 사용하는 number, string 등의 타입 외에도 TypeScript에서는 추가적인 타입들을 제공한다.

Tuple

tuple은 길이와 타입이 고정된 배열에 사용하는 타입이다.

let foo: [string, number] = ['hello', 123];

foostringnumber를 타입으로 갖는 길이 2의 tuple이 되는 것이다.

Enum

이름이 있는 상수 값의 집합을 표현하는 타입이다.

enum Foo {
  bar,
  baz,
  qux,
}

let myFoo: Foo = Foo.qux;

Enum 타입인 Foobar, baz, qux와 같은 상수 값을 가진 열거형 변수를 만들 수 있다.

Any

모든 데이터 타입의 상위 타입이며, 어떤 값도 할당 가능하다. 어떤 타입의 값이 사용될 아직 알 수 없을 때 타입 검사를 우회하는 용도로 사용된다. 자주 사용할 경우 정적 타입 시스템을 사용하는 의미가 없으므로 사용을 자제하는 것이 좋다.

let myValue: any = 'hello';
myValue = 123;
myValue = ['FOO', 'bar', 'Baz'];

Void

void 타입은 반환할 값이 없는 함수에 사용하는 타입이다.

function foo(name: string): void {
  console.log(`Hello, ${name}!`);
}

foo('Foo'); // "Hello, Foo!"

함수 foo는 값을 반환할 필요가 없으므로 void를 지정한다. 그러면 함수는 undefined를 반환하게 된다. 그러면 함수에 undefined를 사용할 수도 있다. 하지만 voidundefined는 의미에 차이가 있다. void는 함수가 반환하는 값이 없음을 나타내지만, undefined는 함수가 값을 갖지 않는 것을 반환한다는 것을 의미한다.

function foo(name: string): undefined {
  console.log(`Hello, ${name}!`);
}

위의 코드의 경우 에러가 발생한다. A function whose declared type is neither 'void' nor 'any' must return a value.라는 내용으로 voidany의 타입이 아닌 함수는 반드시 값을 반환해야 한다는 것이다.

function foo(name: string): undefined {
  console.log(`Hello, ${name}!`);
  return;
}

위와 같이 수정하면 에러가 발생하지 않지만, voidundefined를 혼용하면 코드의 일관성을 해치기 쉬우므로 각자의 사용 영역을 확실히 해야 한다.

Never

never 타입은 절대 발생하지 않는 값을 나타낸다.

function error(message: string): never {
  throw new Error(message);
}
let invalidValue: never = 1;  // Error!
// Type 'number' is not assignable to type 'never'.

error 함수는 throw를 통해 예외를 발생시키기만 하며, 어떠한 값을 반환하지 않는다. 변수 invalidValue에는 어떠한 값을 할당해도 에러가 발생한다.
never 타입은 일반적으로 예외를 던지거나 무한 루프를 실행하는 함수와 같이, 절대로 발생하지 않는 값의 타입을 나타내기 위해 사용한다.

이 외에도 TypeScript는 Union, Generic 등의 타입을 제공한다.


타입 어노테이션 (Type Annotation)

TypeScript는 변수나 함수의 타입을 표현하기 위해 Type Annotation을 사용한다.

const flag: boolean = true;
const str: string = "hello" 
const list: string[] = ['foo', 'bar'];
const unionString: string|number = 123;

Type Annotation을 사용하면 타입의 일치 여부를 런타임이 아닌 컴파일 타임에 검사할 수 있다.

function greeter(person: string) {
  return "Hello, " + person;
}
 
let user = [0, 1, 2];
 
document.body.textContent = greeter(user);  // Error!
// Argument of type 'number[]' is not assignable to parameter of type 'string'.

타입 추론

TypeScript에서는 명시적으로 타입을 지정하지 않아도 변수 또는 함수의 타입을 추론(inference)하여 타입 정보를 제공할 수 있다.

let x = 3;

위와 같은 코드에서 TypeScript는 let x: number로 초기값을 보고 x의 타입을 자동으로 number로 추론한다.

let bar = true;
// bar는 boolean이라는 사실을 알고 있다.
const result = bar + 1;
// Operator '+' cannot be applied to types 'boolean' and 'number'

불필요하게 Type Annotation을 하지 않아도 변수의 타입을 추론하여 에러를 확인할 수 있다. TypeScript는 아래와 같은 방법들로 타입을 추론한다.

Best Common Type

let x = [0, 1, null];

x의 타입을 추론하려면 각 배열 요소들의 타입들을 고려해야 한다. number 타입을 갖는 0, 1null 중 선택할 수 있다. 포함된 요소의 타입 중 최적의 공통 타입을 선택해야 하므로 공통적인 구조를 공유하되, 모든 타입을 포함하는 상위 타입이 아닐 수 있다.

let zoo = [new Rhino(), new Elephant(), new Snake()];

예시에서 zoo는 모든 요소들을 포함하는 Animal[] 타입이 되는 것이 가장 이상적이다. 하지만 배열에는 Animal 타입의 객체가 없기 때문에 TypeScript는 추론 결과로 Union 배열 타입 (Rhino | Elephant | Snake)[]을 적용한다. 한 타입이 다른 모든 후보의 상위 타입이 아닌 경우 원하는 타입을 지정하기 위해 명시적으로 제공해야 한다.

let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

Contextual Typing

TypeScript는 코드의 위치, 즉 문맥을 기준으로 타입을 추론하기도 한다.

window.onmousedown = function (mouseEvent) {
  console.log(mouseEvent.button); //<- OK
  console.log(mouseEvent.kangaroo); //<- Error!
};

TypeScript 타입 검사기는 Window.onmousedown에 할당된 함수의 타입을 추론하기 위해 오른쪽에 할당된 함수 표현식의 타입을 추론한다. TypeScript가 내부적으로 DOM API와 관련된 라이브러리(@types/dom)를 제공하기 때문에 파라미터 mouseEventMouseEvent와 관련되어 있다고 판단한다. MouseEventbutton을 프로퍼티로 갖기 때문에 정상적으로 수행하지만, kangaroo 프로퍼티는 존재하지 않으므로 에러가 발생한다.

window.onscroll = function (uiEvent) {
  console.log(uiEvent.button); //<- Error!
};

오른쪽의 함수는 window.onscroll에 할당되었기 때문에 함수의 인자 uiEvent는 UIEvent로 간주된다. UIEvent 객체에는 button 프로퍼티가 없으므로 오류가 발생한다.

const handler = function (uiEvent) {
  console.log(uiEvent.button); //<- OK
};

위와 같이 수정하면 handler는 앞의 예제와 같은 함수 표현식을 사용하지만, 함수가 할당되는 변수만으로는 타입을 추정하기 어렵기 암묵적으로 any 타입을 갖기 때문에 에러가 발생하지 않는다.


Union & Narrowing

TypeScript에서 union 타입은 둘 이상의 다른 타입 중 하나가 될 수 있는 타입을 의미한다.

let foo: number | string = "hello";
console.log(foo.slice(1));

foo = 100;
console.log(foo > 50);

위의 코드는 foonumberstring이 할당될 수 있음을 의미하며 string 타입을 갖는 값을 넣었으므로 slice 메서드를 실행해도 정상적으로 결과를 출력한다. 그 뒤에 number 100을 재할당해도 값의 비교를 문제 없이 수행한다.

let foo : string | number = Math.random() > 0.5 ? "hello" : 100;

foo.slice(3);
// Property 'slice' does not exist on type 'string | number'.
//   Property 'slice' does not exist on type 'number'.

위의 코드는 정상적으로 실행될 수도 있지만, IDE에서 에러를 출력한다. foo의 타입은 런타임 중에 결정되며 string이나 number 중 하나를 갖게 된다. foonumber일 경우에는 slice 메서드를 호출할 수 없으므로 TypeScript는 이러한 가능성을 파악하여 에러를 출력한다. 이를 해결하려면 slice() 메소드를 호출하기 전에 foo 변수의 타입을 검사하여 string인 경우에만 호출해야 한다. 이를 'narrowing'이라 한다. 런타임 중에 발생하는 값에 따라 변수의 타입이 결정되는 경우, 해당 변수의 타입을 명확히 하기 위해 변수의 타입을 좁혀나가는 것이다.

let foo : string | number = Math.random() > 0.5 ? "hello" : 100;

if(typeof foo === "string") foo.slice(3);  //typeof를 통한 narrowing
if(foo === "hello") foo.slice(3);      //조건검사를 통한 narrowing

타입 별칭 (Type Alias)

타입 별칭은 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미한다. 복잡한 타입을 간결하게 표현하거나, 중복을 줄이기 위해 사용할 수 있다.

type Person = boolean | number | string | null;
let timothy: Person;
type Point = {
  x: number;
  y: number;
};

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

타입 별칭은 타입 별칭과 결합하여 사용할 수도 있다.

type bdValue = boolean | number;
type myValue = bdValue | string | null;

let thomas: myValue;
thomas = 0;
console.log(thomas);  // 0
thomas = "hello";
console.log(thomas);  // "hello"

Intersection

Intersection은 타입을 조합하는 방법 중 하나로 두 개 이상의 타입을 &로 조합하여 새로운 타입을 만드는 것이다.

type Artwork = {
  genre : string;
  name: string
}

type Writing = {
  page: number;
  name: string
}

type WritterArt = Artwork & Writing;

let scripts: WritterArt = {
    genre: "Thriller",
    page: 80,
    name: "각본"
}

WritterArt 타입은 ArtworkWriting의 속성을 모두 갖는 타입이다. 이때 양 타입이 모두 갖는 속성인 name을 두 번 갖는 것이 아니라 중복되는 것은 제거되므로 하나만 갖는 것이다.


인터페이스 (Interface)

인터페이스는 객체의 속성들과 각 속성의 타입을 명시하여 객체의 형태를 정의하는 것이다.

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

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

인터페이스를 인자로 받아 사용할 때 항상 인터페이스의 속성 갯수와 인자로 받는 객체의 속성 갯수를 일치시키지 않아도 된다. 인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 갯수가 더 많아도 상관 없다. 또한, 인터페이스에 선언된 속성 순서를 지키지 않아도 된다.

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
let cubicPoint = { x: 100, y: 100, z: 100 };
printCoord(cubicPoint);
// "The coordinate's x value is 100" 
// "The coordinate's y value is 100" 

바로 위의 예제에서 cubicPoint 객체는 인터페이스 Point에 정의되지 않은 z라는 키를 갖고 있으나, TypeScript는 excess property check라는 방식으로 객체의 추가 속성을 체크하며 추가적인 속성이 있어도 해당 객체를 인터페이스로 타입을 지정하는 것이 가능하기 때문에 정상적으로 작동한다.

옵션 속성 (Optional Property)

인터페이스에서 필드의 값이 있을 수도 있고 없을 수도 있는 경우가 있다. 이러한 경우에 옵션 속성을 지정해줄 수 있다.

interface person {
  name: string;
  age: number;
  address?: string;
}

let jane = { name: "Jane", age: 25 };

function congratUser(personInfo: person) {
  console.log(`Hello, ${personInfo.name}!`);
  console.log(`It's your ${personInfo.age}-th birthday!`);
}

congratUser(jane);

jane 객체에는 address 필드가 없으나 정상적으로 값을 출력한다. 옵션 속성을 사용하면 객체의 일부 속성만을 필수 속성으로 지정할 수 있으면서 정의되지 않은 속성에 대해 인지할 수 있다는 점 등을 장점으로 갖는다.

읽기 전용 속성 (readonly)

인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성을 사용할 때에는 읽기 전용 속성을 사용한다. readonly를 앞에 붙여 사용할 수 있다.

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

const per:Person = {
    lastName:"yo"
}

per.lastName = "hey";  // Error!
// Cannot assign to 'lastName' because it is a read-only property.

Index Signature

Index Signature란 객체의 속성 이름과 속성 값의 타입을 정의하는 문법으로 객체의 속성들의 이름은 알 수 없으나 값의 형태를 알고 있을 때 쓸 수 있다.

interface StringArray {
  [index: number]: string;
}

const myArray: StringArray = ["hello", "world"];
// const secondItem: string

위의 예제에서 인터페이스 StringArray는 Index Signature를 갖는다. 그 Index Signature는 number 타입의 인덱스를 통해 string 타입의 값을 반환한다. Index Signature의 속성으로는 string, number, symbol, Template literal 타입, 또는 이들의 유니온 타입만 사용할 수 있다.

확장

인터페이스는 인터페이스 간의 확장이 가능하다. 확장은 extends 키워드를 사용한다.

interface Base {
    title: string;
}

interface developMembers extends Base {
    [s:string] : string;
}

const members: developMembers = {
    title: 'CodeSquad',
};
members.skill = "ts";

developMembers 인터페이스는 Base 인터페이스를 상속받아 title 속성과 string 타입의 인덱스로 string 타입의 값을 할당받는 Index Signature를 사용한다.


Type Alias VS Interface

interface Point {
    x: number;
    y: number;
}

type Point = {
    x: number;
    y: number;
};

타입 별칭과 인터페이스는 거의 동일한 역할을 수행한다. 둘 다 generic, class implement 를 사용 가능하다는 공통점도 갖는다. 차이점은 다음과 같다.

  • Interface는 동일한 이름으로 재선언 가능

인터페이스는 선언 병합(Declaration Merging)이 가능하다. 선언 병합이란 여러 선언들이 하나로 병합되는 것을 말한다.

interface Person {
  name: string;
}

interface Person {
  age: number;
}

let john: Person = { name: "John", age: 27 };

위의 경우 두 선언의 멤버를 같은 이름의 단일 인터페이스로 결합한다. 따라서 인터페이스 Personnameage 속성을 갖는다.

type Person = {  // Error!
  name: string;
}

type Person = {  // Error!
  age: number;
}

// Duplicate identifier 'Person'.

하지만 type의 경우, 동일한 이름의 타입을 선언하면 에러가 발생한다. 선언 이후에는 해당 타입의 내용을 변경할 수 없기 때문이다.

  • 인터페이스는 extends 사용하여 확장된 타입 선언 가능

앞서 언급한 것처럼 인터페이스는 extends를 사용하여 명시적으로 기존 타입의 확장이 가능하다. 타입 별칭도 extends 키워드를 사용하여 타입 확장이 가능하나 타입 별칭으로 선언된 타입에 extends 키워드로 새로운 type alias 타입을 선언하는 것은 불가능하다.

type TPoint = {
  x: number;
  y: number;
};

interface NewPoint extends TPoint {  // 가능
  z: number;
}

type NewPointT extends TPoint = {  // 불가능
  z: number;
};

type NewPointT = TPoint & {  // 가능
  z: number;
};
  • 타입 별칭은 intersection, union, tuple 사용 가능
type PartialPointX = { x: number };
type PartialPointY = { y: number };

// intersection
type IntersectionPoint = PartialPointX & PartialPointY;

// union
type UnionPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];
  • function 표현

    • Interface
    interface Fn {
      (name:string) : string
    }
    • Type Alias
    type Fn = (name:string) => string;

함수 타입을 정의할 때에도 표현에서 차이가 나타난다. 위와 같은 경우 타입 별칭이 조금 더 간결하게 나타내기 쉽다.

둘의 차이를 비교하면 인터페이스의 경우에는 extends를 활용한 상속 및 확장, 병합이 가능하며 타입 별칭은 Union, Intersection 등을 활용할 수 있다. Typescript 공식 문서에서는 타입 별칭 대신 인터페이스를 사용하여 타입을 선언할 것을 권장하고 있다.

// There are two main tools to declare the shape of an
// object: interfaces and type aliases.
//
// They are very similar, and for the most common cases
// act the same.

type BirdType = {
  wings: 2;
};

interface BirdInterface {
  wings: 2;
}

const bird1: BirdType = { wings: 2 };
const bird2: BirdInterface = { wings: 2 };

// Because TypeScript is a structural type system,
// it's possible to intermix their use too.

const bird3: BirdInterface = bird1;

// They both support extending other interfaces and types.
// Type aliases do this via intersection types, while
// interfaces have a keyword.

type Owl = { nocturnal: true } & BirdType;
type Robin = { nocturnal: false } & BirdInterface;

interface Peacock extends BirdType {
  colourful: true;
  flies: false;
}
interface Chicken extends BirdInterface {
  colourful: false;
  flies: false;
}

let owl: Owl = { wings: 2, nocturnal: true };
let chicken: Chicken = { wings: 2, colourful: false, flies: false };

// That said, we recommend you use interfaces over type
// aliases. Specifically, because you will get better error
// messages. If you hover over the following errors, you can
// see how TypeScript can provide terser and more focused
// messages when working with interfaces like Chicken.

owl = chicken;
chicken = owl;

// One major difference between type aliases vs interfaces
// are that interfaces are open and type aliases are closed.
// This means you can extend an interface by declaring it
// a second time.

interface Kitten {
  purrs: boolean;
}

interface Kitten {
  colour: string;
}

// In the other case a type cannot be changed outside of
// its declaration.

type Puppy = {
  color: string;
};

type Puppy = {
  toys: number;
};

// Depending on your goals, this difference could be a
// positive or a negative. However for publicly exposed
// types, it's a better call to make them an interface.

// One of the best resources for seeing all of the edge
// cases around types vs interfaces, this stack overflow
// thread is a good place to start:

// https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types/52682220#52682220

제네릭 (Generic)

제네릭이란 클래스 또는 함수에서 사용할 타입(Type)을, 그 클래스나 함수를 사용할 때 결정하는 프로그래밍 기법을 말한다. TypeScript에서의 제네릭은 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다.

function identity(arg: number): number {
  return arg;
}

위는 제네릭이 없을 때의 identity 함수이다. identity 함수는 인수로 무엇이 오던 그대로 반환하는 함수이다. 제네릭이 없기 때문에 특정한 타입을 지정해주어야 한다. 예시는 number 값에 대해서는 number만을 반환하므로 정확하지만 범용적이지 않다.

function identity(arg: any): any {
  return arg;
}

any를 지정해주어 어떤 타입이든 받을 수 있다는 점에서 제네릭하게 만들수도 있지만, 실제로 함수가 반환할 때 어떤 타입인지에 대한 정보는 잃게 된다. 만약 number 타입을 넘긴다고 해도 any 타입이 반환된다는 정보만 얻는 것이다.

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

위 예시에서 identity 함수는 제네릭 타입 매개변수 Type을 받는다. 이 함수는 arg 매개변수와 Type 타입을 반환한다. Type은 함수를 호출할 때 동적으로 결정므로 identity 함수를 호출할 때, 타입 매개변수로 어떤 타입을 전달하느냐에 따라 반환되는 값의 타입이 결정되는 것이다. 이 버전의 identity 함수는 타입을 불문하고 동작하므로 제네릭이라 할 수 있다.

함수에서 제네릭을 사용하려면 함수 이름 뒤에 <T>와 같은 형식으로 제네릭 타입 매개변수를 선언하여 쓸 수 있다. 제네릭 함수를 사용하면, 함수를 호출할 때 전달되는 인자의 타입을 검사할 수 있다.
아래는 제네릭을 사용하여 지정한 자료형에 대해 정렬을 수행하는 함수를 작성한 것이다.

function sort<T>(item: T[]): T[] {
    return item.sort();
}
 
const nums: number[] = [1, 2, 3, 4];
const chars: string[] = ["a", "b", "c", "d", "e", "f", "g",];
 
sort<number>(nums);
sort<string>(chars);

제네릭을 사용하면서 얻을 수 있는 이점으로는

  • 컴파일 타임에서의 타입 체크 : 컴파일러가 코드에서 사용하는 타입을 미리 확인할 수 있으므로, 컴파일 타임에서 타입 체크를 수행할 수 있다.
  • 타입 캐스팅 제거 : 동적 타입 변환 없이 타입을 일관되게 유지하며 코드를 더욱 간단하게 작성할 수 있다.
  • 일반화된 알고리즘 구현 : 다양한 데이터 타입에 대해 동작하는 일반화된 알고리즘을 구현할 수 있으며, 코드의 재사용성을 높일 수 있다.

이 있다.

클래스에서도 제네릭을 사용할 수 있다.

class GenericMath<T> {
  pi: T;
  sum: (x: T, y: T) => T;
}

제네릭 클래스를 선언할 때에는 클래스 이름 끝에 <T>를 붙여줌으로써 사용할 수 있다. 그리고 해당 클래스로 인스턴스를 생성할 때 타입에 어떤 값이 들어갈 지 지정하면 된다.

Practice : 제네릭으로 Stack 클래스 구현하기

class Stack<T> {
    private items: T[] = [];

    push(item: T) {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }
}

let stack = new Stack<number>();
stack.push(1);
stack.push(2);
console.log(stack.pop());  // 2
console.log(stack.pop());  // 1

stack.push("foo");  // Error!
// Argument of type 'string' is not assignable to parameter of type 'number'.

References

TypeScript
CodeSquad) 타입스크립트
ts-for-jsdev) 자바스크립트 개발자를 위한 타입스크립트
타입스크립트 핸드북
SAMSUNG SDS) 활용도가 높아지는 웹 프론트엔드 언어, 타입스크립트(TypeScript)
Poiemaweb) 정적 타이핑
Ujeon) [TypeScript] 타입스크립트 기초
DevStory) [TypeScript]인덱스 시그니처(Index Signature) 사용 방법
TypeScript Deep Dive
이 기술 블로그를 만들기 위해 5년을 미뤘다.) Typescript - type alias vs interface 방식 비교
yceffort) 타입스크립트 type과 interface의 공통점과 차이점
요행을 바라지 않는 개발자) TypeScript | Generic 제네릭 (feat. TypeScript 두 달차 후기)
TypeScript TUTORIAL) TypeScript Generics
TypeScript Guidebook) 제네릭
기록의 힘) [TypeScript] 타입스크립트 제네릭(Generic), Factory Pattern with Generics

profile
안녕하세요

0개의 댓글