저번주 주말에 공부했는데 이번주에 일하다 써먹을 일이 생겨서 얼른 도입하고
정리하려고 씀
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 키도 동적으로 만들 수 있을 거 같음......
'TIL' 카테고리의 다른 글
es5은 어떻게 브라우저에서 동작했을까 & UMD (0) | 2024.07.28 |
---|---|
tanstack Query v5 (0) | 2024.07.28 |
jest 2 테스트 종류 (1) | 2024.07.21 |
타입스크립트의 추론 (1) | 2024.07.20 |
새로 나온 hook useFormStatus 등 리액트 동향 살펴보기 (0) | 2024.07.02 |
댓글