[Day8] #class #Generics #Utility Types #casting
다른분들 학습 블로그를 보니 정말 열심히 해야겠다는 동기부여가 생긴다.
전공자분들은 전공자인 만큼, 비전공자는 비전공자대로 다들 열심히 정리를 하시고 회고도 하시는게 멋있었다.
나도 회고를 좀 더 하면서 돌아봐야겠다고 생각했다 !
오늘 수업은 좀 몰랐던 TS의 어려운 부분들을 많이 들었다. 그리고 일단 오늘 지각했다. 눈 떴더니 9시반이었다… ㄷ ㄷ 암튼 자기소개는 당첨 안됐다고 하셔서 수업 듣는데 뭔가 정리가 안되는데 훅훅 진도가 나가서 일단 따라 적었다. ㅜㅜ 내일부턴 자바 알고리즘이니깐 알고리즘 기간동안 TS 개념과 JS 개념을 깊이있게 공부해야한다. “해야지”가 아니라 “해야한다”…!
포스팅 정리하면서 정리해봐야지. 내일부터는 웹표준 JS 스윽 훑어볼 예정이다 !
상속 : Implements
인터페이스는 implements 키워드를 통해 클래스가 따라야 하는 type을 정의하는 데 사용한다.
1
2
3
4
5
6
7
8
9
10
11
interface Shape {
getArea: () => number;
}
class Rectangle implements Shape {
public constructor(protected readonly width: number, protected readonly height: number) {}
public getArea(): number {
return this.width * this.height;
}
}
상속 : Extends
TypeScript에서는, 일반적인 객체-지향 패턴을 사용할 수 있습니다. 클래스-기반 프로그래밍의 가장 기본적인 패턴 중 하나는 상속을 이용하여 이미 존재하는 클래스를 확장해 새로운 클래스를 만들 수 있다는 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
Override
- 클래스가 다른 클래스를 확장하는 경우, 같은 이름을 가진 부모 클래스의 멤버를 바꿀 수 있다.
- TypeScript의 최신 버전에서는 override 키워드를 사용해 명시적으로 표시할 수 있다ㄴ. ```ts interface Shape { getArea: () => number; }
class Rectangle implements Shape { public constructor(protected readonly width: number, protected readonly height: number) {}
public getArea(): number { return this.width * this.height; }
public toString(): string { return Rectangle[width=${this.width}, height=${this.height}]
; } }
class Square extends Rectangle { public constructor(width: number) { super(width, width); }
public override toString(): string { return Square[width=${this.width}]
; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
🔹 기본적으로 override키워드는 메서드를 오버라이드할 때 선택 사항이며, 존재하지 않는 메서드를 실수로 오버라이드하는 것을 방지하는 데 도움이 된다.
## Abstract Classes
- 추상 클래스는 다른 클래스들이 파생될 수 있는 기초 클래스이다.
- 추상 클래스는 직접 인스턴스화할 수 없다.
- 추상 클래스는 인터페이스와 달리 멤버에 대한 구현 세부 정보를 포함할 수 있다.
- abstract 키워드는 추상 클래스뿐만 아니라 추상 클래스 내에서 추상 메서드를 정의하는데 사용된다.
```ts
abstract class Polygon {
// 구현 블락이 없음 -> 이럴때 abstract 라고 한다.
// abstract를 가질 때 클래스에도 abstract를 붙여줘야됨.
public abstract getArea(): number;
public toString(): string {
return `Polygon[area=${this.getArea()}]`;
}
}
class Rectangle extends Polygon {
public constructor(protected readonly width: number, protected readonly height: number) {
super();
}
public getArea(): number {
return this.width * this.height;
}
}
TypeScript Basic Generics
1
2
3
4
5
6
7
// 들어오기 전까지 무슨 type인지 모르니깐 아무거나 이름을 붙임 S, T
// 동적 type을 해석해서 param에 들어오면 type을 고정시킬거야.
// 인터페이스의 이름이라고 생각할까봐 비어있는 상태임을 알려주기 위해 <S, T>라고 알려줌.
function createPair<S, T>(v1: S, v2: T): [S, T] {
return [v1, v2]; // tuple return
}
console.log(createPair<string, number>('hello', 42)); // ['hello', 42]
Classes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class NamedValue<T> {
private _value: T | undefined; // 데이터 선언
constructor(private name: string) {} // 로컬변수로 끝나는게 아니라 멤버변수로 사용할거구나
public setValue(value: T) { // 로컬변수
this._value = value;
}
public getValue(): T | undefined {
return this._value;
}
public toString(): string {
return `${this.name}: ${this._value}`;
}
}
let value = new NamedValue<number>('myNumber');
// 객체를 생성할 때 T를 number로 하겠다.라고 써주면 타입이 고정됨.
// myNumber는 private name: string이 됨
value.setValue(10);
console.log(value.toString()); // myNumber: 10
Type Alias
제네릭에 Type Alias를 사용하면 재사용성이 더 높아진다.
1
2
type Wrapped<T> = { value: T };
const wrappedValue: Wrapped<number> = { value: 10 };
1
2
3
4
5
6
7
8
9
10
11
class A {
constructor(private a: number){} // 로컬변수가 아니라 멤버변수로 등록
public toString():string {
return a+"";
}
}
type Wrapped<S> = { value: S };
const a: Wrapped<A<number>> = { value: new A(10) };
console.log(a)
Default Value
제네릭에는 다른 값이 지정되거나 추론되지 않을 때 기본값이 할당될 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class NamedValue<T = string> {
private _value: T | undefined;
constructor(private name: string) {}
public setValue(value: T) {
this._value = value;
}
public getValue(): T | undefined {
return this._value;
}
public toString(): string {
return `${this.name}: ${this._value}`;
}
}
let value = new NamedValue('myNumber');
value.setValue('myValue');
console.log(value.toString()); // myNumber: myValue
Extends
허용되는 것을 제한하기 위해 제약 조건을 제네릭에 추가할 수 있다. 제약조건을 사용하면 제네릭 유형을 사용할 때 더 구체적인 유형에 의존할 수 있다.
1
2
3
4
function createLoggedPair<S extends string | number, T extends string | number>(v1: S, v2: T): [S, T] {
console.log(`creating pair: v1='${v1}', v2='${v2}'`);
return [v1, v2];
}
TS Utility Types
Partial
Partial 객체의 모든 속성을 선택 사항으로 변경한다.
1
2
3
4
5
6
7
8
9
10
interface Point {
x: number;
y: number;
}
let a:Point = {x : 10, y : 10};
let pointPart: Partial<Point> = {};
// Point의 부분을 사용할 것이다.
// `Partial` allows x and y to be optional
pointPart.x = 10;
Required
Required는 모든 속성을 필수로 변경한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Car {
make: string;
model: string;
mileage?: number;
}
let yeeunCar:Car={
make: 'Ford',
model: 'Focus',
}
let myCar: Required<Car> = {
make: 'Ford',
model: 'Focus',
mileage: 12000 // `Required` forces mileage to be defined
};
Record
Record는 특정 key 타입과 value 타입으로 객체 타입을 정의하는 단축키
1
2
3
4
5
6
7
const nameAgeMap: Record<string, number> = {
'Alice': 21,
'Bob': 25
};
a.ony=25;
a['hkd'] = 40;
// {'Alice': 21, 'Bob': 25, 'ony' : 25, 'hkd' : 40}
💡 Record<string, number> 와 { [key: string]: number } 는 동일하다.
Omit
Omit은 객체 타입에서 key를 제거한다.
1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age: number;
location?: string;
}
const bob: Omit<Person, 'age' | 'location'> = {
name: 'Bob'
// `Omit` has removed age and location from the type and they can't be defined here
};
Pick
Pick은 지정된 key만 남기고 모든걸 제거한다.
1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age: number;
location?: string;
}
const bob: Pick<Person, 'name'> = {
name: 'Bob'
// `Pick` has only kept name, so age and location were removed from the type and they can't be defined here
};
Exclude
Exclude는 union에서 타입을 제거한다.
1
2
3
type Primitive = string | number | boolean
const value: Exclude<Primitive, string> = true;
// a string cannot be used here since Exclude removed it from the type.
ReturnType
ReturnType는 함수 타입의 리턴 타입을 추출한다.
1
2
3
4
5
type PointGenerator = () => { x: number; y: number; };
const point: ReturnType<PointGenerator> = {
x: 10,
y: 20
};
Parameters
Parameters는 함수 타입의 매개변수 타입을 배열로 추출한다.
1
2
3
4
5
6
type PointPrinter = (p: { x: number; y: number; }) => void;
const point: Parameters<PointPrinter>[0] = {
x: 10,
y: 20
};
// 여러개의 파라미터가 있을 수 있기 때문에 몇번째 파라미터인지 작성해야함. <PointPrinter>[0]
Readonly
Readonly는 모든 속성이 읽기 전용인 새로운 타입을 만드는데 사용된다.
1
2
3
4
5
6
7
8
9
10
interface Person {
name: string;
age: number;
}
const person: Readonly<Person> = {
name: "Dylan",
age: 35,
};
person.name = 'Israel';
// error TS2540: Cannot assign to 'name' because it is a read-only property.
TS Keyof
keyof with explicit keys
명시적 키가 있는 객체 유형에서 사용하는 경우 keyof는 해당 키로 유니온 타입을 생성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Person {
name: string;
age: number;
}
// `keyof Person` here creates a union type of "name" and "age", other strings will not be allowed
function printPersonProperty(person: Person, property: keyof Person) {
console.log(`Printing person property ${property}: "${person[property]}"`);
}
let person = {
name: "Max",
age: 27
};
printPersonProperty(person, "name"); // 두번째 인자에는 name, age 중 하나 넣을 수 있음
keyof with index signatures
keyof는 인덱스 타입을 뽑아내 인덱스 시그니처로 사용될 수 있다.
1
2
3
4
5
type StringMap = { [key: string]: unknown };
// `keyof StringMap` resolves to `string` here
function createStringPair(property: keyof StringMap, value: string): StringMap {
return { [property]: value };
}
TypeScript Null & Undefined
기본적으로 null 및 undefined는 비활성화되어 있으며, strictNullChecks를 true로 설정하여 활성화할 수 있다.
Types
null, undefined는 기본 타입이며 string과 같은 다른 유형처럼 사용할 수 있습니다.
1
2
3
4
5
6
7
8
let value: string | undefined | null = null;
console.log(typeof value); // object
value = 'hello';
console.log(typeof value); // string
value = undefined;
console.log(typeof value); // undefined
strictNullChecks가 활성화된 경우, TypeScript는 정의되지 않은 값이 명시적으로 타입에 추가되지 않는 한 값을 설정해야 합니다.
Optional Chaining
Optional Chaining은 TypeScript의 null 처리에서 잘 작동하는 JS 기능이다. 존재할 수도 있고 존재하지 않을 수도 있는 객체의 속성에 컴팩트한 구문으로 접근할 수 있게 해준다. 속성에 접근할 때는 .? 연산자와 함께 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface House {
sqft: number;
yard?: {
sqft: number;
};
}
function printYardSize(house: House) {
const yardSize = house.yard?.sqft;
if (yardSize === undefined) {
console.log('No yard');
} else {
console.log(`Yard is ${yardSize} sqft`);
}
}
let home: House = {
sqft: 500
};
printYardSize(home); // Prints 'No yard'
Nullish Coalescence
- null 또는 정의되지 않은 경우, 특히 fallback이 있는 경우 작성한다.
- 거짓값이 발생할 수 있지만 여전히 유효한 경우 사용
- && 연산자를 사용하는 것과 유사
1
2
3
4
5
6
function printMileage(mileage: number | null | undefined) {
console.log(`Mileage: ${mileage ?? 'Not Available'}`);
}
printMileage(null); // Prints 'Mileage: Not Available'
printMileage(0); // Prints 'Mileage: 0'
Null Assertion
TypeScript의 추론 시스템은 완벽하지 않기 때문에 값이 null 또는 undefined 가능성을 무시하는 것이 합리적일 때가 있다. T 이를 수행하는 쉬운 방법은 casting을 사용하는 것이지만, TS는 “!”연산자를 편리한 단축키로 제공한다.
1
2
3
4
5
function getValue(): string | undefined {
return '';
}
let value = getValue();
console.log('value length: ' + value!.length); // value length: 0
Array bounds handling
strictNullChecks를 활성화하더라도 기본적으로 TypeScript는 배열 접근이 undefined로 반환되지 않는다고 가정한다(undefined가 배열 type의 일부가 아닌 경우).
1
2
3
let array: number[] = [];
let value = array[0]; // undefined
// with `noUncheckedIndexedAccess` this has the type `number | undefined`