# Typescript 시작
Egghead (opens new window)의 Use Types Effectively in TypeScript (opens new window)를 공부하며 정리한 내용입니다.
# Typescript
- TypeScript is JavaScript with static typing.
# 환경 세팅
$ npm install -g typescript // npm으로 설치
$ tsc -v // 버전
$ tsc main.ts // main.ts >> main.js 변환
# playground
https://www.typescriptlang.org/play/ (opens new window)
# Union Types
- 다양한 타입을 받을 수 있도록 해줄 수 있다.
let thing : string | number | string[] | boolean;
let returnSomthing = (someThing : string | number | string[] | boolean ) => {
return someThing;
};
- Union type을 변수에 선언해 줄 수도 있다.
type thing = string | number | string[] | boolean;
let returnSomthing = (someThing : thing ) => someThing;
- 내부에서 타입 검사를 해볼 수도 있다.
type thing = string | number | string[] | boolean;
let returnSomthing = (someThing : thing ) => {
if (typeof someThing === "string") ||
typeof someThing === "number") ||
typeof someThing === "boolean")) {
console.log("someThing = ", someThing )
}
if (someThing instanceof Array) {
let joinedThings = "";
someThing.forEach((thing) => {
joinedThings += `${thing}`;
});
console.log("joinedThings = ", joinedThings);
}
};
returnSomthing(123); // someThing = 123
returnSomthing(["a","b","c"]); // joinedThings = abc
- type 에는 object가 아닌 것도 들어갈 수 있다.
type stuff = string | {name:string};
let gimmeStuff = (stuff: stuff) => {
typeof stuff === "string";
typeof stuff === "string";
};
// error TS2339: Property 'name' does not exist on type 'string | { name: string; }'
// object와 not object가 함께 있으면 에러가 남.
type coolThings = {name: string;} | {id: number;};
let gimmeCoolthings = (thing: coolThings) => {
if (typeof thing.name === "string") { return thing.name; }
if (typeof thing.id === "number") { return thing.id; }
};
// error TS2339: Property 'name' does not exist on type '{ name: string; } | { id: number }'
// error TS2339: Property 'name' does not exist on type '{ name: string; } | { id: number }'
// error TS2339: Property 'id' does not exist on type '{ name: string; } | { id: number }'
// error TS2339: Property 'id' does not exist on type '{ name: string; } | { id: number }'
// not object끼리 있더라도 같은 parameter가 없으면 에러가 남
type stuffAndThings = {cool: string; meh: string;} | {cool: string; lame: string; }
let gimmeStuffAndThings = (sat: stuffandThings) => {
return sat.cool;
};
// It works! 같은 parameter가 있으면 된다.
# string literal type
let unit: string = "아무거나 올 수 있다.";
let miles: "MILES" = "MILES"; // literal type. type과 같은 값, null, undefined 만 가능.
- 이걸 이용해서 parameter에 들어올 값들을 제한 할 수 있다.
type distanceMetric = "MILES" | "KILOMETERS" | "METERS" | "YARDS" | "FEET" | "INCHES";
function moveCharacter(distance: number, value: distanceMetric) {
console.log(`You moved ${distance} ${value}`);
// error TS2345: Argument of type '"dragon"' is not
// assignable to parameter of type '"MILES" | "KILOMETERS" | ... '
};
# interface
let superHero: { secretIdentity: string; superHeroName: string; health: number };
let superVillain: { secretIdentity: string; superHeroName: string; health: number };
- 같은 모양의 변수를 많이 만들어야 할 때 interface를 이용할 수 있다.
interface ComicBookCharacter {
secretIdentity?: string; // ? : optional
alias: string;
health: number;
}
let superHero: ComicBookCharacter = {
alias: true, // Types of property 'alias' are incompatable. Type 'boolean' is not assignable to type 'string'.
health: 5000
}
let superVillain: ComicBookCharacter = {
secretIdentity: "Jack Napier", // Object literal may only specify known
alias: "Joker", // properties and 'secretIdentity' does not
health: 75 // exist in type 'ComicBookCharacter'
}
- 함수에서 parameter에 interface로 받을 수도 있다.
function getSecretIdentity(character: ComicBookCharacter) {
if (character.secretIdentity) {
console.log(`${character.alias} is ${character.secretIdentity}`);
} else {
console.log(`${character.alias} has no secret identity`);
}
}
getSecretIdentity(superHero);
- interface 내에서 다른 interface를 사용도 가능하다.
interface AttackFunction {
(opponent: { alias: string; health: number; }, attackWith: number): number; // opponent와 attackWith를 parameter로 받는 function
}
interface KrustyTheClown {
alias: string;
health: number;
inebriationLevel: number;
attack: AttackFunction;
}
function attackFunc(opponent, attackWith) {
opponent.health -= attackWith;
console.log(`${this.alias} attacked ${opponent.alias}, who's health = ${opponent.health}`);
return opponent.health;
}
let superVillain: ComicBookCharacter = {
scretIdentity: "Jack Napier",
alias: "Joker",
health: 75,
insanity: 175,
attack: attackFunc
}
- interface를 extends 할 수도 있다.
interface OptionalAttributes {
strength?: number;
insanity?: number;
dexterity?: number;
healingFactor?: number;
}
interface ComicBookCharacter extends OptionalAttributes { ... }
# class
- 타입스크립트에서 class는 function 이다. function 은 object 이고 즉, property를 가질 수 있다.
- class는 method도 가질 수 있다.
- method에서는 function이라고 표현하지 않아도 된다.
- method명(parameter){ }
class ComicBookCharacter {
alias: string;
health: number;
strength: number;
secretIdentity: string;
attackFunc(opponent, attackWith: number) {
opponent.health -= attackWith;
console.log(`${this.alias} attacked ${opponent.alias} who's health = ${opponent.health}`);
}
}
- 클래스 내 property들은 기본 public. private으로 하면 class 밖에서는 .으로 접근할 수 없다.
class ComicBookCharacter {
alias: string;
health: number;
strength: number;
private secretIdentity: string;
attackFunc(opponent: Opponent, attackWith: number) { ... }
getSecretIdentity() {
console.log(`${this,alias}'s secret identity is $(this.secretIdentity)`);
}
}
- constructor arguments를 이용하면 property를 따로 정의할 필요가 없다.
- 이 경우 public, private 와 같은 access modifiers를 반드시 함께 써주어야 한다.
- 없으면 그저 constructor의 argument로만 간주된다.
class ComicBookCharacter {
attackFunc(opponent: Opponent, attackWith: number) { ... }
getSecretIdentity() { console.log(`${this,alias}'s secret identity is $(this.secretIdentity)`);}
constructor(public alias: string, public health: number, public strength: number, private secretIdentity: string) {}
}
- class는 static property 역시 가지고 있다.
- instance에서는 사용할 수 없다.
- 오직 class를 통해서만 사용할 수 있다.
class ComicBookCharacter {
static createTeam(teamName: string, members: ComicBookCharacter[]) {
name: teamName,
members: members
}
}
let instanceTeam = new ComicBookCharacter();
instanceTeam.createTeam("oddCouple, [storm, theBlob]); // error!!!!
let team = ComicBookCharacter.createTeam("oddCouple, [storm, theBlob]);
- class역시 extends할 수 있다.
class ComicBookCharacter (
constructor{
public alias: string, public health: number , public strength: number,
private secretIdentity: string
) {}
}
class SuperHero extends ComicBookCharacter {
traits = ["empathy", "strong moral code"];
getSecretId() { console.log(this.secretIdentity); // not working!!!
// extends 된 class 에서는 부모 class의 private property에 접근 할 수없다.
// private은 자신이 속해있는 container에서만 접근 가능하다.
// 이 경우, secretIdentity 를 protected로 변경해주면 된다.
}
class SuperVillain extends ComicBookCharacter {
flaws = ["hubris", "always explains evil plan"];
}
let jubilee = new SuperHero("Jubilee", 23, 233, "Jubilation Lee");
let scarletWitch = new SuperVillain("Scarlet Witch", 233, 4444, "Wanda Maximoff");
- 만약 자식 class에서 constructor를 만드려면 반드시 super(); 를 호출해 주어야 한다.
- 부모 class의 argument를 모두 받아야한다.
- constructor 내부에서 꼭! 첫번째로 불려야한다.
class ComicBookCharacter (
constructor{
public alias: string, public health: number , public strength: number,
protected secretIdentity: string
) {}
}
class SuperVillain extends ComicBookCharacter {
flaws = ["hubris", "always explains evil plan"];
constructor(a, b, c, d) {
super(a, b, c, d);
console.log('${this.alias} eats kittens!!!');
}
}
# Type Converting
- (something as Type)
<Type>
something
interface SuperHero {
powers: string[];
savesTheDay: () => void;
}
interface BadGuy {
badDeeds: string[];
getRandomBadDeed: () => string;
commitBadDeed: () => void;
}
function saveDayOrBadDeed(something: SuperHero | BadGuy) {
// if (<SuperHero>something.powers) {} // angle bracket syntax
if ((something as SuperHero).powers) {
(something as SuperHero).savesTheDay();
} else {
(something as BadGuy).commitBadDeed();
}
}
saveDayOrBadDeed(dazzler); // Dazzler transduces sonic vibrations into light to save the day!!!
saveDayOrBadDeed(badGuy); // BadGuy farts on old folks
- js로 바뀐걸 확인해보면 convert가 모두 사라진 것을 확인할 수 있다.
# Generics
function pushSomethingIntoCollection(something, collection) {
collection.push(something);
console.log(collection);
}
let jeanGrey = { name: "Jean Grey" };
let wolverine = { name: "Wolverine" };
let superHeroes = [jeanGrey];
let powers = ["telekinesis", "esp"];
pushSomethingIntoCollection("cool", superHeroes);
pushSomethingIntoCollection("adamantium claws", []);
// [ { name: 'Jean Grey' }, 'cool' ]
// [ 'adamantium claws' ]
위와같이 할 경우, array 내에 각각 다른 type들이 들어가게 됨.
Generics : function 뒤에
<T>
를 써주고 parameter type에 (param : T) 와 같이 쓴다.꼭 T 일 필요는 없고, cool, test 와 같이 다른 문자열을 쓸 수 있다.
function pushSomethingIntoCollection<T>(something: T, collection: T[]) {
collection.push(something);
console.log(collection);
}
let jeanGrey = { name: "Jean Grey" };
let wolverine = { name: "Wolverine" };
let superHeroes = [jeanGrey];
let powers = ["telekinesis", "esp"];
pushSomethingIntoCollection("meh", superHeroes); // error!!!!!!
pushSomethingIntoCollection(jeanGrey, superHeroes); // it works!!
pushSomethingIntoCollection("adamantium claws", []);
- function 뒤에
<T>
를 위와 같이 생략해도 되지만 써주는게 IDE에서 작업할 때 더 좋다.
interface SuperHero {name: string;}
pushSomethingIntoCollection<SuperHero>("meh", superHeroes); // error!!!
pushSomethingIntoCollection<SuperHero>(jeanGrey, superHeroes); // it works!!
pushSomethingIntoCollection<string>("adamantium claws", []);
# Interface generic constraints
interface Crocodile { personality: string; }
interface Taxes { year: number; }
interface Container<T> { unit: T; }
let crocContainer: Container<Crocodile> = {unit: { personality: "mean"}};
let taxContainer: Container<Taxes> = {unit: {year: 2011}};
interface RedCroc extends Crocodile { color: "red"; }
interface BlueCroc extends Crocodile { color: "blue"; }
interface CrocContainer<T extends Crocodile> { crocUnit: T; }
let blueCrocContainer: CrocContainer<BlueCroc> = {crocUnit: {personality: "cool", color: "blue"}};
# Class generic constraints
class ClassyContainer<T extends Crocodile> {
classyCrocUnit: T;
}
let classyCrocContainer = new ClassyContainer(); // type argument 를 안알려줘도 됨.
classyCrocContainer.classyCrocUnit = {personality: "classy"}; // 하지만 Crocodile을 extends 한 interface들의 property를 사용할 수 없다.
let classyCrocContainer = new ClassyContainer<RedCroc>(); // T을 알려주면
classyCrocContainer.classyCrocUnit = {personality: "classy", color: "red"}; // IDE에서자동완성도 됨.
- constructor를 사용한 경우
- type argument를 생략하더라도 property에 값을 셋 할수 있다.
class CCC<T extends Crocodile> {]
constructor(public cccUnit: T) {}
}
let ccc = new CCC<BlueCroc>({personality: "ultra classy", color: "blue"});
let ccc2 = new CCC({personality: "ultra classy", color: "blue"}); // it works!!