ABOUT ME

개발공부를 시작하면서 서툰 공부일지를 써보는 그런 공간

Today
Yesterday
Total
  • 타입스크립트(ZeroCho) lib,es5,d,ts 분석
    카테고리 없음 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를 사용해준다.

     

Designed by Tistory.