프론트엔드 데브코스 TIL #DAY40

에구마·2023년 11월 13일
1

📚 23.11.13

  • 제네릭
  • 제네릭 조건부 타입, infer

제네릭

재사용성을 위한 데이터 타입의 일반화

<타입변수>

function toObj<T>(a: T, b: T): { a: T; b: T } {
  return { a, b };
}

console.log(toObj<string>("A", "B"));
console.log(toObj<number>(1, 2));

여기서 T를 타입변수라고 부른다.
타입추론 덕분에 호출시에 타입변수를 명시적으로 넣어주지 않아도 된다! 하지만 여러 개의 타입 매개변수를 선언하면 해당 함수에 대한 호출은 명시적으로 제네릭 타입을 모두 선언하거나 모두 선언하지 않아야 한다.

제약조건

<T extends TYPE>

function toObj <T extends string | number>(a: T, b: T): { a: T; b: T } {
  // string과 number 만 허용한다는 조건
	return { a, b };
}

-> 함수 뿐 아니라 인터페이스, 타입 별칭, 클래스에서도 사용 가능하다!

함수에서 제네릭

function 함수명<T>(매개변수 : 매개변수타입) { }

const 화살표함수명 = <T>(매개변수 : 매개변수타입) => ;

함수가 여러 개의 타입 매개변수를 선언하면 해당 함수에 대한 호출은 명시적으로 제네릭 타입을 모두 선언하거나 모두 선언하지 않아야 합니다.

인터페이스에서 제네릭

interface 인터페이스명<T>

interface ToObj<T> {
  a: T;
  b: T;
}
function toObj <T extends string | number>(a: T, b: T): ToObj<T> {
  return { a, b };
}

타입별칭에서 제네릭

interface User<T, U , V > {
  name : T,
  age : U,
  isValid : V
}

const heropy : User<string, number, boolean> = {name :'hero', age :30, isValid :true}
const neo : User<string, number, boolean> = {name :'neo', age :55, isValid :true}

이렇게 객체 형태 말고 배열 형태로도 값을 할당하려면 ??
유니언타입 및 튜플을 이용하면된다!

inteface로도 할 수 있지만, 배열에 대해 인덱스 시그니처가 필요하므로 타입별칭이 적합하다!

type User<T,U,V> = {
  name : T, age : U, isValid : V
} | [T, U, V] // 이렇게!

const neo : User<string, number, boolean> = {name :'neo', age :55, isValid :true}
const evan : User<string, number, boolean> = ['evan', 55, false]

그럼 이렇게 객체 형태도 가능하다.

여기서 반복되고 있는 User<string,number,boolean> 이 부분도 타입별칭 제네릭으로 해결하자!

type U = User<string, number, boolean> // 이렇게
const evan : U = ['evan', 55, false]

클래스에서 제네릭

class Basket<T> {
  public items : T[] // this속성으로 접근할 수 있는 내부 속성임
  constructor(...rest : T[]) {
    this.items = rest
  }
  putItem(item : T) {
    this.items.unshift(item)
  }
  takeOutItems(count: number) {
    return this.items.splice(0, count)
  }
}

const fruitsBasket = new Basket('Apple', 'Banana', 'Cherry') // ...rest를 통해 배열 데이터로 this.items 만들어짐
fruitsBasket.putItem('Orange')
const fruits = fruitsBasket.takeOutItems(2)
console.log(fruits) // ["Orange", "Apple"]  
console.log(fruitsBasket.items) //["Banana", "Cherry"] 

const moneyBasket = new Basket(100 ,1000, 10000)
moneyBasket.putItem(40000)
const money = moneyBasket.takeOutItems(2)
console.log(money)  // [40000, 100] 
console.log(moneyBasket.items) // [1000, 10000]

제약조건, 명시적 제네릭 클래스

위의 코드에서 Basket에 인자로 모든 타입이 가능한 상태이다. 이를 string타입에 대해서만 제한하려면 다음과 같다.

class Basket<T extends string> {

하지만 string값을 주는 fruitsBasket.putItem('Orange') 에도 에러가 난다 !!!
Argument of type '"Orange"' is not assignable to parameter of type '"Apple" | "Banana" | "Cherry"’

초기화 될 때 들어온 세개의 문자로 타입정의가 되어버린 것이다!
제약조건 설정시 타입스크립트는 최대한 구체적인 타입으로 정하려고 하여, 초기화 하는 리터럴 값으로 타입이 선언되었다.

원래의 의도대로 string타입에 허용을 하려면?
명시적으로 정확히 타입을 알려주자! 호출시 명시!

const fruitsBasket = new Basket<string>('Apple', 'Banana', 'Cherry')
fruitsBasket.putItem('Orange'

제네릭의 조건부 타입

나만의 유틸리티타입을 만들거나, 내장 유틸리티 타입을 해석할때 쓰임..

삼항연산자 사용!

T extends 타입 ? 해당 타입 만족시 타입 : 해당 타입 불만족시 타입

type MyType<T> = T extends string | number ? boolean : never

const a : MyType<string> = true
const b : MyType<number> = true

const c : MyType<null> = true // string|number를 만족 못해서 never타입이 되고.타입엔 어느것도 할당ㅌ
type MyExclude<T, U> = T extends U ? never : T 
type MyUnion = string | number | boolean | null

const a : MyExclude<MyUnion, boolean | null > = 123
        // string | number | boolean | null extends boolean | null ? never : T
        // string | number -> T=string | number
        // boolean | null -> never

이렇게 특정한 타입을 만들어서 필요한 경우에 활용하는 것을 “유틸리티 타입” 이라고 부른다!

근데, 위 코드 이부분은 이미 내장에 있음 (Exclude 타입)

const a : Exclude<MyUnion, boolean | null > = 123

일반적으로 조건부 타입이 많이 등장하지 않는다. 그래서 유용함을 공감하기 어렵겠지만,,

type IsPropertyType <T, U extends keyof T, V> = T[U] extends V ? true : false

type Keys = keyof User
interface User {
  name : string,
  age : number
}

const n : IsPropertyType<User ,'name', number > = true
			// U = 'name', 'age' 
            // T[U] extends V === T['name'] 가 V타입인지 === User['name']이 V타입인지
            // U가 'name'일떈 string이기 때문에 현재 에러
  • keyof User
    User라는 객체 타입에서 키밸류의 부분만 추출해서 유니온 타입으로 만들어줌!

infer

“추론하다”. 조건부 타입에서 타입변수를 추론할 때 사용하는 키워드

꼭 infer로 만든 타입(I)를 뒤에 참:거짓 에서 무조건 사용할 필욘 없다!

  1. 타입 매개변수가 T라면, T자리에 온 타입값을 T에 대치한다. => 타입 extends (infer I).. 형태가 됨
  2. extends 앞의 타입 구조와 뒤의 infer I 의 구조가 일치하는지 확인
  3. 일치하면 참, 불일치하면 거짓
type ArrayItemType<T> = T extends (infer  I)[] ? I : never
  • 일치 예제
const numbers = [1 , 2, 3]
const a : ArrayItemType<number[]> = 123 
// 1. 대치
//type ArrayItemType<T> = **number[]** extends **(infer  I)[]** ? I : never
// 2. 일치확인
// I = number 가 될 수 있는 셈. 그래서 참으로 I , 즉 number. 그래서 123 할당 가능! const a: number

// 동일 // numbers[] === typeof numbers
const a2 : ArrayItemType<typeof numbers> = 123 // numbers[] === typeof numbers
  • 불일치 예제
const b : ArrayItemType<boolean> = 123
// 1. 대치
// type ArrayItemType<T> = boolean extends (infer  I)[] ? I : never
// 2. 일치 확인
// boolean이 (infer I)[]구조가 동일하지 않음!! 그래서 거짓 , never
// b: never 이므로 할당 불가능
  • 함수 예제
const fruits = [ 'Apple' , 'Banana', 'Cherry']
const hello = () => {}

const c : ArrayItemType<typeof fruits> = 'ABC'
// typeof fruits === string[]
// 1. 대치
// type ArrayItemType<T> = string[] extends (infer  I)[] ? I : never
// 2. 일치 확인
// I === string 이므로 참 I , 즉 string.  const c: string
  • 함수 불일치 예제
const d : ArrayItemType<typeof hello > = 'ABC'
// typeof hello === ()=>void
// d: never
  • 함수 인자에 사용된 infer
type SecondArgumentType<T> 
  = T extends (f:any, s: infer S) => any ? S : never

function hello(a: string, b: number) {}

const a : SecondArgumentType<typeof hello> = 123 // typeof hello === (a:string, b:number) => void
// 1. 대치
// type SecondArgumentType<T> 
//  = (a:string, b:number) => void extends (f:any, s: infer S) => any ? S : never
// 2. 비교
// S === number. 참이니까 number. a:number

매개변수 이름 (f,s) 는 중요하지 않아유, 반환타입(any)또한 중요하지 않았다.
거짓일 때 never혹은 null을 써도 된다.

  • 삼항연산자에 infer
// T의 제약조건이 (...args: any) => any 즉, 함수타입(()=>{})이어야함. 매개변수와 반환타입은 any인!
type MyReturnType<T extends (...args: any) => any > 
  = T extends (...args: any) => infer R ? R :any

function add(x:string , y: string) { 
  return x + y
}

const a : MyReturnType<typeof add> = 'Hello'
// typeof add === (x:string, y:string) => string
// type MyReturnType<T extends (...args: any) => any > 
//   = (x:string, y:string) => string extends (...args: any) => infer R ? R :any
// 구조 상 string이 infer R 이니까 참. R = string.   const a : string



extends

.. in … 처럼 요소 포함 확인 문법 같기도 하고, 말그대로 확장의 용도로도 쓰여서 헷갈린다!정리!

  • interface 타입1 extends 타입2
    interface Type1 {
      name : string
    }
    interface Type2 extends Type1 {
      age : number
    }
    Type1을 확장한 Type2는 Type1의 멤버 + Type2의 멤버를 모두 가진다.
  • T extends TYPE 제네릭에서의 제약조건으로 쓰인다. extends 우측의 타입들의 하위 타입이어야한다는 조건.
    function toObj <T extends string | number>(a: T, b: T): { a: T; b: T } {
    	****// string과 number 만 허용한다는 조건
    	return { a, b };
    }
    toObj의 제네릭 타입을 extends 우측의 타입(들)로만 제한한다. 즉, 제네릭 T로 전달되는 타입은 string | number의 하위 타입이어야 한다.
  • T extends TYPE ? TYPE1 : TYPE 2 위와 마찬가지로 T로 전달되는 타입이 extends 우측의 타입의 하위 타입이면 true의 타입 아니면 false의 타입

개체 메소드와 객체의 속성

interface WidhLength {
  length: number
}

const a : WidhLength = [1,2,3] // OK ...

a는 숫자배열로서 a.length //3 를 사용할 수 있다.. 이 때 위와 같이 length라는 속성을 가진 인터페이스로 타입을 지정할 수 있따 ...



🤔 오늘 회고

너 T야?

Keep

강의 들으면서 같은 부분 책도 참고. 책까지 내용을 온전히 정리하진 못했지만 중간 중간 내용을 보완할 수 있었고 이해하는데 도움을 얻었다!

코드리뷰 하나 완료! 거의 물음표 살인마가 되었다.. 남의 코드를 리뷰하면서 감탄하면서 나의 코드의 허점이 생각나는 기분.. 가히 코드리뷰의 선순환 일지도,,

죽지도 않고 살아 돌아온 우리의 사이드플젝.. 부활과 동시에 속전속결 ㅋㅅㅋ! 그동안 이리저리 치인게 다 근육이 되었나보다 우리 팀 좀 성장한걸지도 ㅎ

Problem

이상하리만큼 시작부터 컨디션이 안좋았다. 의욕없다는 걸 포장하는 말일지도.. 강의 듣는 내내 졸려서 혼났다ㅠ 그래서 정리하고 차라리 코드리뷰하고 그 외 할일을 했다.
핑계를 찾아보자면 주말에 맘편히 못쉬었다. 전속력도 아니고 그렇다고 쉬는 것도 아니고 애매하게 주말을 다 보내서 다시 열올리기가 힘들었다. 밸런스 조절을 잘하자

Try

다시 의욕 챙기기,, 방학 전날이니까 더 화이팅!

코드리뷰 두개, 코드리뷰 하면서 적어놓은 질문들 정리하기

profile
코딩하는 고구마 🍠 Life begins at the end of your comfort zone

0개의 댓글