Woongjae Lee
NHN Dooray - Frontend Team
Lead Software Engineer @ProtoPie
Microsoft MVP
TypeScript Korea User Group Organizer
Marktube (Youtube)
이 웅재
enum ToastType {
AFTER_SAVED,
AFTER_PUBLISHED,
AFTER_RESTORE,
}
interface Toast {
type: ToastType,
createdAt: string,
}
const toasts: Toast[] = [...];
// toastNodes1 -> (JSX.Element | undefined)[]
const toastNodes1 = toasts.map((toast) => {
if (toast.type === ToastType.AFTER_SAVED)
return (
<div key={toast.createdAt}>
<AfterSavedToast />
</div>
);
else if (toast.type === ToastType.AFTER_PUBLISHED)
return (
<div key={toast.createdAt}>
<AfterPublishedToast />
</div>
);
else if (toast.type === ToastType.AFTER_RESTORE)
return (
<div key={toast.createdAt}>
<AfterRestoredToast />
</div>
);
});
// toastNodes2 -> JSX.Element[]
const toastNodes2 = toasts.map((toast) => {
if (toast.type === ToastType.AFTER_SAVED)
return (
<div key={toast.createdAt}>
<AfterSavedToast />
</div>
);
else if (toast.type === ToastType.AFTER_PUBLISHED)
return (
<div key={toast.createdAt}>
<AfterPublishedToast />
</div>
);
else
return (
<div key={toast.createdAt}>
<AfterRestoredToast />
</div>
);
});
// toastNodes3 -> JSX.Element[]
const toastNodes3 = toasts.map((toast) => {
if (toast.type === ToastType.AFTER_SAVED)
return (
<div key={toast.createdAt}>
<AfterSavedToast />
</div>
);
else if (toast.type === ToastType.AFTER_PUBLISHED)
return (
<div key={toast.createdAt}>
<AfterPublishedToast />
</div>
);
else if (toast.type === ToastType.AFTER_RESTORE)
return (
<div key={toast.createdAt}>
<AfterRestoredToast />
</div>
);
else return neverExpected(toast.typs);
});
function neverExpected(value: never): never {
throw new Error(`Unexpected value : ${value}`);
}
// toastNodes4 -> JSX.Element[]
const toastNodes4 = toasts.map((toast) => {
if (toast.type === ToastType.AFTER_SAVED)
return (
<div key={toast.createdAt}>
<AfterSavedToast />
</div>
);
if (toast.type === ToastType.AFTER_PUBLISHED)
return (
<div key={toast.createdAt}>
<AfterPublishedToast />
</div>
);
if (toast.type === ToastType.AFTER_RESTORE)
return (
<div key={toast.createdAt}>
<AfterRestoredToast />
</div>
);
return neverExpected(toast.typs);
});
const toastNodes5 = toasts.map((toast) => {
switch (toast.type) {
case ToastType.AFTER_SAVED:
return (
<div key={toast.createdAt}>
<AfterSavedToast />
</div>
);
case ToastType.AFTER_PUBLISHED:
return (
<div key={toast.createdAt}>
<AfterPublishedToast />
</div>
);
case ToastType.AFTER_RESTORE:
return (
<div key={toast.createdAt}>
<AfterRestoredToast />
</div>
);
default:
return neverExpected(toast.type);
}
});
// v3.9.7
class Square1 {
area; // error! implicit any
sideLength; // error! implicit any
}
생성자 함수가 없으면, 디폴트 생성자가 불린다.
클래스의 프로퍼티 혹은 멤버 변수가 정의되어 있지만, 값을 대입하지 않으면 undefined 이다.
접근제어자 (Access Modifier) 는 public 이 디폴트 이다.
class Person1 {
name!: string;
age!: number;
}
const person: Person1 = new Person1();
console.log(person); // Person1 {}
person.age = 38;
console.log(person.name); // undefined
class Person2 {
name!: string;
age!: number;
constructor() {
console.log(this.name === null); // false
console.log(this.name === undefined); // true
}
}
const person2: Person2 = new Person2();
person2.name = 'Mark';
person2.age = 38;
console.log(person2); // Person2 {name: 'Mark', age: 38}
클래스의 프로퍼티를 선언과 동시에 값을 할당하는 방법도 있다.
생성자가 불리기 전에 이미 프로퍼티의 값이 저장되어 있음을 알 수 있다.
class Person3 {
name: string = 'Mark';
age: number = 38;
constructor() {
console.log(this.name); // 'mark'
}
}
const person3: Person3 = new Person3();
console.log(person3); // Person3 {name: 'Mark', age: 38}
private 으로 설정된 프로퍼티는 dot 으로 접근할 수 없다.
클래스 내부에서는 private 프로퍼티를 사용할 수 있다.
private 이 붙은 변수나 함수는 _ 를 이름앞에 붙이는데, 이는 문법이 아니라 널리 쓰이는 코딩 컨벤션이다.
class Person4 {
public name!: string;
private _age: number;
constructor(age: number) {
this._age = age;
}
}
const person4: Person4 = new Person4(38);
person4.name = 'Mark';
// person._age (X)
console.log(person4); // Person {name: 'Mark', _age: 38}
부모에서 private 으로 설정된 프로퍼티는 상속을 받은 자식에서도 접근할 수 없다.
부모에서 protected 로 설정된 프로퍼티는 상속을 받은 자식에서 접근이 가능하다.
상속을 받은 자식 클래스에서 부모 클래스에 this 를 통해 접근하려면, 생성자에서 super(); 를 통해 초기화 해야한다.
class Parent5 {
private privateProp!: string;
protected protectedProp!: string;
constructor() {}
}
class Child5 extends Parent5 {
constructor() {
super();
this.protectedProp = 'protected';
// this.privateProp = 'private'; // (X)
}
}
const child5 = new Child5();
console.log(child5);
디폴트 생성자는 프로그래머가 만든 생성자가 없을 때 사용할 수 있다.
사용자가 만든 생성자가 하나라도 있으면, 디폴트 생성자는 사라진다.
생성자에서 바로 멤버로 할당할 수 있는 방법이 있다.
class Person6 {
constructor(public name: string, private _age: number) {}
}
const person6: Person6 = new Person6('Mark', 38);
// const person6: Person6 = new Person6();
클래스 내부에 작성된 메서드는 public 이 디폴트
arrow function 으로 작성 가능
private 을 이용하면 클래스 외부애서 접근 불가
class Person7 {
constructor(private _name: string, private _age: number) {}
print(): void {
console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 살 입니다.`);
}
printName = (): void => {
console.log(`이름은 ${this._name} 입니다.`);
};
private printAge(): void {
console.log(`나이는 ${this._age} 살 입니다.`);
}
}
const person: Person7 = new Person7('Mark', 36);
person.print(); // 이름은 Mark 이고, 나이는 36 살 입니다.
person.printName(); // 이름은 Mark 입니다.
// person.printAge(); // (X)
상속은 extends 키워드를 이용한다.
자식 클래스에서 디폴트 생성자는 부모의 생성자와 입력 형태가 같다.
접근제어자의 오버라이드
class Parent8 {
constructor(protected _name: string, protected _age: number) {}
print(): void {
console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 살 입니다.`);
}
printName = (): void => {
console.log(`이름은 ${this._name} 입니다.`);
};
private printAge(): void {
console.log(`나이는 ${this._age} 살 입니다.`);
}
}
class Child8 extends Parent8 {
_name = 'Mark Jr.';
}
// const p: Child8 = new Child8(); // (X)
const child8: Child8 = new Child8('', 5);
child8.print(); // 이름은 Mark Jr. 이고, 나이는 5 살 입니다.
console.log(child8._name);
생성자를 정의하고, this 를 사용하려면, super 를 통해 부모의 생성자를 호출해줘야 한다.
super 를 호출할때는 부모 생성자의 입력 타입이 같아야 한다.
super 를 호출하는 것은 클래스 외부에서 호출하는 것과 같다.
protected 함수를 호출해서 그 안의 private 을 출력하는 것에 주의한다.
class Parent9 {
constructor(protected _name: string, private _age: number) {}
print(): void {
console.log(`이름은 ${this._name} 이고, 나이는 ${this._age} 살 입니다.`);
}
protected printName = (): void => {
console.log(`이름은 ${this._name} 입니다.`);
};
protected printAge(): void {
console.log(`나이는 ${this._age} 살 입니다.`);
}
}
class Child9 extends Parent9 {
constructor(age: number) {
super('Mark Jr.', age);
this.printName();
this.printAge();
}
}
const child9: Child9 = new Child9(1); // 이름은 Mark Jr. 입니다. 나이는 1 살 입니다.
_ 를 변수명 앞에 붙이고, 내부에서만 사용한다.
getter 를 함수처럼 설정하면, 프로퍼티처럼 꺼내쓸수있다.
마찬가지로 setter 를 함수처럼 설정하면, 추가 작업을 하고 셋팅할 수 있다.
class Person10 {
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
get name() {
return this._name;
}
set name(name: string) {
// 작업
this._name = `${name} Lee`;
}
}
const person10: Person10 = new Person10('Mark', 36);
console.log(person10.name);
person10.name = 'Woongjae';
console.log(person10.name);
class Person11 {
public static CITY = '';
private static lastName: string = 'Lee';
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
public print() {
console.log(`${this._name} ${Person11.lastName} in ${Person11.CITY}.`);
}
}
const person11: Person11 = new Person11('Mark', 36);
Person11.CITY = 'Seoul';
person11.print(); // Mark Lee in Seoul.
class Person12 {
public static Talk(): void {
console.log('안녕하세요.');
}
}
Person12.Talk(); // 안녕하세요.
/*
class Person13 {
private static PROPERTY = '프라이빗 프로퍼티';
private static METHOD() {
console.log('프라이빗 메서드');
}
constructor() {
console.log(Person13.PROPERTY);
Person13.METHOD();
}
}
*/
const PROPERTY = '모듈 내 변수';
function METHOD() {
console.log('모듈 내 함수');
}
export class Person13 {
constructor() {
console.log(PROPERTY);
METHOD();
}
abstract 키워드가 사용된 클래스는 new 로 생성할 수 없다.
abstract 키워드가 사용된 클래스를 상속하면 abstract 키워드가 붙은 함수를 구현해야 한다.
abstract class AbstractPerson14 {
protected _name: string = 'Mark';
abstract setName(name: string): void;
}
class Person14 extends AbstractPerson14 {
setName(name: string): void {
this._name = name;
}
}
// const person14 = new AbstractPerson14(); // (X)
const person14 = new Person14();
생성자 함수 앞에 접근제어자인 private 을 붙일 수 있다.
하지만 외부에서 생성이 불가능하다.
class PrivateClass {
private constructor() {}
}
// const p: PrivateClass = new PrivateClass(); (X)
private 생성자를 이용해서 내부에서만 인스턴스 생성이 가능하도록 함.
pubilc static 메서드를 통해 private static 인스턴스 레퍼런스를 획득한다.
Lazy Loading (Initialization) : 최초 실행시가 아니라, 사용시에 할당을 함
/*
class Preference {
public static getInstance(): Preference {
if (Preference.instance === null) {
Preference.instance = new Preference();
}
return Preference.instance;
}
private static instance: Preference | null = null;
private constructor() {}
}
const p: Preference = Preference.getInstance();
*/
class PreferenceClass {
constructor() {}
}
export const Preference = new PreferenceClass();
export type PreferenceType = typeof Preference;
private readonly 로 선언된 경우, 생성자에서는 할당이 가능하다.
private readonly 로 선언된 경우, 생성자 이외에서는 할당이 불가능하다.
public readonly 로 선언된 경우, 클래스 외부에서는 다른값을 할당할 수 없다.
마치 getter 만 있는 경우와 같다.
class Person17 {
private readonly _name: string;
public readonly age: number = 36;
constructor(name: string) {
this._name = name;
}
public setName(name: string) {
// this._name = name; (X)
}
}
const person17: Person17 = new Person17('Mark');
console.log(person17.age);
// person17.age = 37; // (X)
// v3.9.7
class Square2 {
area: number;
sideLength: number;
}
const square2 = new Square2();
console.log(square2.area); // compile time - number, runtime - undefined
console.log(square2.sideLength); // compile time - number, runtime - undefined
Class 의 Property 가 생성자 혹은 선언에서 값이 지정되지 않으면,
컴파일 에러를 발생시켜 주의를 준다.
// v3.9.7
class Square2 {
area: number; // error TS2564: Property 'area' has no initializer and is not definitely assigned in the constructor.
sideLength: number; // error TS2564: Property 'sideLength' has no initializer and is not definitely assigned in the constructor.
}
// 사용자는 시도조차 할 수 없도록 만듭니다.
const square2 = new Square2();
console.log(square2.area);
console.log(square2.sideLength);
// v3.9.7
class Square3 {
area: number = 0;
sideLength: number = 0;
}
// v3.9.7
class Square4 {
area: number;
sideLength: number;
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}
// v4.0.2
class Square5 {
area; // 4 부터는 any 가 아니라, 생성자에 의해 추론된다.
sideLength; // 4 부터는 any 가 아니라, 생성자에 의해 추론된다.
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}
// v4.0.2
class Square6 {
sideLength;
constructor(sideLength: number) {
if (Math.random()) {
this.sideLength = sideLength;
}
}
get area() {
return this.sideLength ** 2; // error! Object is possibly 'undefined'.
}
}
// v4.0.2
class Square7 {
sideLength!: number; // ! 로 의도를 표현해야 한다.
constructor(sideLength: number) {
this.initialize(sideLength);
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
get area() {
return this.sideLength ** 2;
}
}
hello 의 리턴이 any 이기 때문에 타입 헬퍼가 제대로 되지 않음
helloGeneric 을 사용하면 정상적으로 사용가능
function helloString(message: string): string {
return message;
}
function helloNumber(message: number): number {
return message;
}
// 더 많은 반복된 함수들 ...
function hello(message: any): any {
return message;
}
function helloGeneric<T>(message: T): T {
return message;
}
console.log(hello('Mark').length);
console.log(hello(38).length); // undefined
console.log(helloGeneric('Mark').length);
// console.log(helloGeneric<number>('Mark').length); (X)
console.log(helloGeneric(38).toString());
// console.log(helloGeneric(36).length); (X)
Generic 타입을 쓰지 않으면, T 를 추론
Generic 타입을 쓰면, T 를 검증
function helloBasic<T>(message: T): T {
return message;
}
console.log(helloBasic<string>('Mark'));
const age = helloBasic(38);
// helloBasic<number>('38'); (X)
function helloArray<T>(messages: T[]): T {
return messages[0];
}
function helloTuple<T, K>(messages: [T, K]): T {
return messages[0];
}
console.log(helloArray(['Hello', 'World'])); // string[]
console.log(helloArray(['Hello', 1])); // Array<string | number>
console.log(helloTuple(['Hello', 'World'])); // [string, string]
console.log(helloTuple(['Hello', 1])); // [string, number]
// console.log(helloTuple(['Hello', 'world', 1])); // Error
type HelloFunctionGeneric = <T>(message: T) => T;
const helloFunction: HelloFunctionGeneric = <T>(message: T): T => {
return message;
};
console.log(helloFunction<string>('Hello').length);
class Person<T> {
private _name: T;
constructor(name: T) {
this._name = name;
}
}
new Person('Mark');
// new Person<string>(38); (X)
class Person6<T extends string | number> {
private _name: T;
constructor(name: T) {
this._name = name;
}
}
new Person6('Mark');
new Person6(38);
// new Person6(true); // T 가 string 또는 number 를 상속받기 때문에 boolean 은 X
class Person7<T, K> {
private _name: T;
private _age: K;
constructor(name: T, age: K) {
this._name = name;
this._age = age;
}
}
new Person7('Mark', 38);
interface Person8 {
name: string;
age: number;
}
const person8: Person8 = {
name: 'Mark',
age: 36
};
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
setProperty(person8, 'name', 'Anna');
// setProperty(person8, 'name', 27);
console.log(getProperty(person8, 'name'));
// console.log(getProperty(person8, 'fullname'));
function shuffle(value: string | any[]): string | any[] {
if (typeof value === 'string')
return value
.split('')
.sort(() => Math.random() - 0.5)
.join('');
return value.sort(() => Math.random() - 0.5);
}
console.log(shuffle('Hello, Mark!')); // string | any[]
console.log(shuffle(['Hello', 'Mark', 'long', 'time', 'no', 'see'])); // string | any[]
console.log(shuffle([1, 2, 3, 4, 5])); // string | any[]
function shuffle2<T extends string | any[]>(
value: T,
): T extends string ? string : T;
function shuffle2(value: any) {
if (typeof value === 'string')
return value
.split('')
.sort(() => Math.random() - 0.5)
.join('');
return value.sort(() => Math.random() - 0.5);
}
// function shuffle2<"Hello, Mark!">(value: "Hello, Mark!"): string
shuffle2('Hello, Mark!');
// function shuffle2<string[]>(value: string[]): string[]
shuffle2(['Hello', 'Mark', 'long', 'time', 'no', 'see']);
// function shuffle2<number[]>(value: number[]): number[]
shuffle2([1, 2, 3, 4, 5]);
// error! Argument of type 'number' is not assignable to parameter of type 'string | any[]'.
shuffle2(1);
function shuffle3(value: string): string;
function shuffle3<T>(value: T[]): T[];
function shuffle3(value: string | any[]): string | any[] {
if (typeof value === 'string')
return value
.split('')
.sort(() => Math.random() - 0.5)
.join('');
return value.sort(() => Math.random() - 0.5);
}
shuffle3('Hello, Mark!');
shuffle3(['Hello', 'Mark', 'long', 'time', 'no', 'see']);
shuffle3([1, 2, 3, 4, 5]);
class ExportLibraryModal {
public openComponentsToLibrary(
libraryId: string,
componentIds: string[],
): void;
public openComponentsToLibrary(componentIds: string[]): void;
public openComponentsToLibrary(
libraryIdOrComponentIds: string | string[],
componentIds?: string[],
): void {
if (typeof libraryIdOrComponentIds === 'string') {
if (componentIds !== undefined) { // 이건 좀 별루지만,
// 첫번째 시그니처
libraryIdOrComponentIds;
componentIds;
}
}
if (componentIds === undefined) { // 이건 좀 별루지만,
// 두번째 시그니처
libraryIdOrComponentIds;
}
}
}
const modal = new ExportLibraryModal();
modal.openComponentsToLibrary(
'library-id',
['component-id-1', 'component-id-1'],
);
modal.openComponentsToLibrary(['component-id-1', 'component-id-1']);
함수다.
컴파일 타임에는 그 함수의 타입만 체크한다.
런타임에 사용 및 처리가 된다.
클래스, 메서드, 프로퍼티, 메서드의 파라미터에 사용할 수 있다.
클래스가 인스턴스로 만들어질 때가 아니라, 최초 클래스가 참조될 때, 한번만 적용된다.
experimentalDecorators 옵션
// Basic
function classDecorator<T extends { new (...args: any[]): {} }>(constructorFn: T) {...}
function classDecoratorFactory(...) {
return function<T extends { new (...args: any[]): {} }>(constructorFn: T) {...}
}
@classDecorator
class Test {
}
@classDecorator()
class Test {
}
/*
# constructorFn
- constructorFn
- return class
*/
function classDecorator<T extends { new (...args: any[]): {} }>(target: T) {
return class extends target {
constructor(...args: any[]) {
super(args);
}
public print() {
console.log('this is print');
}
};
}
@classDecorator
class Test1 {}
(new Test1() as any).print();
function classDecoratorFactory(arg: string) {
return function<T extends { new (...args: any[]): {} }>(constructorFn: T) {
constructorFn.prototype.print2 = function() {
console.log('this is print2', arg);
};
constructorFn.prototype.gender = 'male';
return class extends constructorFn {
public name = 'mark';
private _age = 36;
constructor(...args: any[]) {
super(args);
}
public print() {
console.log('this is print', arg);
}
};
};
}
@classDecoratorFactory('what')
class Test2 {}
const test2 = new Test2();
(test2 as any).print(); // this is print what
(test2 as any).print2(); // this is print2 what
console.log(Test2.prototype); // class_1 { constructor: [Function: class_1], print: [Function] }
console.log(test2); // class_1 { name: 'mark', _age: 36 }
console.log(Object.keys(test2)); // [ 'name', '_age' ]
console.log((test2 as any).gender); // male
function methodDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {...}
function methodDecoratorFactory(...) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {...};
}
function methodDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('target', target);
console.log('propertyKey', propertyKey);
console.log('descriptor', descriptor);
}
class Test3 {
@methodDecorator
public print() {}
}
// target Test3 { print: [Function] }
// propertyKey print
// descriptor { value: [Function], writable: true, enumerable: true, configurable: true }
target
클래스
propertyKey
메소드 이름
descriptor
PropertyDescriptor (lib.es5.d.ts)
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
function methodDecoratorFactory(canBeEdit: boolean = false) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.writable = canBeEdit;
};
}
class Test4 {
@methodDecoratorFactory()
first() {
console.log('first original');
}
@methodDecoratorFactory(true)
second() {
console.log('second original');
}
@methodDecoratorFactory(false)
third() {
console.log('third original');
}
}
const test4 = new Test4();
test4.first = function() {
console.log('first new');
}; // runtime error
test4.second = function() {
console.log('second new');
};// runtime error
test4.third = function() {
console.log('third new');
};
configurable
이 속성기술자는 해당 객체로부터 그 속성을 제거할 수 있는지를 기술한다.
true 라면 삭제할 수 있다. 기본값은 false.
enumerable
해당 객체의 키가 열거 가능한지를 기술한다.
true 라면 열거가능하다. 기본값은 false.
value
속성에 해당되는 값으로 오직 적합한 자바스크립트 값 (number, object, function, etc) 만 올 수 있다. 기본값은 undefined.
writable
writable이 true로 설정되면 할당연산자 assignment operator 를 통해 값을 바꿀 수 있다. 기본값은 false.
function log(show: boolean = true) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
show && console.log('start');
original(...args);
show && console.log('end');
};
};
}
class Test5 {
@log()
print1(first: string, second: number) {
console.log('print1', first, second);
}
@log(false)
print2(first: string, second: number) {
console.log('print2', first, second);
}
}
const test5 = new Test5();
test5.print1('mark', 36);
test5.print2('mark', 36);
function propertyDecorator(target: any, propName: string) {...}
function propertyDecoratorFactory(...) {
return function(target: any, propName: string) {...}
}
target
propName
function propertyDecorator(target: any, propName: string): any {
console.log(target);
console.log(propName);
return {
writable: false
};
}
class Test6 {
@propertyDecorator
name: string = 'Mark';
}
const test6 = new Test6();
test6.name = 'Anna'; // runtime error
function parameterDecorator(
target: any,
methodName: string,
paramIndex: number
) {...}
function parameterDecoratorFactory(...) {
return function(
target: any,
methodName: string,
paramIndex: number
) {...}
}
target
methodName
paramIndex
function parameterDecorator(
target: any,
methodName: string,
paramIndex: number
) {
console.log('parameterDecorator start');
console.log(target);
console.log(methodName);
console.log(paramIndex);
console.log('parameterDecorator end');
}
class Test7 {
private _name: string;
private _age: number;
constructor(name: string, @parameterDecorator age: number) {
this._name = name;
this._age = age;
}
print(@parameterDecorator message: string) {
console.log(message);
}
}
// parameterDecorator start
// Test7 { print: [Function] }
// print
// 0
// parameterDecorator end
// parameterDecorator start
// [Function: Test7]
// undefined
// 1
// parameterDecorator end
interface StringContainer {
value: string;
format(): string;
split(): string[];
}
interface NumberContainer {
value: number;
nearestPrime: number;
round(): number;
}
type Item1<T> = {
id: T,
container: any;
};
const item1: Item1<string> = {
id: "aaaaaa",
container: null
};
type Item2<T> = {
id: T;
container: T extends string ? StringContainer : NumberContainer;
};
const item2: Item2<string> = {
id: 'aaaaaa',
container: null, // Type 'null' is not assignable to type 'StringContainer'.
};
type Item3<T> = {
id: T extends string | number ? T : never;
container: T extends string
? StringContainer
: T extends number
? NumberContainer
: never;
};
const item3: Item3<boolean> = {
id: true, // Type 'boolean' is not assignable to type 'never'.
container: null, // Type 'null' is not assignable to type 'never'.
};
type ArrayFilter<T> = T extends any[] ? T : never;
type StringsOrNumbers = ArrayFilter<string | number | string[] | number[]>;
// 1. string | number | string[] | number[]
// 2. never | never | string[] | number[]
// 3. string[] | number[]
interface Table {
id: string;
chairs: string[];
}
interface Dino {
id: number;
legs: number;
}
interface World {
getItem<T extends string | number>(id: T): T extends string ? Table : Dino;
}
let world: World = null as any;
const dino = world.getItem(10);
const what = world.getItem(true); // Error! Argument of type 'boolean' is not assignable to parameter of type 'string | number'.ts(2345)
type Flatten<T> = T extends any[]
? T[number]
: T extends object
? T[keyof T]
: T;
const numbers = [1, 2, 3];
type NumbersArrayFlattened = Flatten<typeof numbers>;
// 1. number[]
// 2. number
const person = {
name: 'Mark',
age: 38
};
type SomeObjectFlattened = Flatten<typeof person>;
// 1. keyof T --> "id" | "name"
// 2. T["id" | "name"] --> T["id"] | T["name"] --> number | string
const isMale = true;
type SomeBooleanFlattened = Flatten<typeof isMale>;
// true
type UnpackPromise<T> = T extends Promise<infer K>[] ? K : any;
const promises = [Promise.resolve('Mark'), Promise.resolve(38)];
type Expected = UnpackPromise<typeof promises>; // string | number
function plus1(seed: number): number {
return seed + 1;
}
type MyReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
type Id = MyReturnType<typeof plus1>;
lookupEntity(plus1(10));
function lookupEntity(id: Id) {
// query DB for entity by ID
}
// type Exclude<T, U> = T extends U ? never : T;
type Excluded = Exclude<string | number, string>; // number - diff
// type Extract<T, U> = T extends U ? T : never;
type Extracted = Extract<string | number, string>; // string - filter
// Pick<T, Exclude<keyof T, K>>; (Mapped Type)
type Picked = Pick<{name: string, age: number}, 'name'>;
// type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Omited = Omit<{name: string, age: number}, 'name'>;
// type NonNullable<T> = T extends null | undefined ? never : T;
type NonNullabled = NonNullable<string | number | null | undefined>;
/*
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
*/
/*
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;
*/
type MyParameters = Parameters<(name: string, age: number) => void>; // [name: string, age: number]
interface Constructor {
new (name: string, age: number): string;
}
/*
type ConstructorParameters<
T extends new (...args: any) => any
> = T extends new (...args: infer P) => any ? P : never;
*/
type MyConstructorParameters = ConstructorParameters<Constructor>; // [name: string, age: number]
/*
type InstanceType<T extends new (...args: any) => any> = T extends new (
...args: any
) => infer R
? R
: any;
*/
type MyInstanceType = InstanceType<Constructor>; // string
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Person {
id: number;
name: string;
hello(message: string): void;
}
type T1 = FunctionPropertyNames<Person>;
type T2 = NonFunctionPropertyNames<Person>;
type T3 = FunctionProperties<Person>;
type T4 = NonFunctionProperties<Person>;
By Woongjae Lee
LG CNS 타입스크립트 특강 3주차