[ 🍫 사내스터디-2023.01 ~ 03]
null
과 undefined
가 모든 타입에서 허용되는지 확인undefined
는 객체가 아닙니다 같은 런타임 오류 방지🪄 유용한 ts관련 vscode 익스텐션
🤔❔ never은 언제 필요할까?
never 가이드
불가능
을 나타내는 타입이 필요 => 공집합- 타입스크립트에서는
불가능
을 아래와 같이 다양한 방법으로 나타내고 있다.
- 값을 포함할 수 없는 빈 타입
1) 제네릭과 함수에서 허용되지 않는 매개변수
2) 호환되지 않는 타입들의 교차 타입
3) 빈 합집합(무의 합집합)
- 실행이 끝날 때 호출자에게 제어를 반환하지 않는 함수의 반환 타입
- 절대로 도달할수 없을 esle 분기의 조건 타입
- 거부된 프로미스에서 처리된 값의 타입
- 사용 케이스
/* 유니언 유형에서 멤버를 필터링 */ type Foo = { name: 'foo' id: number } type Bar = { name: 'bar' id: number } type All = Foo | Bar type ExtractTypeByName<T, G> = T extends { name: G } ? T : never type ExtractedType = ExtractTypeByName<All, 'foo'> // the result type is Foo // type ExtractedType = { // name: 'foo'; // id: number; // }
/* mapped type에서 키를 필터링 하는 용도 */ type Filter<Obj extends Object, ValueType> = { [Key in keyof Obj as ValueType extends Obj[Key] ? Key : never]: Obj[Key] } interface Foo { name: string id: number } type Filtered = Filter<Foo, string> // {name: string;}
/* 제어 흐름에서 타입을 좁히고 싶을 때 */ function throwError(): never { throw new Error() } let foo: string | undefined if (!foo) { throwError() } foo // string
할당 가능한
: ~의 부분집합 관계extends
: ~에 할당가능한, ~의 부분 집합// # Intersection 타입 문법
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
// NOTE: Union 타입 => 어떤 것이 들어올지 모르므로 공통된 속성만 접근가능
function askSomeone(someone: Developer | Person) {
someone.name; // O
// someone.age; // X
}
askSomeone({ name: '서영', skill: 'js' });
askSomeone({ name: '서영', age: 20 });
// NOTE: Intersection 타입 => 조합된 모든 속성만 접근가능
function askSomeone2(someone: Developer & Person) {
someone.name; // O
someone.age; // O
someone.skill; // O
}
askSomeone2({ name: '서영', age: 20, skill: 'js' });
🤔❔ 표 2-1 인터섹션 / 유니온
- 타입 연산자는 인터페이스의 속성이 아니라 값의 집합에 적용되어 헷갈리는 것
- 교집합: T1 & T2 = keyof A | keyof B
Developer & Person
// name, age, skill- 합집합: T1 | T2 = keyof A & keyof B
Developer | Person
// name
🤔❔ Exclude
// 정의 type Exclude<T, U> = T extends U ? never : T;
// T에 오는 타입들 중 U에 오는 것들은 제외 type Fruit = "cherry" | "banana" | "strawberry" | "lemon"; type RedFruit = Exclude<Fruit, "banana" | "lemon">; // type RedFruit = "cherry" | "strawberry"
컴파일 과정에서 타입 정보는 제거됨
typeof
타입 | 값 |
---|---|
타입스크립트 타입 반환 | 런타임 연산자(string, number, function, object...) |
interface Person {
name: string;
}
const p: Person = { name: 'seoyoung' };
type T = typeof p; // 타입은 Person
const v1 = typeof p; // 값은 'object'
🤔❓ class 48~50pg
class Cylinder { radius=1; height=1; } function calculateVolume(shape: unknown) { if (shape instanceof Cylinder) { shape // 정상, 타입은 Cylinder shape.radius // 정상, 타입은 number }
- ❓ 48pg 마지막줄 "다음 예제에서 Cylinder 클래스는 타입으로 쓰였습니다"
instanceof
앞Cyliner
?
- 47pg: instanceof는 런타임 연산자이고 값에 대한 연산을 함
- ts playground 컴파일 결과:Cylinder
사라지지 않음
타입 | 값 |
---|---|
속성과 메서드 사용 | 생성자 사용 |
const v = typeof Cylinder; // 값이 function; 클래스는 js에서 함수로 구현
type T = typeof Cylinder; // 타입이 class Cylinder, 즉 생성자 함수
const c = new fn(); // 타입이 Cylinder(인스턴스타입)
type C = InstanceType<typeof Cylinder>; // 타입이 Cylinder(인스턴스타입)
🤔❔
obj['field']
vsobj.field
- 값은 동일하지만 타입은 다를 수 있음
- 타입의 속성을 얻을 때는 반드시
obj['field']
사용interface Person { name: string }; type t1 = Person["name"] // string type t2 = Person.name // 오류 // Cannot access 'Person.name' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'name' in 'Person' with 'Person["name"]'?
🤔❔ 타입으로 쓰이는
this
- 다형성의
this
- 메서드 체인을 구현할 때 유용
export class calculator { constructor(public value: number =0){} add(value : number){ this.value += value; return this } multiply(value : number){ this.value *= value; return this } } import {Calculator} from './method-chain' let calc = new Calculator; let result = calc.add(1).add(2).multiply(3).value
const v1 = 'string literal'; // 문자열 리터럴
type T1 = 'string literal'; // 문자열 리터럴 타입
const s1: T1 = 'string'; // 오류
// type '"string"' is not assignable to type '"string literal"'.
const s2: T1 = 'string literal'; // 정상
interface Person { name: string };
const p: Person = { name: 'seoyoung' }; // 타입 선언
const p = { name: 'seoyoung' } as Person; // 타입 단언
interface Person { name: string };
const alice: Person = {}; // name속성이 없습니다
const bob = {} as Person; // 오류 없음
const people = ['alice', 'bob'].map(name => ({name}))
// { name: string }[]
// Person[]이지만 bad - 런타임 오류 발생 가능성
const people = ['alice', 'bob'].map(name => ({name} as Person))
const people = ['alice', 'bob'].map(name => ({} as Person)) // [{}, {}] 인데 오류 없음
// 안전하지만 복잡
const people = ['alice', 'bob'].map(name => {
const person: Person = { name }
return person
})
🤔❔ 화살표 함수의 반환타입을 명시하는 방법
// 소괄호가 중요 => name의 타입이 없고 반환타입은 Person const people = ['alice', 'bob'].map( (name): Person => ({ name }) ) // Person[]
// 방법2 - 더직관적 const makePersonList = (name: string): Person => ({ name }) const people = ['alice', 'bob'].map(makePersonList) // Person[]
new
없이 BigInt
와 Symbol
호출시 기본형 생성// 리터럴 할당시 잉여속성 체크
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
const r: Room = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present',
}
// 개체 리터럴은 알려진 속성만 지정할 수 있으며 'Room' 형식에 'elephant'이(가) 없습니다.
const obj = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present',
}
const r: Room = obj; // 정상
interface Options {
darkMode: boolean;
[otheroption: string]: unknown
}
const o: Options = { darkMode: true } // 정상
const o: Options = { darkMode: true, color: 'black' } // 정상
🤔❔ 약한타입(선택적 속성만 가짐) - 공통 속성 체크
interface LineChartOptions { logscale?: boolean; invertedYAxis?: boolean; areaChart?: boolean; } const opts = { logScale: true }; const o: LineChartOptions = opts; // ~ Type '{ logScale: boolean; }' has no properties in common // with type 'LineChartOptions'
- 잉여속성 체크와 다르게 약한 타입과 관련된 할당문마다 수행
const opts = { logScale: true }; const o: LineChartOptions = opts; // Type '{ logScale: boolean; }' has no properties in common with type 'LineChartOptions'.
// 임시 변수를 제거하더라도 공통 속성 체크는 여전히 동작 const o: LineChartOptions = { logScale: true }; // Object literal may only specify known properties, but 'logScale' does not exist in type 'LineChartOptions'. Did you mean to write 'logscale'?
const add = (a: number, b: number): number => a+b;
// ❗️ 함수 표현식 전체에 타입 구문을 적용하는 것이 좋음
type BinaryFn = (a: number, b: number) => number
const add: BinaryFn = (a, b) => a + b;
I
를 붙이는 것은 현재는 지양해야할 스타일type TFn = (a: number) => string
interface IFn = {
(a: number) => string
}
type TState = {
name: string,
}
interface IState {
name: string;
}
// IStateWithPop 와 TStateWithPop 는 동일
interface IStateWithPop extends TState {
population: number;
}
type TStateWithPop = IState & { population: number; };
type
키워드는 유니온이 가능하고 튜플과 배열타입을 더 간결하게 표현 가능interface Pair {
0: number;
1: number;
length: 2;
}
type Pair = [number, number];
type
키워드는 매핑된 타입 또는 조건부 타입 같은 고급 기능에 활용 되기도 함
interface
키워드는 보강이 가능함
// 속성확장 - 선언 병합 (사용자가 채워야 하는 빈틈)
interface IState {
name: string;
}
interface IState {
population: number;
}
const wyoming: IState = {
name: 'wyoming',
population: 50000
} // 정상
type
사용interface
사용extends
를 사용해서 인터페이스 반복을 피해야 함keyof
는 타입을 받아서 속성타입의 유니온을 반환interface Options {
width: number;
height: number;
color: string;
}
type OptionsKeys = keyof Options; // 'width' | 'height' | 'color'
Pick
어떤 정의된 객체 형태의 타입에서 특정한 프로퍼티들만 골라서 새로운 타입 생성// 정의
type Pick<T, K extends keyof T> = {
[k in K]: T[K]
}
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Partial
모든 프로퍼티를 선택적으로 만드는 타입을 구성// 정의
type Partial<T> = {
[P in keyof T]?: T[P];
}
type OptionsUpdate = Partial<Options>;
interface OptionsUpdate { // 기존의 Options타입과 동일하면서 선택적 필드
width?: number;
height?: number;
color?: string;
}
// keyof와 매핑된 타입으로 동일하게 생성 가능
type OptionsUpdate = { [k in keyof Options]?: Options[k] };
ReturnType
함수 T의 반환 타입으로 구성된 타입 생성type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
type T3 = ReturnType<(<T extends U, U extends number[]>() => T)>; // number[]
declare function f1(): { a: number, b: string }
type T4 = ReturnType<typeof f1>; // { a: number, b: string }
type Roket = {
[propertiy: string]: string
}
const roket: Roket = {
name: 'sally',
thrust: '4,940 kN'
}
// [키의 위치만 표시하는용도: 보통 string 사용]: 어떤 것이든 가능
name
대신 Name
도 가능{}
도 유효thrust
는 number여야 할수도 있음key
로 숫자를 사용하면 자바스크립트 런타임은 문자열로 변환> {1: 2, 3: 4}
{'1': 2, '3': 4}
nunmber
타입은 버그를 잡기위한 순수 타입스크립트 코드interface Array<T> {
// ...
[n: number]: T;
}
number
를 인덱스 타입으로 사용하면 숫자 속성이 어떤 특별한 의미를 지닌다는 오해를 불러 일으킬 수 있음const arr = [1, 2, 3]; // 숫자로 인덱스할 항목을 지정하는 경우 배열을 사용
for-in
루프는 느림ArrayLike
타입을 사용ArrayLike
를 사용하더라도 키는 여전히 문자열const tupleLike: ArrayLike<string> = {
'0': 1,
'1': 2,
length: 2,
}; // 정상
readonly
는 얕게(shallow) 동장로 선언readonly
는 얕게(shallow) 동작interface Outer {
inner: {
x: number;
}
}
const o: Readonly<Outer> = { inner: { x: 0 } };
o.inner = { x: 1 }; // 오류
o.inner.x = 1; // 정상
🤔❔
const
vsreadonly
// 변경 가능성을 옮긴 것 const arr1: number[] = [3] arr1 = [3, 4] // Cannot assign to 'ac' because it is a constant // 가리키는 배열을 자유럽게 변경할 수 있지만 배열 자체는 변경 못함 let arr2: readonly number[] = [3] arr2 = [3, 4]
// ScatterProps 가 변경되면 REQUIRES_UPDATE 도 변경됨을 알림
const REQUIRES_UPDATE: {[k in keyof ScatterProps]: boolean} {
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false,
};
function shouldUpdate(
oldProps: ScatterProps,
newProps: ScatterProps
) {
let k: keyof ScatterProps;
for(k in oldProps) {
if(oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) return true;
return false;
}
}
실패에 닫힌 방법
은 오류 발생 시 적극적으로 대처하는 방향🔍 린터 규칙: no-infferable-types
-> 작성된 모든 타입 구문이 정말로 필요한지 확인
let
대신 const
로 선언하게 되어 타입추론에 도움타입 넓히기
-> 지정된 단일 값을 가지고 할당 가능한 값들의 집합을 유추
const mixed = ['x', 1];
('x' | 1)[] ['x', 1] [string, number] readonly [string, number] (string|number)[] readonly (string|number)[] [any, any] any[]
const
로 넓히기 과정을 제어할 수 있음const x = 'x'; // 타입이 'x'
let vec = {x: 10, y: 20, z:30};
getComponent(vec, x); // 정상
const v: { x: 1|3|5; } = {
x: 1,
}; // 타입이 { x: 1|3|5; }
2) 타입 체커에 추가적 문맥 제공
ex) 함수의 매개변수로 값을 전달`
3) const 단언문
사용
const v1 = {
x: 1,
y: 2,
}; // 타입은 { x: number; y: number;}
const v2 = {
x: 1 as const, // const 단언
y: 2,
}; // 타입은 { x: 1; y: number;}
const v3 = {
x: 1,
y: 2,
} as const; // const 단언
// 타입은 { readyonly x: 1; readyonly y: 2;}
const a1 = [1,2,3]; // 타입은 number[]
const a2 = [1,2,3] as const; // 타입이 readonly [1,2,3]
명시적 태그
를 붙이는 것 (태그된 유니온, 구별된 유니온)interface UploadEvent { type: 'upload'; filename: string; contents: string }
interface DownloadEvent { type: 'download'; filename: string; }
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent){
switch (e.type) {
case 'download':
e // DownloadEvent
break;
case 'upload':
e // UploadEvent
break;
}
}
function isInputElement(el: HTMLElement): el is HTMLInputElement {
return 'value' in el;
}
function getElementContent(el: HTMLElement){
if (isInputElement(el)) {
el; // 타입이 HTMLInputElement
return el.value;
}
el; // 타입이 HTMLElement
return el.textContent;
}
🤔❔ 타입 가드 예시
NAME is TYPE
형태의 타입 술부(Predicate)를 반환 타입으로 명시function isNumber(val: string | number): val is number { return typeof val === 'number'; } function someFunc(val: string | number) { if (isNumber(val)) { val.toFixed(2); } else { val.split(""); } } someFunc(123);
null
또는 {}
이용하여 객체 전개 사용declare let hasMiddle: boolean
const firstLast = { first: 'Harry', last: 'Truman' }
const president = { ...firstLast, ...(hasMiddle ? { middle: 'S' } : {}) }
// const president: {
// middle?: string;
// first: string;
// last: string;
// }
function addOptional<T extends object, U extends object>(a: T, b: U | null): T & Partial<U> {
return { ...a, ...b }
}
declare let hasMiddle: boolean
const firstLast = { first: 'Harry', last: 'Truman' }
const president = addOptional(firstLast, hasMiddle ? { middle: 'S' } : null)
president.middle // OK, type is string | undefined
Lodash
같은 유틸리티 라이브러리를 사용하는 것 권장콜백
보다는 프로미스
가 코드를 작성하기 쉽고 타입을 추론하기 쉬움async/awiat
을 사용하면🤔❔
Promise<T>
new Promise<T>( ( resolve: (sucessValue: T) => void, reject: (any) => void ) => { //코드 구현 } ); // 타입스크립트에서 Promise는 다음처럼 제네릭 클래스 형태로 사용 const numPromise: Promise<number> = new Promise<number>(콜백 함수); const strPromise: Promise<string> = new Promise<string>(콜백 함수); const arrayPromise: Promise<number[]> = new Promise<number[]>(콜백 함수);
type Language = 'JavaScript' | 'TypeScript' | 'Python'
function setLanguage(language: Language) {
/* ... */
}
setLanguage('JavaScript') // OK
let language = 'JavaScript'
setLanguage(language) // Error. Argument of type 'string' is not assignable to parameter of type 'Language'.
// 해결법 1 - 타입을 선언해 값을 제한하는 것
let language: Language = 'JavaScript'
setLanguage(language) // OK
// 해결법 2 - 상수로 만드는 것
const language = 'JavaScript'
setLanguage(language) // OK
function panTo(where: [number, number]) {
/* ... */
}
panTo([10, 20]) // OK
const loc = [10, 20] // number[]
panTo(loc) // Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
// 해결법 1 - 명시적 선언
const loc: [number, number] = [10, 20]
panTo(loc) // OK
// 해결법 2 - as const를 사용해 상수 문맥을 제공: 너무 과하게 정확
const loc = [10, 20] as const // readonly [10, 20]
panTo(loc)
// ~~~ Type 'readonly [10, 20]' is 'readonly'
// and cannot be assigned to the mutable type '[number, number]'
// 오류는 타입 정의가 아니라 호출되는 곳에서 발생
// 해결법 3 - panTo 함수에 readonly 구문을 추가
function panTo(where: readonly [number, number]) {
/* ... */
}
const loc = [10, 20] as const
panTo(loc) // OK
type Language = 'JavaScript' | 'TypeScript' | 'Python'
interface GovernedLanguage {
language: Language
organization: string
}
function complain(language: GovernedLanguage) {
/* ... */
}
complain({ language: 'TypeScript', organization: 'Microsoft' }) // OK
const ts = {
language: 'TypeScript',
organization: 'Microsoft',
}
complain(ts)
// ~~ Argument of type '{ language: string; organization: string; }'
// is not assignable to parameter of type 'GovernedLanguage'
// Types of property 'language' are incompatible
// 해결법 1 - 타입 선언 추가
const ts: GovernedLanguage = {
// ...
}
// 해결법 2 - as const를 사용
const ts = {
language: 'TypeScript',
organization: 'Microsoft',
} as const
"당신의 작업은 엄격하게 하고 다른 사람의 작업은 너그럽게 받아들여야 한다."
ageNum
(x) age: number
(o)null
여부가 다른 값의 null
여부에 관련되도록 설계하면 안됨null
이거나 null
이 아니게 만들어야 함null
이 존재하지 않도록함/* bad */
interface Person {
name: string;
// 다음은 둘 다 동시에 있거나 동시에 없습니다
placeOfBirth?: string;
dateOfBirth?: Date;
}
/* good */
interface Person {
name: string;
birth?: {
place: string;
date: Date;
}
}
string
은 any
와 비슷한 문제를 가지고 있어string
타입보다는 문자열 리터럴 타입의 유니온을 사용string
보다 keyof T
를 사용🤔❔
any
vsunknown
let notSure: unknown; notSure = "maybe a string instead"; // any type에는 unknown 타입 할당 가능 let anyType: any; anyType = notSure; // any, unknown 이외의 type에는 unknown 타입 할당 불가능 let numberType: number; numberType = notSure; // error
- 공통점: 모든 타입을 허용
- 차이점
any unknown "어떤 타입이든 가능하다" "어떤 타입인지 알 수 없다, 모른다" 타입 검사를 느슨하게하여 체크 하지 않음 프로퍼티 또는 연산을 하는 경우 컴파일러가 체크 타입을 좁혀서 사용하지 않아도 되서 자유로움 컴파일 에러가 나므로 무조건 타입을 좁혀서 사용(타입 가드 필요) ->
any
보다는unknown
을 사용하는것이 좀 더 안전하게 코딩을 할 수 있는 방법
"컴퓨터 과학에서 어려운일은 캐시 무효화와 이름 짓기 뿐이다"
interface Vector2D {
x: number;
y: number;
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D);
interface Vector2D {
_brand: "2d";
x: number;
y: number;
}
function vec2D(x: number, y: number): Vector2D {
return { x, y, _brand: "2d" };
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
calculateNorm(vec2D(3, 4)); // 정상
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D); // '_brand' 속성이 없습니다.
- 타입시스템은 선택적이고 점진적이어서 정적이면서도 동적인 특성
- 마이그레이션할 때
any
타입이 중요한 역할
// bad
function f1() {
const x: any = expressionReturningFoo();
processBar(x);
}
// good
function f2() {
const x = expressionReturningFoo();
processBar(x as any);
}
any
타입을 반환하면 타입 안정성이 나빠짐any
대신 @ts-ignore
사용 권장any
타입의 값을 그대로 정규식이나 함수에 넣지 말아야 함// bad
function getLengthBad(array: any) {
return array.length;
}
// good
function getLength(array: any[]) {
return array.length;
}
// 객체이긴 하지만 값을 알 수 없는 경우
function paramTest({[key: string]: any}) {
...
}
any
단언문은 타입이 정의된 함수 안으로 감추는 것이 더 좋은 설계const result = []; // any[]
result.push('a'); // string []
result.push(1); // (string|number) []
noImplicity
가 설정된 상태에서 변수의 타입이 암시적인 any
에 값을 할당할 경우에만 진화any
선언시 타입 그대로 유지any
상태일 때 값을 읽으려고 하면 오류 발생any
타입은 함수 호출을 거쳐도 진화하지 않음function makeSquares(start: number, limit: number) {
const out = []; // 'out' 변수는 일부 위치에서 암시적으로 'any[]' 형식입니다.
range(start, limit).forEach((i) => {
out.push(i * i);
});
return out; // 'out' 변수에는 암시적으로 'any[]' 형식이 포함됩니다.
}
any | unknown | never |
---|---|---|
모든 타입 any 타입에 할당가능 | 모든 타입 unknown 타입에 할당가능 | 어떤 타입도 never에 할당 불가능 |
어떠한 타입으로도 할당 가능 | unknown과 any타입에만 할당 가능 | 어떠한 타입으로도 할당 가능 |
unknown
을 반환하고 단언문을 사용하거나 원하는대로 타입을 좁히는 것이 좋음{}
를 사용하는 방법은 unknown
보다는 범위가 좁음{}
타입은 null
과 undefined
제외한 모든 값 포함null
과 undefined
가 불가능하다고 판단되는 경우 unknown
대신 {}
사용export {};
declare global {
interface Document {
monkey: string;
}
}
interface MonkeyDocument extends Document {
monkey: string;
}
(document as MonkeyDocument).monkey = "Monkey";
// 백분율 출력
$ npx type-coverage
// any타입 위치 모두 출력
$ npx type-coverage --detail
전이 의존성
: 다른 사용자가 프로젝트 설치 시 dependencies에 들어 있는 라이브러리도 설치 됨
peerDependenceise
: 런타임에 필요하긴 하지만 의존성을 직접 관리하지 않는 라이브러리들
DefinitelyTyped git
: 타입스크립트 커뮤니티에서 유지보수하고 있는 자바스크립트 라이브러리의 타입을 정의한 모음
@types
의 의존성은 devDependencies
에 있어야 함다음 세가지 버전 중 하나라도 맞지 않으면 오류 발생
1) 라이브러리의 버전
2) 타입선언(@types
)의 버전
3) 타입스크립트의 버전
라이브러리와 타입 정보의 버전이 별도로 관리되는 방식의 문제점
1) 라이브러리는 업데이트 && 타입 선언은 업데이트 하지 않은 경우
2) 라이브러리보다 타입 선언의 버전이 최신
3) 타입스크립트 버전보다 라이브러리에서 필요로 하는 타입스크립트 버전이 최신
4) @types
의존성 중복
번들링 방식의 문제점
1) 타입스크립트 버전이 올라가며 오류 발생(번들된 타입에서는 @types
버전 선택 불가능)
2) 프로젝트 내 타입선언이 다른 라이브러리의 타입 선언에 의존하는 경우
3) 과거 버전에 있는 타입 선언에 문제가 있는 경우 과거 버전에서 패치 업데이트 해야 함
4) 타입 선언의 패치 업데이트를 자주 하기 어려움
🎯 결론:
DefinitelyTyped
를 사용하자
=> 라이브러리 업데이트 & 해당 @types 업데이트
Parameters
와 ReturnType
제너릭 타입을 사용하여 추출할 수 있긴 함JSDoc vs TsDoc
1) TSDoc 주석은 마크다운 형식으로 꾸며짐 (in@example
)
2) 타입정보가 코드에 있기때문에@param
과@returns
에 타입 정보를 명시하지 않음
3) 에러타입 역시 명시하지 않고@link
등으로 API문서와 연결하기도 함
4) TSDoc 설명은 한줄(80자 미만)이어야 하며 부가적 설명이 필요한 경우@remarks
이용
this
는 다이나믹 스코프로 정의된 방식이 아닌 호출된 방식에 따라 달라짐function addKeyListener (
el: HTMLElement,
fn: (this: HTMLElement, e:KeyboardEvent) => void
) {
el.addEventListener('keydown', e => {
fn.call(el, e)
fn(el, e); // 1개의 인수가 필요한데 2개를 가져옴
fn(e); // 'void' 형식의 'this' 컨텍스트를 'HTMLElement' 형식에 할당 할 수 없음
})
}
function double<T extends string | number>(x: T): T extends string ? string : number;
미러링(mirroring)
: 의존하는 라이브러리 구현과 무관하게 타입에만 의존할 때 필요한 선언부만 추출하여 작성 중인 라이브러리에 넣는 것,
=> 의존성 분리에 유용, 프로젝트가 커지면 적용이 어렵고 명시적으로 의존성을 추가 하는 것 권장
dtslint
사용 권장 (DefinitelyTyped의 타입 선언을 위한 도구)// 특별한 형태의 주석을 통해 동작
const beatles = ['john', 'paul', 'george', 'ringo'];
map(beatles, function(
name, // $ExpectType string
i, // $ExpectType number
array // $ExpectType string[]
) {
this // $ExpectType string[]
return name.length;
}) // $ExpectType number[]
/* 글자 자체를 비교한다는 단점이 존재 */
this
역시 테스트 권장블랙박스 스타일
: 중간 단계의 세부 사항은 테스트 하지 않음
타입스크립트의 역할을 명확히 하기위해 다음은 사용하지 않는 것이 좋음
열거형(enum)
매개변수 속성
트리플 슬래시 임포트
데코레이터
=> 타입정보를 제거한다고 자바스크립트가 되지 않음
열거형(enum)
의 문제점
1) 숫자형에 0, 1, 2 외의 가른 숫자가 할당되면 위험
2) 대체제인 상수형 열거형은 런타임에 완전히 제거됨
(문자열 열거형과 다른 동작 / 바벨로 트랜스파일 될 수 없음)
3) 명목적 타이핑(타입의 이름이 같아야 할당 허용 !== 구조적타이핑) 사용
const enum Flavor {
VANILLA = "vanilla",
CHOCOLATE = "chocolate",
STRAWBERRY = "strawberry",
}
let flavor = Flavor.CHOCOLATE;
flavor = "strawberry";
// Type '"strawberry"' is not assignable to type 'Flavor'.
import { Flavor } from "ice-cream";
scoop(Flavor.VANILLA);
4) 열거형대신 리터럴 타입의 유니온 사용 권장
type Flavor = "vanilla" | "chocolate" | "strawberry";
let Flavor: Flavor = "chocolate"; // 자바스크립트와 호환되고 자동완성 기능도 사용 가능
5) 특정 번들러에서 TreeShaking
안됨
🤔 ❔ 열거형대신 리터럴 타입의 유니온 사용 권장
- Flavor 개수가 많다면..?
scoop(Flavor.CHOCOLATE)
vsscoop("chocolate")
?- enum을 사용하지 않고 상수를 const object를 통해서 관리할 때
type | interface
정의 할 경우, 정의된 상수를 사용 못함const Flavor = { VANILLA = "vanilla", CHOCOLATE = "chocolate", STRAWBERRY = "strawberry", }; type TypeFoo = { flavor: Flavor.VANILLA; // ERROR: Cannot find namespace 'Flavor' }
❗️
export const Flavor = { VANILLA: "vanilla", CHOCOLATE: "chocolate", STRAWBERRY: "strawberry", } as const; // as const 를 붙여야 "vanilla" | "chocolate" | "strawberry" type Flavor = typeof Flavor[keyof typeof Flavor]; // 붙이지 않을 경우 string
매개변수 속성
의 문제점class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 매개변수 속성 - public name
class Person {
constructor(public name: string) {}
}
1) 더 간결한 문법을 제공하지만 컴파일 시 코드 늘어남
2) 일반 속성과 섞어 사용할 시 클래스 설계 혼란
3) 이질적이고 초보자에게 생소한 문법
keyof
선언이 적절Object.entires
쓰임🔍 참고 자료
lib.dom.d.ts
타입 | 예시 |
---|---|
EventTarget | window, XMLHttpRequest |
Node | document, Text, Comment |
Element | HtmlElement, SVGElemnet |
HTMLElement | <i> , <b> |
HTMLButtonElement | <button> |
❗️ 인라인 함수
🔍 자바스크립트 인라인함수 link1 link2
: 함수 정의를 변수에 할당const test = function() { console.log("이것은 인라인 함수입니다."); }
일반 함수의 경우는 브라우저 자바 엔진이 javascript 를 코드 전체를 파싱(Parsing)하는 단계에 생성되는 반면, 인라인 함수는 변수에 대입되는 방식이므로 런타임(Runtime) 시에 생성
❓ 🤔 277pg "인라인 함수와 이벤트 타입 변경 적용하여 오류제거"
인라인 함수 어디...?function addDragHandler(el: HTMLElement) { el.addEventListener('mousedown', eDown => { const dragStart = [eDown.clientX, eDown.clientY]; const handleUp = (eUp: MouseEvent) => { el.classList.remove('dragging'); el.removeEventListener('mouseup', handleUp); const dragEnd = [eUp.clientX, eUp.clientY]; console.log('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i])); } el.addEventListener('mouseup', handleUp); }); } const div = document.getElementById('surface'); if (div) { addDragHandler(div); }
❗️🔍 인라인함수
: 일반적인 함수의 호출 과정을 거치지 않고, 함수의 모든 코드를 호출된 자리에 바로 삽입하는 방식의 함수function handleDrag(eDown: Event) { const targetEl = eDown.currentTarget; targetEl.classList.add('dragging'); // ~~~~~~~ Object is possibly 'null'. // ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget' const dragStart = [ eDown.clientX, eDown.clientY]; // ~~~~~~~ Property 'clientX' does not exist on 'Event' // ~~~~~~~ Property 'clientY' does not exist on 'Event' const handleUp = (eUp: Event) => { targetEl.classList.remove('dragging'); // ~~~~~~~~ Object is possibly 'null'. // ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget' targetEl.removeEventListener('mouseup', handleUp); // ~~~~~~~~ Object is possibly 'null' const dragEnd = [ eUp.clientX, eUp.clientY]; // ~~~~~~~ Property 'clientX' does not exist on 'Event' // ~~~~~~~ Property 'clientY' does not exist on 'Event' console.log('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i])); } targetEl.addEventListener('mouseup', handleUp); // ~~~~~~~ Object is possibly 'null' } const div = document.getElementById('surface'); div.addEventListener('mousedown', handleDrag); // ~~~ Object is possibly 'null'
타입 | 설명 |
---|---|
UIEvent | 모든 종류의 사용자 인터페이스 이벤트 |
MouseEvent | 클릭처럼 마우스로부터 발생되는 이벤트 |
TouchEvent | 모바일 기기의 터치 이벤트 |
WheelEvent | 스크롤 휠을 돌려서 발생되는 이벤트 |
KeyboardEvent | 키 누름 이벤트 |
정보를 숨기기 위해 가장 효과적인 방법은 클로저 사용
- 클로저 방식은 동일 클래스 개별 인스턴스간 속성접근 불가능
접두사로
#
을 붙여 비공개 필드기능 사용 가능 (현재 표준화 진행중)class ClassWithPrivateMethod { #privateMethod() { return 'hello world'; } getPrivateMessage() { return this.#privateMethod(); } } const instance = new ClassWithPrivateMethod(); console.log(instance.getPrivateMessage()); // 'hello world' ClassWithPrivateMethod.#privateMethod = 'good' // Private field '#privateMethod' must be declared in an enclosing class
- 동일한 클래스의 다른 인스턴스가 서로의 비공개 멤버를 읽을 수 있습니다.
class Thing { static #counter = 0; #hidden; constructor() { this.someProperty = 'foo'; this.#hidden = ++Thing.#counter; } showPublic() { return this.someProperty; } showPrivate() { return this.#hidden; } } const thing = new Thing(); const thing2 = new Thing(); const thing3 = new Thing(); console.log(thing3.showPrivate()) // 3
WeakMap
으로 대체❔ 🤔 WeakMap ?
키에 대한 강력한 참조를 생성하지 않는 키/값 쌍의 모음으로
키는 반드시 객체이며, 값은 임의의 JavaScript 타입
- 클로저와 비교하여 생성자에서 생성된 모든 인스턴스에 대해 동일한 WeakMap을 재사용가능
=> 메모리 효율성이 더 높음let Thing; { const privateScope = new WeakMap(); let counter = 0; Thing = function() { this.someProperty = 'foo'; privateScope.set(this, { hidden: ++counter, }); }; Thing.prototype.showPublic = function() { return this.someProperty; }; Thing.prototype.showPrivate = function() { return privateScope.get(this).hidden; }; } console.log(typeof privateScope); // "undefined" const thing = new Thing(); console.log(thing); // object {someProperty : "foo"} thing.showPublic(); // "foo" thing.showPrivate(); // 1 console.log(thing.privateScope.hidden); // Cannot set properties of undefined thing.privateScope.hidden = 72 // Cannot set properties of undefined
class Thing { static #counter = 0; #hidden; constructor() { this.someProperty = 'foo'; this.#hidden = ++Thing.#counter; } showPublic() { return this.someProperty; } showPrivate() { return this.#hidden; } } const thing = new Thing(); console.log(thing); // object {someProperty : "foo"} thing.showPublic(); // "foo" thing.showPrivate(); // 1
- WeakMap을 쓰는 이유는 WeakMap의 키는 기존 객체를 약한 참조해서 가비지컬렉션을 방해하지 않기 때문
- 약한 참조: 할당된 객체를 해지하기 위해 null을 넣으면 GC 수집대상이 되고 객체는 사라짐
- 강한 참조: 할당된 객체를 해지하기 위해 null을 넣어도 GC가 수집해가지 않음- 객체 안에 속성을 추가하기 싫고 객체에 정보를 저장하고 싶을 때 사용
let sally = { name: 'sally'}; let map = new Map(); map.set(sally, "hi") sally = null; // sally is stored inside the map, // we can get it by using map.keys() let weakSally = { name: 'weakSally'}; let map = new Map(); map.set(sally, "hi") weakSally = null; // weakSally is removed from memory
👀 활용 예시
타입스크립트를 도입할 때 가장 중요한 기능은 Es6모듈과 ES2015 클래스
for(;;)
대신 for-of
또는 배열 메서드 사용
index 필요한 경우 forEach
사용
for-in
의 문제점: 📚 느림
👀 why js for in loops are bad
=> 🤔 주의할 점만 알면 필요할 때 사용하는것에는 문제가 없을 것같음
함수 표현식보다 화살표 함수 사용하기
noImplicitThis
설정 시 this 바인딩 관련 오류 표시
📌
this
스코프 정리
1) 전역공간: 전역객체(window/global)를 참조
2) 메서드로서 호출한경우 (obj.func()
,obj[]()
- 앞에 객체가 명시 되어 있는 경우)
:메서드 호출 주체를 참조(obj)
3) 함수로서 호출한 경우: 전역객체를 참조 (메서드 내부함수에서도 동일)
4) 콜백함수 내부에서의 this:
제어권을 넘겨받은 함수가 정의한 바에 따르며
그렇지 않은경우 전역객체 참조
개발자가 this를 바인딩해서 넘기면 그에 따름
5) 생성자 함수에서의 this: 생성될 인스턴스 참조
단축 객체 표현과 구조 분해 할당 사용하기
const props = obj.props;
const a = props.a;
const b = props.b;
// 아래와 같이 줄이기 가능
const {props: {a, b}} = obj; // a,b는 변수지만 props는 변수선언이 아님을 주의
// 구조 분해 문법 내에서 기본값 지정 가능
const {a = 'default'} = obj.props
// 배열 구조분해 시 첫 요소 무시
const [, a, b] = [x, y, z]
// 매개변수에서도 사용 가능
point.forEach(([x, y, z]) => console.log(x + y + z));
constructor
등)이 주어지면 문제 발생use strict
넣지 않기@ts-check
로 간단히 타입 체크 동작 실험 가능하며 오류를 빠르게 잡아낼 수 있음JSDoc
주석은 중간 단계이기 때문에 공들일 필요 없음.allowJs
컴파일러 옵션allowJs
옵션 필요@types
모듈 설치로 가장 먼저 해결 권장noImplicitAny
설정으로 마이그레이션의 마지막 단계 진행noImplicitAny
를 로컬에만 설정하고 작업하는 것이 좋음stirct: true
> noImplicitAny
> strictNullChecks