Today I Learned_230314

5 분 소요

TypeScript

인프런 타입스크립트 강의 수강

filter 분석

같은 함수가 여러가지 방법으로 선언되는 경우에는 여러 타입으로 선언되는 경우가 있음

→ filter는 unknown을 받고있어서 타입 추론을 제대로 못 하는 경우도 있다.

제네릭의 경우 한 칸 씩 진짜 타입으로 채워가면서 분석해 나가자

interface Array<T>{
    filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
    filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
}

// value는 Number
// number를 2로 나눈 나머지 - number이므로 S는 number라고 추론 가능
const filtered = [1, 2, 3, 4, 5].filter((vaule) => vaule % 2)

타입 채워나가면 아래와 같다.

filter<number extends number>(predicate: (value: number, index: number, array: number[]) => value is number, thisArg?: any): number[];
// filter(predicate: (value: string | number, index: number, array: string | number[]) => unknown, thisArg?: any): string | number[];
// 결과가 string | number[] 니까 string | number로 나오게 되는 상황. 
const filtered1 = ['1', 2, '3', 4, '5'].filter((value) => typeof value === 'string')// ['1','3', '5'] string[]

// string extends string | number
constpredicate= (value: string|number): value is string => typeof value === 'string'
const filtered2 = ['1', 2, '3', 4, '5'].filter(predicate);

// 아래꺼는 에러가 난다.
// <string extends string | number> -> 직접 기입. T는 정해져 있기 때문에 extends를 붙지 않아도 된다.
// 커스텀 타입 가드가 아니기 때문에 되지 않음
// const result = ['1', 2].filter<string extends string | number>((value) => typeof value === 'string');
//(value) => typeof value === 'string' 이게 형식 조건자에 맞지 않는 상황.

const result = ['1', 2].filter((value) => typeof value === 'string');
  • 결과는 string[]이어야 하는데 filtered1는 string|number가 된다
    • 추론을 잘못한 것. 추론을 잘 하도록 해야한다.
    • string[]으로 나오게 하려면 이에 맞게 바꾸어 줘야 한다.
  • predicate를 별도로 빼자. 이 순간 S는 string이라고 알려준다.

forEach 타입 만들기

타입을 제대로 만들 줄도 알아야 함.

a.forEach((item) => {
    console.log(item);
});

a.forEach((item) => {
    console.log(item);
    return '3';
})

위 형태를 바탕으로 forEach 함수 구현

인터페이스 선언 → 함수 선언 → callback 추가

에러 찾아가는 방법 : 마지막 줄 부터 보고 천천히 위로 올라가면서 찾아나가자.

Untitled

interface Arr {
    forEach (callback: (item: number) => void) : void;  // 모든 타입을 커버할 수 없음
}

// --------여기는 가능
const a : Arr = [1, 2, 3];
a.forEach((item) => {
    console.log(item);
});

a.forEach((item) => {
    console.log(item);
    return '3';
})

// --------여기는 불가능
const b : Arr = ['1', '2', '3']
b.forEach((item) => {
    console.log(item);
});

b.forEach((item) => {
    console.log(item);
    return '3';
})
interface Arr {
    forEach (callback: (item: string | number) => void) : void; 
}

const a : Arr = [1, 2, 3];
a.forEach((item) => {
    console.log(item);
    item.toFixed(1); //가능해야 하는데 현재 구조로는 불가
});

a.forEach((item) => {
    console.log(item);
    return '3';
})

const b : Arr = ['1', '2', '3']
b.forEach((item) => {
    console.log(item);
    item.charAt(3); //가능해야 하는데 현재 구조로는 불가
});

b.forEach((item) => {
    console.log(item);
    return '3';
})

일단 에러가 나오지 않으면 성공한것. 근데 복잡하게 만들다 보면 나중에 가서야 잘못된 것을 발견하게 되는 경우도 있음..

또는 관계로 표현이 되지 않음 → 제네릭 사용

interface Arr {
    forEach<T> (callback: (item: T) => void) : void;
}

이러면 제네릭 자리를 추론을 하지 못해 아래와 같이 명시해주어야 함

a.forEach<number>((item) => {
    console.log(item);
    item.toFixed(1);
});

아래와 같이 수정 진행

interface Arr<T> {
    forEach (callback: (item: T) => void) : void;
}

const a : Arr<number> = [1, 2, 3];
a.forEach((item) => {
    console.log(item);
    item.toFixed(1);
});

a.forEach((item) => {
    console.log(item);
    return '3';
})

const b : Arr<string> = ['1', '2', '3']
b.forEach((item) => {
    console.log(item);
    item.charAt(3);
});

b.forEach((item) => {
    console.log(item);
    return '3';
})

원본 forEach

forEach(callbackfn: (value: number, index: number, array: Int8Array) => void, thisArg?: any): void;

구현부에서는 index를 사용하고 있지 않으므로 생략.

실제 구현 코드 발전도에 따라 타입을 발전해 나가자. 처음부터 모든 것을 예상하려 하지 말자.

map 타입 만들기

interface Arr<T> {
    map (callback: (v: T) => T) : T[];
}

const a : Arr<number> = [1, 2, 3];
const b = a.map((v) => v + 1); // [2, 3, 4]

그런데 위와 같이 하면 아래 경우가 안 된다.

const c = a.map((v) => v.toString()); // ['2', '3', '4']

v.toString에서 TS2322: Type ‘string’ is not assignable to type ‘number’. 발생

또한 리턴값이 string[]이 되어야 하는데 c의 타입을 보면 number[]로 되어 있음

새로운 제네릭 문자를 추가하면 된다.

interface Arr<T> {
    map<S> (callback: (v: T) => S) : S[];
}

const a : Arr<number> = [1, 2, 3];
const b = a.map((v) => v + 1); // [2, 3, 4]
const c = a.map((v) => v.toString()); // ['2', '3', '4'], string[]
const d = a.map((v) => v % 2 === 0); //[false, true, false], boolean[]

const e: Arr<string> = ['1' ,'2', '3'];
const f = e.map((v) => +v);

filter 타입 만들기

interface Arr<T> {
    filter (callback: (v: T) => v is T) : T[];
}

const a : Arr<number> = [1, 2, 3];
const b = a.filter((v) : v is number => v % 2 === 0);  // [2], number[]

const c : Arr<number &#124; string> = [1, '2', 3, '4', 5];
const d = c.filter((v) : v is string => typeof v === 'string'); // ['2', '4'], string[]

커스텀 타입 가드를 사용해도 d는 여전히 number|string형식

T타입이 S타입으로 변화하려면 extends를 써서 확장 (부분집합 관계이므로)

커스텀 타입 가드(형식 조건자)는 넓은 타입을 좁은 타입으로 좁혀주는 역할을 한다.

interface Arr<T> {
    filter<S extends T> (callback: (v: T) => v is S) : S[];
}

const a : Arr<number> = [1, 2, 3];
const b = a.filter((v) : v is number => v % 2 === 0);  // [2], number[]

const c : Arr<number &#124; string> = [1, '2', 3, '4', 5];
const d = c.filter((v) : v is string => typeof v === 'string'); // ['2', '4'], string[]
interface Arr<T> {
    filter<S extends T> (callback: (v: T) => v is S) : S[];
}

const a : Arr<number> = [1, 2, 3];
const b = a.filter((v) : v is number => v % 2 === 0);  // [2], number[]

const c : Arr<number &#124; string> = [1, '2', 3, '4', 5];
const d = c.filter((v) : v is string => typeof v === 'string'); // ['2', '4'], string[]

//가독성을 높이려면 바깥으로 뺌.
//그런데 타입스크립트는 인라인으로 쓰나 바깥으로 쓰나 가독성이 안 좋긴 하다..
const predicate = (v : string &#124; number) : v is number => typeof v === 'number';
const e = c.filter(predicate); // [1, 3, 5] number[];

lib.es5.d.ts를 안 보고 만들어내는 연습을 할 수 있으면 좋다.

카테고리:

업데이트: