오늘은 리액트를 사용할 때 좋은 타입스크립트 개념들에 대한 내용들이 많다
any
대신 unknown
활용unknown
활용 시 type narrowing을 해주어야 하는데, 이를 통해 타입의 범위를 의도했던 대로 좁힐 수 있게 된다.
(*unknown
과 반대되는 타입이 never
인데, 어떠한 타입도 들어올 수 없는 경우에 사용한다.)
조건문과 함께 타입 가드를 사용해 타입을 좁혀서 변수나 함수를 사용함
특정 요소의 자료형을 확인하는 typeof
외에도 지정한 인스턴스가 특정 클래스의 인스턴스인지 확인하는 instanceof
,
어떤 객체에 해당 key가 존재하는지를 확인하는 in
도 있다.
제네릭을 잘만 활용하면 좋을 것 같다는 생각을 최근 작성된 어드민쪽 코드를 보면서 하고 있었다.
제네릭은 함수나 클래스 내에서 단일 타입이 아닌 다양한 타입에 대응할 수 있도록 도와주는데,
타입은 다르지만 비슷하게 다른 작업을 하는 컴포넌트들을 하나의 제네릭으로 선언해 간단하게 작성할 수 있게 된다.
//제네릭 활용하지 않은 경우
function getfirstAndLast(list:unknown[]){
return [list[0],list[list.length-1]
}
const [first, last] = getFirstAndLast([1,2,3,4])
first//unknown
last//unknown
//제네릭 활용
function getFirstAndLast<T>(list:T[]):[T,T]{
return [list[0],list[list.length-1]
}
//이 경우 first, last 의 타입은 number
const [first,last] = getFirstAndLast([1,2,3,4])
//이 경우 first, last의 타입은 string
const [first, last] = getFirstAndLast(['a','b','c'])
직접 작성하는 함수가 아닌 useState에도 제네릭으로 타입을 선언해줄 수 있는데, 종종 useState를 사용할 때 initialState를 넘기지 않은 상태로 사용하면 값을 undefined로 추론하는 문제가 생긴다.
복수의 제네릭을 사용할 수도 있는데, 이 경우에는 제네릭의 의미를 알 수 있는 네이밍을 하는 것이 좋다.
function multiple<First,Last>(a1:First,a2:Last):[First,Last]{
return [a1,a2]
}
const [a,b] = multiple<string,boolean>('true',true)
객체의 키를 정의하는 방식을 인덱스 시그니처라고 한다
[key:string]:string
과 같이 작성하는데 키의 범위를 지금과 같이 string으로 작성하면 범위가 너무 넓아지기 때문에 동적으로 선언되는 경우는 지양하고, 필요에 따라 좁혀서 사용해야 한다.
//객체의 키 좁히기
//1.Record 사용 (객체의 key, value 지정함)
type Hell = Record<'fire'|'devil',string>
const hell : Hell = {
fire: 'fire',
devil: 'devil'
}
//2. 타입을 사용
type Hell = {[key in 'fire'|'devil']:string}
위와 같이 작성한 뒤 Object.keys
를 이용해 키를 배열로 뽑아내고, 뽑아낸 배열을 순회하면서 hell이라는 객체의 value를 반환한다면 에러가 발생한다
//Element implicitly has an 'any' type
//because expression of type 'string' can't be
//used to index type 'Hell'
//No index signature with a parameter of type
//'string' was found on type 'Hell'
Object.keys
는 string[]
을 반환하는데 이 string은 hell의 인덱스 키로 접근할 수가 없다 (hell의 인덱스 시그니처는 'fire'|'devil'이니까)
이런 에러를 해결하는 방법은
//1.as - 타입 단언
Object.keys(hell) as Array<keyof Hell>).map((key) =>{
const value = hell[key]
return value
{
//2.타입 가드 함수 KeyOf만들기
//*Array.from은 순회 가능한, 유사 배열 객체에서 얕게 복사된 새로운 인스턴스를 생성하는데, 두번째 인자로 매 요소마다 호출할 함수를 받는다 (map 함수와 유사한 형태)
function keyOf<T extends Object>(obj:T):Array<keyof T>{
//가져온 배열에 대해서도 타입 단언 처리
return Array.from(Obejct.keys(obj)) as Array<keyof T>
}
//keyOf 사용하기
keyOf(hell).map((key) => {
const value = hell[key]
return value
})
//3. 가져온 키에 대해 타입 단언 처리하기
Obejct.keys(hell).map((key) => {
const value = hell[key as keyof Hell]
return value
})
타입스크립트의 핵심 원칙 중 하나로 타입 검사의 초점이 '값의 형태'에 맞춰져 있음을 뜻한다
오리처럼 걷고, 헤엄치고, 소리내면 그것이 무엇이든 오리라고 부를 수 있는 것처럼 어떤 객체가 필요한 변수와 매서드만 지닌다면 해당 타입에 속한다고 인정해준다
위 인덱스 시그니처 예시에서 Object.keys
가 반환하는 값은 제네릭으로 정해진게 아니라 string[]
으로 고정되어 있었다
이는 자바스크립트가 객체 타입에 열려있기 때문에 타입스크립트 역시 모든 키가 들어올 수 있는 가능성에 대응하기 위해 포괄적으로 지정해둔 것