본문 바로가기
TIL

타입스크립트 never, 컨디셔널 타입

by 은지:) 2024. 7. 27.
728x90
반응형

저번주 주말에 공부했는데 이번주에 일하다 써먹을 일이 생겨서 얼른 도입하고

정리하려고 씀

 

 

never는 어떠한 타입도 대입할 수 없음

 

그럼 언제 never가 뜨는지....

 

함수가 정상적으로 완료되지 않거나, 반환할 가능성이 전혀 없는 경우에 사용됨

예를 들어, 무한 루프나 예외를 던지는 경우임

 

근데 선언형일때랑 표현식 쓸 때랑 타입을 다르게 추론함

 

표현식일 때는 void()로 추론함

never가 더 맞는 거 아닌가? 해서 never 넣으면

never는 어떠한 타입도 넣을 수 없기 때문에 타입 오류 뜸

 

 

이렇게 쓰면 됨

 

근데 저런 코드가 많진 않을 거 같음

그럼 왜 never를 쓸까 뭐가 좋아서 ???

 

 

  • 코드의 명확성: never 타입을 사용하면 이 함수가 정상적으로 반환되지 않는다는 것을 명확하게 나타낼 수 있습니다. 이는 함수의 동작을 이해하는 데 도움을 줍니다.
  • 타입 체커의 강력한 검사: never 타입을 사용하면 TypeScript의 타입 체커가 더 강력한 검사를 수행할 수 있습니다. 예를 들어, never 타입의 값을 사용하려고 하면 컴파일 타임에 오류를 발생시켜 잠재적인 버그를 미리 방지할 수 있습니다.
  • 오류 핸들링: 오류를 던지는 함수는 never 타입을 반환해야 합니다. 이렇게 하면 해당 함수가 오류를 던지는 경우를 명확히 하고, 다른 코드가 이를 적절하게 처리하도록 강제할 수 있습니다.

 

 

 

그러니까 never는 다른 타입을 넣지 못하는 점을 이용해서

타입 체커의 역할을 시키는 거 같음

 

 

여기저기 사용되는 거 같은데

컨디셔널 타입에도 사용할 수 있음

 

 

밑 간단한 예시

type Start = string | number;
type New = Start extends string | number ? Start[]:never;
let n: New = ["hi"]

n = [123]


console.log(n)

 

 

 

extends 기능 이용해서 내가 원하는 게 있는지 찾는 거임

 

이해를 위한 예시 안의 예시

 

 

아무튼 이때 Start에는 string | number가 있기 때문에 무조건 Start[]로 넘어감

 

만약 Start가 boolean이었다면

 

 

never 형식 되어서 에러 뱉음

딱 타입 체커의 느낌....

 

 

제네릭 사용하면 이렇게 쓸 수도 있음

근데 써놓고 보니 굳이템...

 

 

간단한 예시라서 저렇게 쓰는거지

물론 저렇게 쓰진 않음

 

그냥 이렇게 쓸 듯

interface Test {
    0: string;
    1: number;
    // length: 2;
}

type Test = (number| string)[]

let n2: Test = ["hi",123]

 

 

 

음.............

never와 컨디셔널 타입이 의미가 있을 땐 제네릭과 함께 쓸 때임

 

참고로 모든 타입들은 never를 포함할 수 있기 때문에 이건 무조건 true임

 

 

 

1. 원래 있던 타입 중에 하나의 타입을 지우고 싶을 때

 

타입 키가 never면 속성은 제거되는 걸 이용함

 

 

1-1. never 을 쓰는 건 아니지만 type을 돌려서 다시 재조정하는 건 많이 쓰이는 거 같음

type ReadonlyObject<O> = {
  readonly [K in keyof O]: O[K];
};

interface Person {
  name: string;
  age: number;
}

const person: ReadonlyObject<Person> = {
  name: "John",
  age: 30
};

 

이런 식으로.... 예시는 몽땅 readOnly 붙이고 싶을 때

 

 

 

 

2. never를 사용한 타입 체크 + 제네릭 + 컨디셔널 타입 분배 법칙

 

 

하고 싶은 거

 

1. Start라는 타입이 있음
2. n 이라는 변수는 여기서 string 값만 뽑아서 쓰고 싶음

 

그럴 때 이렇게 쓰면 됨

 

Key에 string | number가 들어가서

느낌상.... number도 되는 거 아닌가? 하는 생각이 듦

그 예시가 n1 변수임

 

Start가 유니언 타입이기 때문에 분배법칙이 일어나고, 좀 다르게 해석해야 함

Result<Start>
= Result<string | number>
= (string extends string ? string[] : never) | (number extends string ? number[] : never)
= string[] | never
= string[]

 

이렇게... ㅎㅎ

never는 아무 타입도 포함하지 않으니까 없는 거나 마찬가지라 결국 string[]만 남게 됨

 

 

음...... 이해는 했는데

그럼 이걸 나는... 어떨 때 쓰는지.... 한번 찾아봄

 

 

2-1. 값이 string 일 땐 string[], number일 땐 number[]를 내보내고 싶을 때

type InputType = string | number;
type ProcessedType<T> = T extends string ? T[] : T extends number ? T[] : never;

 

 

2-2. 데이터 형태에 따라 다른 컴포넌트를 반환해야할 때

type ApiResponse = { kind: 'user'; name: string; age: number } | { kind: 'product'; title: string; price: number };

type ResponseHandler<T> = T extends { kind: 'user' }
  ? { kind: 'user'; name: string; age: number }
  : T extends { kind: 'product' }
  ? { kind: 'product'; title: string; price: number }
  : never;

 

kind 반환값에 따라서 타입 분기처리임

 

 

 

 

3. 분배 법칙 없었으면 좋겠음 

 

boolean 은 어차피 false 아니면 true기 때문에 저렇게 됨

저게 싫다면

 

이렇게 []로 감싸주면 됨

 

 

사용성 있는 예시로는

 

위는 무조건 false 나와야 하는데....

boolean 나옴

 

이유는 분배 법칙 일어나서임

= IsString<'hi'> | IsString<34>
= true | false
= booelan

 

 

그래서 요렇게 쓰는 거임

 

['hi | 3] 이 [string] 을 extends하는지 검사함

이건 무조건 false임

 

 

 

 

4. never의 분배법칙

 

never도 분배법칙 대상이 됨

근데 결과물은 항상 never가 나옴

 

never는 공집합임

공집합에서 분배법칙 실행해봤자 아무것도 실행하지 않는 것과 똑같음

그러다보니 그대로 never를 뱉음

 

이것도 마찬가지로 막으려면 제네릭과 never를 []로 감싸야함

 

 

 

----------------------------------------------------

 

 

정리하자면

 

never는 어떠한 타입도 담을 수 없음

그러다보니 사용하지 않는, 반환하지 않는 것들을 never로 쓰는 게 좋고

선언형 / 표현식 함수에서는 유의하는 게 좋음

 

제네릭과 함께 타입 체크할 때도 쓰는데 이때는 분배법칙을 잘 써야함

 

&

 

컨디셔널 타입은

 

말 그대로 타입 안에서 삼항자 쓰는 거임

extends 문법을 사용해서 씀

 

예시로 이번에 적용한 코드는 이렇게 생김...

상태 라이브러리에 적용한 건데

 

1. state, setState 여러 종류를 띄우고 싶었음

2. 타입이 키가 똑같고 비슷해서 중복을 피하고 싶었음

3. 대신 state 값은 어떤 건 Date, 어떤 건 키와 값이 같은 객체였음

4. state, setState도 앞에 set 붙고 첫글자 대문자로 바꾸는 것 빼고는 동일해서 이것도 만들고 싶었음

 

그래서

 

1. 가장 작은 state 단위를 Keys 에 담음

2. state 가 객체로 이루어진 것들(이것도 키가 같았음) ComplexKeys로 분리

3. 업데이트 쳐주는 함수들을 Function에 담음

 

이걸 조합해서 만듦

 

1. state, setState 를 각각 X,Y 로 분리해 type안에 교차타입(&)을 사용해서 합침

2. X => in K 로 키를 돌리면서 ComplexKeys 안에 있다면 state가 객체타입, 아니라면 Date를 반환

3. Y => Capitalize를 사용해 set + State 로 만들어서 Function 안에 있다면 객체 타입 반환, 아니라면 Date 반환하는 함수 반환함

 

 

type ComplexKeys = 'placeDate' | 'keywordDate' | 'keywordGroupDate';

type TDashBoardDateFilter<
  K extends string,
  Y extends string,
  ComplexKeys extends string,
> = {
  [key in K | ComplexKeys]: key extends ComplexKeys
    ? { startDate: Date; endDate: Date }
    : Date;
} & {
  [key in Y]: key extends `set${Capitalize<ComplexKeys>}`
    ? (date: { startDate: Date; endDate: Date }) => void
    : (date: Date) => void;
};

type Keys =
  | 'adDateChannelDate'
  | 'placeDate'
  | 'keywordDate'
  | 'keywordGroupDate';

type Functions =
  | 'setAdDateChannelDate'
  | 'setPlaceDateStartDate'
  | 'setPlaceDateEndDate'
  | 'setKeywordDateStartDate'
  | 'setKeywordDateEndDate'
  | 'setKeywordGroupDateStartDate'
  | 'setKeywordGroupDateEndDate';

const initialValue = new Date();

const useDashBoardStore = create<
  TDashBoardDateFilter<Keys, Functions, ComplexKeys>
>()(

 

 

지금 보니까 Function 키도 동적으로 만들 수 있을 거 같음......

728x90
반응형

댓글