let: 재할당이 가능한 블록 레벨 변수.
const: 재할당이 불가능한 블록 레벨 변수.
Best Practice
1. const를 기본적으로 사용.
2. 재할당이 반드시 필요할 경우에만 let으로 선언.
for-in은 객체의 property를 순회함.
for-of는 이터러블(프로토콜을 구현한) 객체를 순회함.
타 언어에서의 람다 함수.
function으로 선언되는 일반적인 함수와의 차이: this, super, arguments, prototype을 가지지 않음.
const obj = {
a: 1,
normalFunc: function() { console.log(this); },
arrowFunc: () => { console.log(this); },
};
const { normalFunc, arrowFunc } = obj;
obj.normalFunc(); // {
// a: 1,
// normalFunc: [Function: normalFunc],
// arrowFunc: [Function: arrowFunc]
// }
normalFunc(); // undefined
obj.arrowFunc(); // (global object)
arrowFunc(); // (global object)
기존 비동기 처리 방식: 비동기 서브루틴에 콜백 함수를 넘김.
프로미스: 생성자의 인자로 resolve, reject 두 핸들러를 인자로 받는 함수를 받음. 함수 본문에서 특정 로직 실행 후, resolve, reject 수행하게 하면 됨.
then과 catch는 프로미스를 반환하므로 프로미스 체인을 작성할 수 있음. 프로미스 체인의 다음 프로미스는 이전 프로미스가 반환한 값으로 resolve를 호출.
function errorHandler(err) {
if (err) {
console.log(err);
}
}
fetchDocument(url)
.then(document => fetchAuthor(document), errorHandler)
.then(author => fetchPostsFromAuthor(author), errorHandler)
.then(posts => /* do something with posts */, errorHandler);
제네레이터: 출구가 여럿인 함수. 함수 내부에서 yield를 마주할 때마다 해당 값을 밖으로 반환하고 수행을 멈춤. 제네레이터 객체의 next를 호출하면 다음 코드 수행.
function* idMaker() {
let index = 0
while (index < 3) yield index++
}
let gen = idMaker()
console.log(gen.next()) // { value: 0, done: false }
console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: 2, done: false }
console.log(gen.next()) // { done: true }
async, await: 프로미스, 제네레이터를 활용하여 비동기 프로그래밍을 마치 동기 프로그래밍처럼 수행하게 해줌.
async function foo() {
try {
var val = await getMeAPromise()
console.log(val)
} catch (err) {
console.log('Error: ', err.message)
}
}
var __extends =
this.__extends ||
function(d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p] // [1]
function __() {
this.constructor = d
}
__.prototype = b.prototype
d.prototype = new __() // [2]
}
super 키워드는 __proto__를 뒤지는 것이며, 정적 멤버는 클래스에, 상수 멤버는 각 객체에 할당되므로 __proto__에는 메서드, accessor 등만 포함됨.
공집합 - never 타입: 아무런 값도 할당할 수 없음.
단일 값만 포함하는 집합- 리터럴 타입: 입력된 그대로의 값.(=유닛 타입)
합집합 - 유니온 타입.
교집합 - 인터섹션 타입.
A는 B에 할당 가능하다의 의미: A는 B의 부분집합이다.
subtyping: 상위 타입의 객체를 하위 타입의 객체로 치환할 수 있음.(*리스코프 치환 원칙)
Variance: 더 복잡한 타입들끼리의 subtyping이 등장했을 때 어떻게 동작해야 하는가?
기본적으로 Covariant.
let array1: Array<string | number> = [];
let array2: Array<string> = [];
array1 = array2; // Ok
array2 = array1; // Error
let func1: (x: string) => number | string = (x) => 0
let func2: (x: string) => number = (x) => 0
func1 = func2 // Ok
func2 = func1 // Error
함수의 인수는 Contraviariant.
let func1: (x: string | number) => number = (x) => 0
let func2: (x: string) => number = (x) => 0
func1 = func2 // Error
func2 = func1 // Ok
메서드 Shorthand Definition으로 정의하면 메서드 인자에 대해 Bivariant.
let o = {
func1: function(item: string | number): void {}, // normal
func2: (item: string | number) => void {}, // arrow
func3(item: string | number): void {}, // method
}
const func = (item: string): void => {}
o = {
func1: func, // Error
func2: func, // Error
func3: func // Ok
}
타입 공간: 타입 주석으로 사용할 수 있는 것들.
값 공간: 변수로 사용할 수 있는 항목들.
interface Person {
first: string;
last: string;
}
const p: Person = {first: 'Jane', last: 'Jacos' };
interface Person: 타입으로 사용됨.
const p: 값으로 사용됨.
타입 공간에 있는 것들은 타입 체커에서만 사용되며, 런타임에는 아무 영향도 끼치지 않음(컴파일 시 제거됨).
interface Cylinder {
radius: number;
height: number;
}
const Cylinder = (radius: number, height: number) => ({radius, height});
typeof 연산자와 같이 타입에서 쓰일 때와 값으로 쓰일 때 다른 역할을 하는 연산자가 있음.
const v = typeof Cylinder; // 값이 "function"
type T = typeof Cylinder; // 타입이 "typeof Cylinder"
각각 null과 undefined라는 하나의 값만을 갖는 타입. null와 undefined는 기본적으로 모든 타입의 서브타입이지만, --strictNullChecks 플래그 활성화 시 다른 타입에 null / undefined를 할당할 수 없음.
null / undefined를 비교할 때 == null과 같이 동등연산자를 사용하여 비교하는 것을 추천.(undefined도 함께 걸러짐)
단, 최상단에서 undefined를 확인할 시 해당 값이 정의되지 않았다면 직접 참조 시 Reference Error 발생
declare const someglobal: number
if (typeof someglobal !== 'undefined') {
// 전역에서 사용해도 안전
console.log(someglobal)
}
function f1() {
const x: any = expressionReturningFoo();
processBar(x);
}
function f2() {
const x = expressionReturningFoo();
processBar(x as any);
}
function cacheLast<T extends Function>(fn: T): T {
let lastArgs: any[] | null = null;
let lastResult: any;
return function(...args: any[]) {
if (!lastArgs || !shallowEqual(lastArgs, args)) {
lastResult = fn(...args);
lastArgs = args;
}
return lastResult;
} as unknown as T;
}
any를 사용할 경우 타입 체커를 이용하기 어려움. 때문에 unknown을 사용.
interface Feature {
id?: string | number;
geometry: Geometry;
properties: unknown;
}
declare const foo: Foo;
let barAny = foo as any as Bar;
let barUnk = foo as unknown as Bar;
// union type
type DeviceType = 'phone' | 'desktop' | 'pad' | 'watch'
// enum
enum DeviceType {
phone = 'phone',
desktop = 'desktop',
tablet = 'tablet',
watch = 'watch'
}
// const object
const DeviceTypeObject = {
phone: 'phone',
desktop: 'desktop',
tablet: 'tablet',
watch: 'watch'
} as const
type DeviceType = typeof DeviceTypeObject[keyof typeof DeviceTypeObject]
enum의 단점
확장이 불가능.
enum을 사용하기 위해서는 import가 필요.
enum Fruit {
Apple = 'Apple',
Banana = 'Banana
}
getSomeFruits('Apple') // X
getSomeFruits(Fruit.Apple) // O
enum 중 numeric enum은 타입 안정성이 보장되지 않음.
enum Status {
pending = 0,
success = 1,
fail = 2
}
const newStatus: Status = 100 // 에러 발생하지 않음!
컴파일된 코드 사이즈가 가장 큼.
Best Practice
1. 우선 union type으로 타입 정의.
2. 런타임에 접근이 필요하다면 const object 사용.
3. numeric enum에서의 리버스 매핑, flag 값 정의 및 bitwise 연산 사용 등 특별한 경우에 한하여 enum 사용.
type Tstate = {
name: string;
capital: string;
}
interface IState {
name: string;
capital: string;
}
공통점
const wyoming: TState = {
name: 'Wyoming',
capital: 'Cheyenne',
population: 500_000
};
type TDict = { [key: string]: string};
interface IDict {
[key: string]: string;
}
type TFn = (x: number) => string;
interface IFn {
(x: number) :string;
}
type TPair<T> = {
first: T;
second: T;
}
interface IPair<T> {
first: T;
second: T;
}
class StateT implements TState {
name: string = '';
capital: string = '';
}
class StateI implements IState {
name: string = '';
capital: string = '';
}
차이점
interface IState {
name: string;
capital: string;
}
interface IState {
population: number;
}
const wyoming: IState = {
name: 'Wyoming',
capital: 'Cheyenne',
population: 500_000
};
type Pair = [number, number];
interface Tuple {
0: number;
1: number;
length: 2;
}
Best Practice
1. 우선 interface 사용.(특히 외부 API일 경우)
2. 합 타입, tuple 사용 시 타입 별칭 사용.
타입을 명시적으로 선언하지 않아도 타입을 알 수 있다면 타입이 추론됨. 이를 이용한다면 리팩토링이 쉬워짐.
타입 추론이 되더라도 명시적으로 선언하는 게 나은 경우.
const elmo: Product = {
name: 'Tickle Me Elmo',
id: '048188 627152',
price: 28.99,
};
Best Practice
타입을 추론할 경우 값을 가지고 할당 가능한 값의 집합을 유추함.
필요하다면 타입 넓히기 과정을 제어할 필요가 있음.
const v1 = {
x: 1,
y: 2,
}; // { x: number; y: number; }
const v2 = {
x: 1 as const,
y: 2m
}; // { x:1 , y: number; }
const v3 = {
x: 1,
y: 2,
} as const; // { readonly x: 1; readonly y: 2}
const a1 = [1, 2, 3]; // number[]
const a2 = [1, 2, 3] as const; // readonly [1, 2, 3]
조건문을 이용해 타입 가드를 활용한다면 넓은 타입에서 좁은 타입으로 타입을 좁혀가며 추론할 수 있음.
class Address {
constructor(public city: string, public zip: string) {}
}
function print(name: string | null, address: Address | {}) {
if (name != null) {
console.log(name)
} else {
console.log("name is null")
}
if (typeof name == 'string') {
console.log(name)
} else {
console.log("name is null")
}
if (address instanceof Address) {
console.log(address.city + " " + address.zip)
} else {
console.log("address is null")
}
if ('city' in address) {
console.log(address.city + " " + address.zip)
} else {
console.log("address is null")
}
}
구별된 유니온: 공통된 리터럴 멤버 속성이 있다면 해당 속성으로 유니온 구성원을 구별할 수 있음.
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Rectangle;
function area(s: Shape) {
if (s.kind === "square") {
return s.size * s.size;
}
else {
return s.width * s.height;
}
}
사용자 정의 타입 가드: 어떠한 인자는 어떠한 타입이다, 라는 값을 반환하는 함수.
function isDefined<T>(x: T | undefined): x is T {
return x !== undefined;
}
const members = ['Janet', 'Michael'].map(
who => jackson5.find(n => n === who)
).filter(isDefined); // string[]
동적 데이터 등을 작성할 때, 해당 데이터의 형식을 정의.
let foo:{ [index:string] : {message: string} } = {};
foo['a'] = { message: 'some message' }; // Ok
foo['a'] = { messages: 'some message' }; // Error: Type '{ messages: string; }' is not assignable to type '{ message: string; }'.`message` 부분에 오타 존재
인덱서의 key 타입은 string, number, symbol 혹은 template literal type만 사용 가능.
인덱스 시그니처 사용 시 주의할 점
interface NestedCSS {
color?: string;
[selector: string]: string | NestedCSS | undefined;
}
const failsSilently: NestedCSS = {
colour: 'red', // `colour`는 유효한 문자열 인덱서이므로 오류 아님
}
Mapped object type: string, number, symbol 등의 타입 외 별도의 타입을 key로 사용하고 싶거나, 키마다 별도의 타입을 사용하고 싶다면?
type Vec3D = {[k in 'x' | 'y' | 'z' ]: number};
type ABC = {[k in 'a' | 'b' | 'c' ]: k extends 'b' ? string : number};
함수 선언의 두 가지 방식
type Callable = {
(a: number): number;
};
type ArrowFunction = (a: number) => number;
두 방식은 동일하나, 오버로드 추가는 콜러블 방식에서만 가능.
type OverloadCallable = {
(a: number): number;
(a: string): string;
};
뉴어블: 특별한 종류의 콜러블 타입 어노테이션. 앞에 new가 붙으며, 실행 시 new를 통해 실행시켜야 함.
interface CallMeWithNewToGetString {
new(): string
}
declare const Foo: CallMeWithNewToGetString;
const bar = new Foo();
타입스크립트에서 함수 오버로드는 타입 체커에게 인자를 알려주기 위함. 이는 런타임에서 사라지므로 런타임 동작과는 상관 없음.
즉 실제로 JS 코드로 컴파일되는 함수 바디 내에서 모든 인자 타입에 따른 케이스를 구현해야 함.
// Overloads
function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
function padding(a: number, b?: number, c?: number, d?: number) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a;
}
else if (c === undefined && d === undefined) {
c = a;
d = b;
}
return {
top: a,
right: b,
bottom: c,
left: d
};
}
조건부 타입을 이용하여 타입 공간에 if문을 만들어주는 효과를 줄 수 있음.
function double1(x: number|string): number|string;
function double1(x: any) { return x + x }
const num1 = double1(12) // string | number
const str1 = double1('x') // string | number
function double2(x: number): number;
function double2(x: string): string;
function double2(x: any) { return x + x };
const num2 = double2(12); // number
const str2 = double2('x'); // string
function f2(x: number|string){
return double2(x); // string|number 형식의 인수는 'string'형식의 매개변수에 할당 불가
}
function double3<T extends number | string> (
x: T
): T extends string ? string: number;
function double3(x: any) { return x + x };
const num3 = double2(12); // number
const str3 = double2('x'); // string
function f3(x: number|string){
return double3(x); // string|number 형식의 인수는 'string'형식의 매개변수에 할당 불가
}
함수 내부의 this 값은 함수가 정의되는 시점이 아닌, 실행되는 시점에 결정되므로 this 타입을 추론하기 어려움. 이를 위해 함수 내에서 this 타입을 명시할 수 있음.
class C {
a: string = "a"
f(this: C) {
console.log(this.a)
}
}
const c: C = new C()
c.f()
const callback = c.f
callback() // Error: The 'this' context of type 'void' is not assignable to method's 'this' of type 'C'.
Decorator: 클래스의 다양한 property 및 method의 정의를 수정 및 교체하는 function.
일부 속성이나 메서드로 기존 클래스를 확장하는 데 적합.
type ClassDecorator = <TFunction extends Function>
(target: TFunction) => TFunction | void;
정보 수집 혹은 일부 메서드나 속성 추가까지도 가능.
type PropertyDecorator =
(target: Object, propertyKey: string | symbol) => void;
메서드 데코레이터와 접근자 데코레이터는 거의 유사함. 유일한 차이는 속성 설명자의 프로퍼티.
설명자를 통해, 원래 구현을 재정의할 수 있음.
type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
일반적으로 다른 데코레이터가 사용할 수 있는 정보를 기록하는데에 사용.
type ParameterDecorator = (
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) => void;
reflect-metadata 라이브러리를 통해 각종 타입에 대한 metadata를 정의할 수 있음.
import "reflect-metadata"
const user = {
firstName: "정민",
}
Reflect.defineMetadata("phone", "010-1234-5678", user)
console.log(user)
const number = Reflect.getMetadata("phone", user)
console.log(number)
require / exports: CommonJS 키워드
import / export: ES6 키워드
변수, 상수, 함수, 클래스: JS 모듈 로더 코드로 컴파일.
타입: JS코드에서는 삭제됨.
네임스페이스: JS 일반 객체로 컴파일.(IIFE 함수에 해당 객체를 전달하고, 그 함수를 즉시 호출하는 식.)
default로 모듈을 export할 시 import하면 모듈명.객체명으로 접근.
각각 export할 시 각 객체들을 import 해야 함.
declare: 명시된 변수, 상수, 함수, 클래스가 어딘가에 이미 선언되어 있음을 알림. 즉, TS 컴파일러에게 타입 정보를 알리기만 함.
declare 블록(declare namespace, declare module, declare global)
.d.ts파일: 선언부만을 작성하는 용도의 파일. 즉 JS 코드로 컴파일되지 않음. 이 파일에 작성되는 declare namespace 블록과 declare module 블록의 필드들에는 export 키워드가 기본으로 붙음.