카테고리 없음

타입스크립트(ZeroCho) lib,es5,d,ts 분석

stella0905 2023. 8. 10. 18:33

forEach, map 제네릭 분석

forEach

interface Array<T>{
  forEach(callbackfn:(value: T, index:number,array:T[])=>ConstrainDOMString, thisArg?:any):void;
}

[1,2,3].forEach((item)=>{console.log(item);})

1. 자바스크립트로만 생각했을때 아래 코드는 짐작해봤을때 콘솔에 1,2,3 이 찍힌다.

item이 number로 추론되어있다.

이처럼 string number, boolean을 다추론이 정상적으로 된다.

이렇게 자동으로 추론을 잘 하는 이유는 위 코드에서 

value = item

array:T[] = [true,false,true]

interface Array<T>{
  forEach(callbackfn:(value: T, index:number,array:T[])=>ConstrainDOMString, thisArg?:any):void;
}

const a: Array<number> = [1,2,3];
a.forEach((value)=>{console.log(value)})
['1','2','3'].forEach((value)=>{console.log(value);})
[true,false,true].forEach((value)=>{console.log(value);})

처음 Array<T>가 number로 타입이 정의되면 이후 T들도 number가 되고 string이면 나머지 T들도 string이된다.

 

코드를 작성할때는 어떤 타입이 올지 모르지만 실행이되면 어떤게 들어올지 알때 제네릭을 사용한다.

제네릭을 사용하지않으면

function add(x:string|number, y:string|number){}
add('1',2)
add(1,'2')

이렇게 문자열과 숫자열이 같이 나와있는걸 막을 수 없다.

그래서 제네릭을 사용해서 같은 타입끼리만 가능하게 할 수 있다.

function add<T>(x:T, y:T){retrun x}
add('1','2')
add(1,2)
add(true,false)

타입스크립트는 추론을 x, y중 타입이 있는지 확인하고 T가 number가 되면 나머지도 number로 추론한다.

직접 타입을 지정해주는건 타입스크립트가 제대로 타입을 지정해주지 못할때 사용한다.

 

map

interface Array<T> {
  map<U>(callbackfn:(value:T,index:number, array:T[])=>U, thisArg?:any):U[]
}

const strings = [1,2,3].map((value)=>value+1); // [2,3,4] number[]

 

filter 제네릭 분석

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

const filtered = [1,2,3,4,5].filter((value)=>value % 2);

⬇️

filter<number extends number>(predicate: (value: number, index: number, array: number[])=>value is number, thisArg?:any):number[];

filtered는 number로 추론된다. 고로 T는 number가 된다.

제네릭 분석은 찾기다 왜 filtered가 number인지 T를 진짜 타입으로 찾아가면 풀린다.

string인 것만 필터하고싶은데 filtered의 타입추론이 string | number로 나와서 string인것만 분리할 수 없다.

이럴경우 왜 추론을 못하는지? 추론을 잘 하게하려면 어떻게 해야하는지 생각해봐야한다.

 

  filter<S extends string | number>(predicate: (value: T, index: number, array: T[])=>value is S, thisArg?:any):string | number[];

결국에는 string | number [] 로 되기때문에 추론도 string | number가 되는거다. 

해결방법

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

// S를 원하는 타입으로 바꿔서 predicate를 똑같이 만들어준다.
const predicate = (value: string | number): value is string => typeof value === 'string';
const filtered = ['1',2,'3',4,'5'].filter(predicate);

이렇게 해주면 filtered의 타입추론은 string[]으로 나온다.

 

forEach 타입 직접 만들기

1. interface를 만든다.

2. forEach 메서드 선언을 하고 리턴을 사용안한다면 void로 처리한다.

// 1. interface를 만든다.
interface Arr {
// 2. forEach 메서드 선언을 하고 리턴값 정해진게 없다면 void로 처리한다.
  forEach():void
}

3. 콜백 자리를 만든다.

4. item의 타입 정의를 해준다.

// 1. interface를 만든다.
interface Arr {
// 2. forEach 메서드 선언을 하고 리턴값을 사용안한다면 void로 처리한다.
// 3. 콜백 자리를 만든다.
// 4. item의 타입 정의를 해준다.
  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';
})

이렇게 하면 에러는 사라진다 그치만 forEach함수는 여러가지가 들어올 수 있어서 b를 만들어보면 에러가 난다..

에러는 string형식은 number형식에 할당할 수 없다고 나온다.

interface Arr {
  forEach(callback:(item:string)=> void ):void
}

item을 string으로 변경을하면 b의 에러는 사라지지만 이번엔 a에 에러가 발생한다.

interface Arr {
  forEach(callback:(item:string | number)=> void ):void
}

이렇게 해주면 에러가 발생하지 않는다. 다만 이렇게 하면 조금만 복잡해져도 에러가 발생할 수 있다.

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

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

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';
})

제네릭을 넣어서 사용하면 에러를 없애고 타입을 지정할 수 있다.

 

 

 

map 타입 직접 만들기

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

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

b의 타입을 추론한다.

1. 함수를 만든다. 

interface Arr<T> {
  map():void
}

2. 콜백함수를 넣는다.

interface Arr<T> {
  map(callback:(v)=> void):void
}

3. 제네릭을 추가한다.

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

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

4. 여러 조건을 만족하게 수정한다.

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

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

toString의 문자 타입을 만족하기 위해 S를 추가한다.

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

const a: Arr<number> = [1,2,3];
const b = a.map((v)=> v+1);
const c = a.map((v)=> v.toString());
const d = a.map((v)=> v % 2 === 0);

만약 인덱스를 추가하게된다면 

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

const a: Arr<number> = [1,2,3];
const b = a.map((v, i)=> v+1);
const c = a.map((v)=> v.toString());
const d = a.map((v)=> v % 2 === 0);

그때그때 추가해서 수정해주면 된다.

 

여러 조건을 만족하는 타입의 map

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

const a: Arr<number> = [1,2,3];
const b = a.map((v, i)=> v+1);
const c = a.map((v)=> v.toString());
const d = a.map((v)=> v % 2 === 0);

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

 

filter 타입 직접 만들기

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

const a: Arr<number> = [1,2,3];

const b = a.filter((v)=> v % 2 === 0); // [2] number[]

 

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

const a: Arr<number> = [1,2,3];

const b = a.filter((v)=> v % 2 === 0); // [2] number[]

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

이렇게 되면 b는 타입이 정상적으로 추론되는데

문제는 d가 string| number[]가 되버린다.

interface Arr<T> {
  filter<S>(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|string> = [1,'2',3,'4',5];
const d = c.filter((v):v is string=>  typeof v === 'string') //['2','4']  string[]

타입가드를 사용하면서 결과값이 다 T가 아닌 S제네릭을 추가하면 d의 타입추론이 string[]으로 바뀐걸 볼 수 있다.

그치만 T는 S일 수 없다고 에러가 뜬다. 

넓은 타입을 좁은타입으로 줄여줄수 있는데 그럴때 <S extends T>를 사용해서 S는 T의 부분집합이라는걸 정의해서 사용할 수 있다.

interface Arr<T> {
  filter<S extends T>(callback:(v: T)=> v is S):S[];
}
const predicate = (v:string|number): v is number => typeof v === 'number'
const e = c.filter(predicate)

위 예제처럼 분리해서 사용하면 조금은 가독성이 약간 좋아보일 수 있다.

 

 

공변성과 반공변성

function a(x:string): number {
  return +x;
}
a('1'); // 1
type B = (x: string) => number | string
// 리턴값은 더 넓은 타입으로 대입이 가능하다.
const b: B = a;

리턴값은 좁은타입에서 더 넓은 타입으로 대입이 불가능하다. 넓은 타입으로만 대입 가능하다

function a(x:string | number): number {
  return +x;
}

type B = (x: string) => number ;
// 리턴값은 더 넓은 타입으로 대입이 가능하다.
const b: B = a;

매개변수는 리턴값과는 반대로 좁은타입으로 대입이 가능하고 넓은 타입으로는 대입이 불가능하다.

 

리턴값은 넓은 타입으로 대입이 가능하고 매개변수는 좁은타입으로 대입이 가능하다

 

오버로딩

같은코드를 여러번 선언하는걸 오버로딩이라 한다.

declare function add(x:number, y:number) :number
declare function add(x:number, y:number, z:number) :number

add(1,2)
add(2,3,4)

add함수호출 두번을 한번에 처리하는걸 모르겠을땐 그냥 각각 써주면된다.

한번에 해결하는 방법으로는 옵셔널을 이용하면된다.

declare function add(x:number, y:number, z?:number) :number

add(1,2)
add(2,3,4)

 

 

타입스크립트는 건망증이 심하다(+에러 처리법)

interface Axios{
  get():void
}
interface CustomError extends Error{
  response?:{
    data:any;
  }
}

declare const axios: Axios
try {
  await axios.get();
} catch (err: unknown) {
  console.error((err as CustomError).response?.data);
  //위에서 err가 뭔지 알려줬으나 바로 밑에서 또 err를 적으면 커스텀인지 알수 없다. 
  
}

as를 사용 했다면 변수에 담아서 사용하면 코드마다 as를 안넣을 수 있다.

 

interface Axios{
  get():void
}
interface CustomError extends Error{
  response?:{
    data:any;
  }
}

declare const axios: Axios
try {
  await axios.get();
} catch (err: unknown) {
  const customError = err as CustomError
  console.error(customError.response?.data);
  //위에서 err가 뭔지 알려줬으나 바로 밑에서 또 err를 적으면 커스텀인지 알수 없다. 
  customError.response.data
}

as를 사용하는건 최대한 지양해야 한다. 그리고 이렇게 사용하면 실수가 무조건 날 수도 있고 커스텀에러가 아니라 다른에러가 생길 수 도 있기 때문에 조건문을 사용해줘야 한다.

interface Axios{
  get():void
}
class CustomError extends Error{
  response?:{
    data:any;
  }
}

declare const axios: Axios
try {
  await axios.get();
} catch (err) {
  if(err instanceof CustomError)
    console.error(err.response?.data) 
    err.response?.data
}

as를 사용하지 않는게 제일 좋은 코드고 unknown이 있으면 타입가드를 사용해주는게 좋다! 

인터페이스는 자바스크립트에서 사라지기 때문에 유지시키고 싶으면 class를 사용해준다.