typescript

typescript

Typescript

What is TypeScript

  • 자바스크립트를 확장한 언어
  • ES6가 표준(최신 ECMA 표준을 지원)

TypeScript Features

  • 숫자 분리 기호, 초기화 및 클래스, 알 수 없는 타입 등과 같이 안전성과 가독성을 향상시키는 데 사용하지 쉽도록 최근 릴리즈됨

Improve Readablility with TypeScript Numeric Separators when working with Large Numbers

  • 큰 숫자를 볼 때, 얼마나 큰 숫자인지 알기 힘들다. TypeScript를 사용하면 JavaScript를 변경하지 않고 숫자 분리 기호를 사용하여 사람이 읽을 수 있는 형식인 1_800_000같은 형식으로 숫자를 사용할 수 있다.(>=TypeScript 2.7)
class  AmountInput {
 private  static  MAX_ALLOWED = 99_999_999;
 amount: number = 0;

 showTooltip() {
  //show tooltip
  setTimeout(() => {
   //hide tooltip
  }, 2500);
 }

 formatMillion() {
  return  this.amount / 1_000_000 + "M";
 }
}

Make TypeScript Class Usage Safer with Strict Property Initialization

  • tsconfig.json 파일의 플래그
  • strictPropertyInitialization: 속성을 초기화하지 않으면 TypeScript에서 에러를 throw함(오류를 throw 시킬 것인가에 대한 속성)

class  Library {
 titles: string[];
 // titles: string[] | undefined;

 constructor(){}
}

const  library = new  Library();
const  shortTitles = library.titles.filter( // error("Cannot read property filter of undefined.") -> if(library.titles) {...}로 묶어주면 된다.
 title  =>  title.length < 5;
// 실행: `node index.js`
  • strictNullCheck: 컴파일시 type 문제에 대하여 경고
{
 "compilerOptions": {
  "strictPropertyInitialization": true,
  "strictNullChecks": true
 }
}
위의 방법은 Class property에서 초기화하는 방법이고, 아래의 내용은 생성자(constructor)에서 초기화하는 방법이다.
class  Library {
 titles: string[]

 constructor() { // 초기화하는 과정에서 Class property가 할당되기 때문에 오류가 없다.
  this.titles = ["What if?", "Flow"];// this
 }
}
  • exclamation mark(!)
class  Library {
 titles!: string[] // 속성이 초기화될 것이라고 알려줌(런타임시)

 constructor() {}
}

Use the JavaScript “in” operator for automatic type interface in TypeScript

in 연산자를 사용하여 객체에 특정 속성이 있는지 확인
interface  Admin {
 id: string;
 role: string:
}

interface  User {
 email: string;
}

function  redirect(usr: Admin | User) {
 if((<Admin>usr).role !== undefined) { // error(parameter가 Admin임을 확신할 수 없기 때문에 오류 발생)

  routeToAdminPage(usr.role);
 } else {
  routeToHomePage(usr.email);
 }
}
따라서 in 연산자를 사용하면 아래와 같다.
...
function  redirect(usr: Admin | User) {
 if("role"  in  usr) {
  routeToAdminPage(usr.role);
 } else {
  routeToHomePage(usr.email);
 }
}

Redux and readonly

  1. Redux: 액션과 상태를 기억(모든 액션이 하나의 type을 가져야 함)
  2. readonly: 읽기만 가능한 property 제한자. property를 사용할 수 있는 const. 값 변경 불가

Create Explicit and Readable Type Declarations with TypeScript mapped Type Modifiers

readonly 속성

const  pet:IPet = {name:  'Buttons', age:  5};
const  readonlyPet: ReadonlyPet = {
 name:  'Buttons',
 age:  5
}

pet.age = 6;
readonlyPet.age = 6; // error(readonly 속성은 값을 변경할 수 없음)

? 속성

type  ReadonlyPet = {
 readonly [K  in  key  of  IPet]?: IPet[K]; // 모든 속성을 선택 사항으로 표시하는 경우
}
type  ReadonlyPet = {
 readonly [K  in  key  of  IPet]: IPet[K]; // 기존 유형에 새로운 항목만 추가헐 수 있음
}
  • readonly 속성 추가
interface  IPet {
 name: string;
 age: number;
 favoritePark?: string; //this
}

  • 모든 속성 제거
type  ReadonlyPet = {
 readonly [K  in  key  of  IPet] -?: IPet[K]; // ?앞에 - 기호 추가(TypeScript >=2.8)
}
type  ReadonlyPet = {
 readonly [K  in  keyof  IPet]-?: IPet[K];
}
  
const  pet:IPet = {name:  'Buttons', age:  5};
const  readonlyPet: ReadonlyPet = { // error(새로운 객체에 대한 속성을 모르기 때문)
 name:  'Buttons',
 age:  5,
 favoritePark:  'Central'
}
// 따라서  아래와  같이  수정한다.

type  ReadonlyPet = {
 +readonly [K  in  keyof  IPet]-?: IPet[K]; // - 기호에 대한 유연성을 갖기 위해 + 기호도 추가됨
}

Use Types vs. Interface

type과 interface는 TypeScript에서 바꾸어 사용이 가능하다.
  • type aliases
  • interface와 마찬가지로 확장 가능(클래스에 의해 구현되거나 확장될 수 있음)
  • 동일한 속성과 메서드를 가지고 있는 interface와 type이라면 변수 대입이 가능하다.
interface  IAnimal {
 age: number;
 eat(): void;
 speak(): string;
}

type  AnimalTypeAlias = {
 age: number;
 eat(): void;
 speak(): string;
};

let  animalInterface: IAnimal;
let  animalTypeAlias: AnimalTypeAlias;

animalInterface = animalTypeAlias; // this

export {};
그러나, 속성 또는 메서드의 이름이 변경되면 에러가 난다.
type  Cat = IPet & IFeline;

interface  IPet {
 pose(): void;
}

interface  IFeline {
 nightvision: boolean;
}

let  cat: Cat; // cat.pose()와 cat.nightvision 둘 다 접근 가능
인터페이스를 사용하여 구현하려면 완전히 새로운 인터페이스를 구현해야 한다.
type  Cat = IPet & IFeline;

interface  ICat  extends  IPet, IFeline {...}

let  cat: ICat; // cat.pose()와 cat.nightvision 둘 다 접근 가능
인터페이스와 클래스 모두 인터페이스와 type을 상속받을 수 있다.(extend와 implements의 차이)
type  Pet = {
pose(): void;
};

interface  IFeline {
 nightvision: boolean;
}

interface  ICat  extends  IFeline, Pet {}

type  Cat = IFeline & Pet;

class  HouseCat  implements  IFeline, Pet {}

export {};
interface와 type의 차이점
  • 두 개의 interface 또는 두 개의 type: 두 개의 interface를 사용하면 속성들이 병합
  • 서로 다른 interface와 type: interface와 type을 함께 사용하면 속성들이 중복되더라도 모두 사용 가능
interface  Foo {
 a: string;
}

interface  Foo {
 b: string;
}

let  foo: Foo; // foo.a와 foo.b 모두 접근 가능
type  Foo = {
 a: string;
}

type  Foo = { // error(message: Duplicate identifier 'Foo')
b: string;
}

let  foo: Foo;
  • jQuery를 가져오는 예제
import  *  as  $  from  "jquery";

$.fn.extend({
 hideChildren:  function() {
 // ...
 }
});

$("test").hideChildren(); // 함수를 사용하려고 하면 error(jQuery does not exist...)

// TypeScript가 인터페이스 선언을 같은 이름으로 합치려는 동작 때문에
위에서는 jQuery 인터페이스는 적당하지 않기 때문에 typings.d.ts에 추가한다.
interface  JQuery {
 hideChildren(): JQuery;
}

Build self-referencing type aliases in TypeScript

TypeScript에서는 자체를 참조하는 형식을 선언할 수 있다.
interface  TreeNode<T> {
 value: T;
 left: TreeNode<T>;
 right: TreeNode<T>;
}

Simplify iteration of custom data structures in TypeScript with iterators

// iterator.ts
interface  Action {
 type: string;
}

interface  ListNode<T> {
 value: T;
 next: ListNode<T>;
 prev: ListNode<T>;
}

class  BackwardsActionIterator  implements  IterableIterator<Action> {

constructor(private  _currentActionNode: ListNode<Action>) {}

[Symbol.iterator](): IterableIterator<Action> {
 return  this;
}

next(): IteratorResult<Action> {
 const  curr = this._currentActionNode;
 if(!curr || !curr.value) {
  return {value:  null, done:  true};
 }
 //1. move through each item in the list
 this._currentActionNode = curr.prev;
 
 //2. return each item
 return {value:  curr.value, done:  false};
 }
}

let  action1 = { type:  "LOGIN" };
let  action2 = { type:  "LOAD_POSTS" };
let  action3 = { type:  "DISPLAY_POSTS" };
let  action4 = { type:  "LOGOUT" };
  
let  actionNode1: ListNode<Action> = {
 prev:  null,
 next:  null,
 value:  action1
};

let  actionNode2: ListNode<Action> = {
 prev:  actionNode1,
 next:  null,
 value:  action2
};

actionNode1.next = actionNode2;

let  actionNode3: ListNode<Action> = {
 prev:  actionNode2,
 next:  null,
 value:  action3
};

actionNode2.next = actionNode3;

let  actionNode4: ListNode<Action> = {
 prev:  actionNode3,
 next:  null,
 value:  action4
};

actionNode3.next = actionNode4;
 
const  backwardsActionsList = new  BackwardsActionIterator(actionNode4);

for(let  action  of  backwardsActionsList) {
 console.log(action.type);
}
tsc iterator.ts --target es6 command line에서 실행시키면 action4부터 차례대로 출력되는 것을 확인할 수 있다.

Use the TypeScript “unknown” type to avoid runtime errors

any type은 런타임 시 오류를 유발할 수 있다.
unknown type은 런타임 시 오류를 방지할 수 있다.

Infer the Return Type of a Generic Function Type Parameter

infer keyword를 통해서 배열 요소의 type이나 함수의 return type을 얻을 수 있다. 이를 사용하여 FnReturnType type을 만들면 일반 매개 변수로 전달된 함수의 return type을 얻을 수 있다.
function  generateId(seed: number) {
 return  seed + 5;
}

lookupEntity(generateId(10));

function  lookupEntity(id: string) {...}
// failed compile...
function  generateId(seed: number) {
 return  seed + 5;
}

type  ReturnType<T> = T  extends (...args: any[]) =>  R ? R : any;
// T가 변수 수를 가진 함수를 확장한다면, ReturnType을 추론하여 R에 저장하고, R을 반환하거나, T가 함수를 확장하지 않으면 그냥 임의의 값을 자리 표시 자로 반환한다.

// type ReturnType<T> = T extends (...args: infer K) => R ? R : any;

type  Id = ReturnType<typeof  generateId>;

lookupEntity(generateId(10));

function  lookupEntity(id: string) {...}

Deeply mark all the properties of a type as read-only in TypeScript(#14)

아래의 코드는 ITodo 인터페이스의 속성이 IEmail 속성들을 가지고 있다.
interface  IEmail {
 from: string;
 to: string[];
 body: string;
}

interface  ITodo {
 isCompleted: boolean;
 text: string;
 linkedEmail: IEmail;
}

interface  IRootState {
 userId: string;
 showCompletedOnly: boolean;
 todoTypes: string[];
 todos: ITodo[];
 iconGrid: string[][];
}

function  rootReducer(action: any, state: IRootState): IRootState {
 // case action 1...
 // case action 2...
 return  state;
}
Redux에서는 state 변경이 불가능해야 한다.

Dynamically initialize class properties using TypeScript decorators

데코레이터를 사용하여 클래스의 속성을 초기화하는 방법과 특정 URL에 GET 요청
다양한 추상화를 만들기 위해 여러 데코레이터를 연결하는 방법을 살펴본다.
  • @decorrator
  • TypeScript에 있는 함수(생성자 함수!!)
  • 전체 파일이 로드될 때 호출
  • 실제 인스턴스는 나중에 생성될 수 있음
  • Object.defineProperty 구문을 이용하여 클래스 속성 이름에 대한 getter 정의
// like this...
function  GetTodos(target: any, name: string) {
 const  init = () => {
  return  fetch('https://jsonplaceholder.typicode.com/todos')
   .then(response  =>  response.json());
  };

  Object.defineProperty(target, name, {
  get:  function() {
   return  init();
  }
 });
}
decorator는 실제로 여러 개를 만들어 서로 겹쳐 놓을 수 있다.

댓글

가장 많이 본 글