You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
타입스크립트의 never는 다른 타입들만큼 아주 흔하거나 무시할 수 없는 타입이 아니기 때문에 그다지 활발하게 논의되는 타입이 아닙니다. 심화된 타입을 다루거나 까다로운 타입 에러 메시지를 읽을 때나 등장하기 때문에 초급자들은 아마 never 타입을 무시하고도 타입스크립트를 쓸 수 있습니다.
never 타입은 타입스크립트에서 꽤 좋은 유즈 케이스를 가지고 있습니다. 그러나, 그만큼 여러분들이 조심해야 하는 위험들도 갖고 있습니다.
이 글에서는 다음 내용을 다룹니다.
never 타입의 의미와 필요성
never 타입의 응용과 위험
여러 말장난들 🤣
never 타입이 무엇일까?
never 타입과 이것의 목적을 완벽하게 이해하기 위해서는, 타입이란 무엇인지 그리고 타입 시스템에서 타입이 어떤 역할을 하는지 먼저 이해해야 합니다.
타입은 가능한 값들의 집합과 같습니다. 예를 들어 string 타입은 가능한 문자열의 무한집합을 나타냅니다. 우리가 변수에 string 타입이라고 붙여줄 때, 그 변수는 문자열 집합 내에서만 값을 가질 수 있게 됩니다.
letfoo: string='foo'foo=3// ❌ 숫자는 문자열 집합에 속하지 않습니다.
타입스크립트에서 never는 값들의 공집합을 의미합니다. 실제로 또다른 유명한 자바스크립트 타입 시스템인 Flow에서도 never와 동일한 타입을 empty라고 부른답니다.
never 타입은 합집합(union)과 교집합(intersection)에서 어떻게 동작할까?
덧셈, 곱셈에서 0이 동작하는 방식과 유사하게, never 타입은 union 타입과 intersection 타입에서 사용되는 특별한 프로퍼티를 가집니다.
never와 합집합을 하게 되면 never는 제외됩니다. 어떤 숫자에 0을 더했을 때 그 숫자가 그대로인 것과 같은 원리입니다.
예시) type Res = never | string // string
never와 intersection 하게 되면 다른 타입을 오버라이딩합니다. 어떤 숫자에 0을 곱하면 0이 되는 것처럼 말입니다.
예시) type Res = never & string // never
never 타입이 가지는 두 가지 동작/특성은 나중에 보게 될 몇몇 가장 중요한 유즈 케이스의 기반을 마련해줍니다.
어떻게 never 타입을 사용할까?
아마 여러분들은 스스로 never를 많이 사용하진 않았겠지만, 이미 꽤 잘 사용되는 유즈 케이스들이 존재합니다.
허용되지 않는 함수 매개변수를 제한하기
어떤 값에 never 타입을 할당할 수 없기 때문에 우리는 다양한 응용을 위해 함수에 제한을 가하는 방식으로 never를 사용할 수 있습니다.
switch문과 if-else문에서 완전히 일치하는지 보장
어떤 함수가 오직 never 타입인 하나의 매개변수만 받는다면 그 함수는 never가 아닌 값과 함께 호출될 수 없습니다.
functionfn(input: never){}// `never`만 허용declareletmyNever: neverfn(myNever)// ✅// 다른 걸 전달하면 타입 에러 발생fn()// ❌ 'input`에 대한 인자가 주어지지 않음.fn(1)// ❌ number 타입은 never에 할당할 수 없음fn('foo')// ❌ string 타입은 never에 할당할 수 없음// 심지어 any도 불가능declareletmyAny: anyfn(myAny)// ❌ any 타입은 never에 할당할 수 없음
이러한 기능을 사용하여 switch문과 if-else문에서 완전히 일치하도록 보장할 수 있습니다. 남은 타입은 반드시 never 타입이므로 default 케이스로 never 타입을 사용하면 모든 경우가 커버될 수 있기 때문입니다. 실수로 일치하는 항목을 누락하면 타입 에러가 발생합니다.
functionunknownColor(x: never): never{thrownewError("unknown color");}typeColor='red'|'green'|'blue'functiongetColorName(c: Color): string{switch(c){case'red':
return'is red';case'green':
return'is green';default:
returnunknownColor(c);// Argument of type 'string' is not assignable to parameter of type 'never'}}
구조적인 타이핑을 부분적으로 비허용
VariantA 또는 VariantB를 가지는 파라미터를 받는 함수가 있다고 가정해봅시다. 그러나 사용자가 두 유형의 모든 프로퍼티를 포함하는 타입을 전달해서는 안되는 경우입니다.
파라미터로 유니온 타입인 VariantA | VariantB을 사용할 수 있습니다. 그러나 타입스크립트에서 타입 호환성은 구조적인 서브타이핑을 기반으로 하고 있기 때문에 함수의 파라미터의 타입보다 더 많은 프로퍼티를 가지는 객체를 전달하는 것은 허용되지 않습니다. (객체 리터럴을 전달하지 않는 한)
typeVariantA={a: string,}typeVariantB={b: number,}declarefunctionfn(arg: VariantA|VariantB): voidconstinput={a: 'foo',b: 123}fn(input)// TypeScript가 안된다고 하진 않지만 우리는 이를 허용하지 않아야 합니다.
위의 코드 스니펫에서 타입스크립트는 타입 에러를 발생시키지 않습니다. never를 사용함으로써 부분적으로 구조적 타이핑을 불가능하게 하고 사용자들이 두 프로퍼티를 다 가지는 객체를 전달하지 못하게 할 수 있습니다.
typeVariantA={a: stringb?: never}typeVariantB={b: numbera?: never}declarefunctionfn(arg: VariantA|VariantB): voidconstinput={a: 'foo',b: 123}fn(input)// ❌ Types of property 'a' are incompatible
어떠한 이유든지 간에 get 메소드를 통해 데이터를 읽기만 가능한 read-only 캐시를 가지고 싶어질 수도 있습니다. put 메소드 인자에 never 타입을 추가하게 되면 함수 내부로 어떠한 값이든 받을 수 없게 됩니다.
declareclassReadOnlyCache<R>extendsMyCache<never,R>{}// Now type parameter `T` inside MyCache becomes `never`constreadonlyCache=newReadOnlyCache<Read>()readonlyCache.put(data)// ❌ Argument of type 'Data' is not assignable to parameter of type 'never'.
부가적으로 never 타입과는 관련없지만, 이는 파생 클래스의 좋은 예시가 아닐 수 있습니다. 저는 객체 지향 프로그래밍의 전문가가 사실 아니기 때문에 각자 알아서 판단해주시길 바랍니다.
이론적으로 닿을 수 없는 조건 분기들을 나타내기
조건 타입 내에서 추가적인 타입 변수를 생성하기 위해 infer를 사용할 때 우리는 모든 infer 키워드에 반드시 else 분기를 추가해야 합니다.
typeA='foo';typeB=Aextends infer C ? (Cextends'foo' ? true : false// inside this expression, C represents A) : never// 이 분기점에 닿을 수 없겠지만 생략할 순 없습니다
왜 extends infer 콤보가 유용한가요?
이전에 작성한 글에서 "로컬 (타입) 변수"를 선언하는 방식과 함께 extends infer를 언급했었습니다. 아직 읽지 않았다면 여기를 참고하세요.
유니온 타입에서 유니온 멤버 분리하기
불가능한 분기를 나타내는 것 외에도, never는 조건 타입에서 원치않는 타입을 분리해내는 데 사용할 수 있습니다.
이전에도 얘기했듯이, 유니온 멤버로써 never를 사용하면 자동적으로 제거된다고 했습니다. 즉, 유니온 타입에서 never는 쓸모가 없는 것이죠.
어떤 기준점을 기반으로 유니온 타입에서 유니온 멤버를 선택하는 유틸리티 타입을 작성할 때 never 타입의 쓸모없음은 else 분기에 완벽한 타입을 두게끔 만들어줍니다.
foo 문자열 리터럴을 가지는 name 프로퍼티를 추출하고, 그 외에는 필터링하는 유틸리티 타입인 ExtractTypeByName을 작성한다고 해볼까요.
typeFoo={name: 'foo'id: number}typeBar={name: 'bar'id: number}typeAll=Foo|BartypeExtractTypeByName<T,G>=Textends{name: G} ? T : nevertypeExtractedType=ExtractTypeByName<All,'foo'>// the result type is Fo
실제로 어떻게 동작하는지 자세히 살펴봅시다.
다음은 타입스크립트가 결과 타입을 평가하고 가져오는 각 단계들입니다.
never의 응용 사례보다는 타입스크립트 언어의 동작/특징에 더 가깝다고 느껴질 수도 있습니다. 그럼에도, 이 점은 여러분이 마주칠 수도 있는 몇 가지 암호 에러 메시지를 이해하는 데 아주 중요합니다.
공존할 수 없는 타입들끼리 교집합 연산을 하면 never 타입을 얻을 수 있습니다.
typeRes=number&string// never
또한, 어느 타입이든지 never로 교집합 연산을 하면 never를 얻겠죠.
typeRes=number&never// never
이건 객체 타입에서 더 복잡합니다.
객체 타입을 교차하게 되면, 프로퍼티 타입이 판별 프로퍼티인지 아닌지에 따라(리터럴 유형 또는 리터럴 유형의 유니온 포함), 전체 타입을 `never`로 좁힐 수도 있고 아닐 수도 있습니다.
이 예시에서 오직 `name` 프로퍼티만 `never`타입이 됩니다. `string`과 `number`는 공존할 수 없기 때문이죠
여러분이 명시적으로 never라고 타입을 지정하지 않으면, 예상치 못한 never 타입과 함께 에러 메시지를 마주하게 될 수도 있습니다. 이는 기본적으로 타입스크립트 컴파일러가 타입들을 교차하기 때문입니다. 타입의 안전성을 지키고 건전성(soundness)을 보장하기 위해 암묵적으로 수행되는 작업입니다.
typeReturnTypeByInputType={int: numberchar: stringbool: boolean}functiongetRandom<Textends'char'|'int'|'bool'>(str: T): ReturnTypeByInputType[T]{if(str==='int'){// generate a random numberreturnMath.floor(Math.random()*10)// ❌ Type 'number' is not assignable to type 'never'.}elseif(str==='char'){// generate a random charreturnString.fromCharCode(97+Math.floor(Math.random()*26)// ❌ Type 'string' is not assignable to type 'never'.)}else{// generate a random booleanreturnBoolean(Math.round(Math.random()))// ❌ Type 'boolean' is not assignable to type 'never'.}}
이 함수는 전달받는 인자에 따라 숫자, 문자열, 불리언을 리턴합니다. 리턴 값에 해당하는 타입을 가져오기 위해 ReturnTypeByInputType[T]를 사용합니다. 그러나 모든 리턴문에서 Type X is not assignable to type 'never'라는 타입 에러가 발생합니다. 분기에 따라 X는 string, number, boolean이 됩니다.
타입스크립트는 우리 프로그램이 문제를 일으킬 가능성을 줄여주기 위해 도움을 주려고 하기 때문에 발생합니다. 각 리턴 값은 ReturnTypeByInputType[T]에 할당할 수 있어야 하는데, 이 값은 런타임 때 number, string, boolean이 될 수 있습니다.
리턴 타입이 모든 가능한 ReturnTypeByInputType[T]에 할당할 수 있다고 보장할 수 있을 때 타입 안정성이 지켜집니다. 즉 number, string, boolean의 intersecion일 때가 그런 경우지만 이는 서로 공존할 수 없는 타입이기 때문에 never가 되는 것이죠. 그래서 에러 메시지에 never를 보게 되는 겁니다.
이를 해결하려면, 여러분은 타입 단언(type assertions) (또는 함수 오버로딩)을 사용해야만 합니다.
return Math.floor(Math.random() * 10) as ReturnTypeByInputType[T]
return Math.floor(Math.random() * 10) as never
다른 분명한 예시가 있을 수 있습니다.
functionf1(obj: {a: number,b: string},key: 'a'|'b'){obj[key]=1;// Type 'number' is not assignable to type 'never'.obj[key]='x';// Type 'string' is not assignable to type 'never'.}
obj[key]는 런타임에 key의 값에 따라 string 또는 number가 됩니다. 이러한 제약조건이 더해진 타입스크립트는 타입의 안정성을 위해 string과 number 두 타입 모두 공존가능한 값만 쓰게끔 합니다. 그래서 두 타입의 교차된 타입인 never 타입이 전달되는 것입니다.
never 확인하기
어떤 타입이 never이어야 하는지 확인하는 것보다 never인지 확인하는 것이 더 어렵습니다. 다음 코드 예시를 봅시다.
typeIsNever<T>=Textendsnever ? true : falsetypeRes=IsNever<never>// never 🧐
Res는 true 또는 false가 될까요? 놀랍게도 둘 다 정답이 아닙니다. 실제로는 never가 됩니다.
타입스크립트의
never
는 다른 타입들만큼 아주 흔하거나 무시할 수 없는 타입이 아니기 때문에 그다지 활발하게 논의되는 타입이 아닙니다. 심화된 타입을 다루거나 까다로운 타입 에러 메시지를 읽을 때나 등장하기 때문에 초급자들은 아마never
타입을 무시하고도 타입스크립트를 쓸 수 있습니다.never
타입은 타입스크립트에서 꽤 좋은 유즈 케이스를 가지고 있습니다. 그러나, 그만큼 여러분들이 조심해야 하는 위험들도 갖고 있습니다.이 글에서는 다음 내용을 다룹니다.
never
타입의 의미와 필요성never
타입의 응용과 위험never 타입이 무엇일까?
never
타입과 이것의 목적을 완벽하게 이해하기 위해서는, 타입이란 무엇인지 그리고 타입 시스템에서 타입이 어떤 역할을 하는지 먼저 이해해야 합니다.타입은 가능한 값들의 집합과 같습니다. 예를 들어
string
타입은 가능한 문자열의 무한집합을 나타냅니다. 우리가 변수에string
타입이라고 붙여줄 때, 그 변수는 문자열 집합 내에서만 값을 가질 수 있게 됩니다.타입스크립트에서
never
는 값들의 공집합을 의미합니다. 실제로 또다른 유명한 자바스크립트 타입 시스템인 Flow에서도never
와 동일한 타입을 empty라고 부른답니다.집합에 값이 없기 때문에
never
는any
타입을 포함하여 절대로(never ㅋ) 어떠한 값도 가질 수 없습니다.never
가 때때로 거주 불가능한 타입(uninhabitable type) 또는 바닥 타입(bottom type)이라고 불리는 이유입니다.바닥 타입은 TypeScript Handbook에서 정의한 단어입니다. 저는 서브타이핑을 이해하는 데 사용하는 타입 계층 트리에서
never
를 두지 않는 게 더 이해하기 쉽다고 생각했습니다.다음 논리적인 질문은, '왜
never
타입이 필요한가?' 입니다.왜 우리는 never 타입이 필요할까?
숫자 시스템에서 아무것도 가지지 않았다는 것을 나타내기 위해 우리가 0을 사용하는 것처럼, 타입 시스템도 불가능함을 나타내는 타입이 필요합니다. "불가능함"이란 단어 자체가 모호합니다. 타입스크립트에서 "불가능함"은 다양한 방식으로 나타납니다.
never) 반환하지 않는 함수의 리턴 타입 (Node에서process.exit
처럼)void
와 혼동하지 마세요.void
는 호출자에게 유용한 무언가를 리턴하지 않는다는 의미입니다.never) 들어갈 수 없는 else 분기promise
의 fulfilled된 값의 타입never 타입은 합집합(union)과 교집합(intersection)에서 어떻게 동작할까?
덧셈, 곱셈에서 0이 동작하는 방식과 유사하게,
never
타입은 union 타입과 intersection 타입에서 사용되는 특별한 프로퍼티를 가집니다.never
와 합집합을 하게 되면never
는 제외됩니다. 어떤 숫자에 0을 더했을 때 그 숫자가 그대로인 것과 같은 원리입니다.type Res = never | string // string
never
와 intersection 하게 되면 다른 타입을 오버라이딩합니다. 어떤 숫자에 0을 곱하면 0이 되는 것처럼 말입니다.type Res = never & string // never
never
타입이 가지는 두 가지 동작/특성은 나중에 보게 될 몇몇 가장 중요한 유즈 케이스의 기반을 마련해줍니다.어떻게 never 타입을 사용할까?
아마 여러분들은 스스로
never
를 많이 사용하진 않았겠지만, 이미 꽤 잘 사용되는 유즈 케이스들이 존재합니다.허용되지 않는 함수 매개변수를 제한하기
어떤 값에
never
타입을 할당할 수 없기 때문에 우리는 다양한 응용을 위해 함수에 제한을 가하는 방식으로never
를 사용할 수 있습니다.switch문과 if-else문에서 완전히 일치하는지 보장
어떤 함수가 오직
never
타입인 하나의 매개변수만 받는다면 그 함수는never
가 아닌 값과 함께 호출될 수 없습니다.이러한 기능을 사용하여 switch문과 if-else문에서 완전히 일치하도록 보장할 수 있습니다. 남은 타입은 반드시
never
타입이므로 default 케이스로never
타입을 사용하면 모든 경우가 커버될 수 있기 때문입니다. 실수로 일치하는 항목을 누락하면 타입 에러가 발생합니다.구조적인 타이핑을 부분적으로 비허용
VariantA
또는VariantB
를 가지는 파라미터를 받는 함수가 있다고 가정해봅시다. 그러나 사용자가 두 유형의 모든 프로퍼티를 포함하는 타입을 전달해서는 안되는 경우입니다.파라미터로 유니온 타입인
VariantA | VariantB
을 사용할 수 있습니다. 그러나 타입스크립트에서 타입 호환성은 구조적인 서브타이핑을 기반으로 하고 있기 때문에 함수의 파라미터의 타입보다 더 많은 프로퍼티를 가지는 객체를 전달하는 것은 허용되지 않습니다. (객체 리터럴을 전달하지 않는 한)위의 코드 스니펫에서 타입스크립트는 타입 에러를 발생시키지 않습니다.
never
를 사용함으로써 부분적으로 구조적 타이핑을 불가능하게 하고 사용자들이 두 프로퍼티를 다 가지는 객체를 전달하지 못하게 할 수 있습니다.의도되지 않은 API 사용 방지
데이터를 읽고 저장하는
Cache
인스턴스를 생성한다고 해봅시다.어떠한 이유든지 간에
get
메소드를 통해 데이터를 읽기만 가능한 read-only 캐시를 가지고 싶어질 수도 있습니다.put
메소드 인자에never
타입을 추가하게 되면 함수 내부로 어떠한 값이든 받을 수 없게 됩니다.이론적으로 닿을 수 없는 조건 분기들을 나타내기
조건 타입 내에서 추가적인 타입 변수를 생성하기 위해
infer
를 사용할 때 우리는 모든infer
키워드에 반드시 else 분기를 추가해야 합니다.유니온 타입에서 유니온 멤버 분리하기
불가능한 분기를 나타내는 것 외에도,
never
는 조건 타입에서 원치않는 타입을 분리해내는 데 사용할 수 있습니다.이전에도 얘기했듯이, 유니온 멤버로써
never
를 사용하면 자동적으로 제거된다고 했습니다. 즉, 유니온 타입에서never
는 쓸모가 없는 것이죠.어떤 기준점을 기반으로 유니온 타입에서 유니온 멤버를 선택하는 유틸리티 타입을 작성할 때
never
타입의 쓸모없음은 else 분기에 완벽한 타입을 두게끔 만들어줍니다.foo
문자열 리터럴을 가지는name
프로퍼티를 추출하고, 그 외에는 필터링하는 유틸리티 타입인ExtractTypeByName
을 작성한다고 해볼까요.never
를 제거합니다.매핑된 타입에서 key를 필터링하기
타입스크립트에서 타입은 불변입니다. 객체 타입에서 어떤 프로퍼티를 제거하고자 한다면 이미 존재하는 타입을 변형하고 필터링하여 새로운 타입을 만들어야만 합니다. 매핑된 타입의 키를 조건부로 다시
never
로 매핑할 때, key는 필터링됩니다.객체의 값 타입에 따라 객체 타입 프로퍼티를 필터링하는
Filter
타입에 대한 예시입니다.제어 흐름 분석에서 타입 좁히기
함수의 리턴 값을
never
라고 타입을 지정한다는 것은, 함수가 실행이 종료되었을 때 절대 호출자에게 제어권을 리턴해주지 않는다는 것을 의미합니다. 타입을 좁히기 위한 제어 흐름 분석에서 이 점을 응용할 수 있습니다.다음 코드 스니펫에서,
never
타입을 리턴함수를 사용하여foor
라는 union 타입에서undefined
를 제거할 수 있습니다.또는
||
나??
연산자 이후에throwError
를 호출할 수도 있습니다.공존할 수 없는 타입들의 불가능한 교집합 나타내기
never
의 응용 사례보다는 타입스크립트 언어의 동작/특징에 더 가깝다고 느껴질 수도 있습니다. 그럼에도, 이 점은 여러분이 마주칠 수도 있는 몇 가지 암호 에러 메시지를 이해하는 데 아주 중요합니다.공존할 수 없는 타입들끼리 교집합 연산을 하면
never
타입을 얻을 수 있습니다.또한, 어느 타입이든지
never
로 교집합 연산을 하면never
를 얻겠죠.이건 객체 타입에서 더 복잡합니다.
다음 예시에서는 전체 타입인
Baz
가never
로 좁혀지는데boolean
은 판별 프로퍼티이기 때문입니다. (true | false
의 유니온)이 PR에서 더 살펴보세요.
(에러 메시지로부터) never 타입을 읽는 방법
여러분이 명시적으로
never
라고 타입을 지정하지 않으면, 예상치 못한never
타입과 함께 에러 메시지를 마주하게 될 수도 있습니다. 이는 기본적으로 타입스크립트 컴파일러가 타입들을 교차하기 때문입니다. 타입의 안전성을 지키고 건전성(soundness)을 보장하기 위해 암묵적으로 수행되는 작업입니다.다음 예시는 이전에 다형적인 함수의 타입에 대해 작성한 포스팅에서 사용한 예시입니다.
이 함수는 전달받는 인자에 따라 숫자, 문자열, 불리언을 리턴합니다. 리턴 값에 해당하는 타입을 가져오기 위해
ReturnTypeByInputType[T]
를 사용합니다. 그러나 모든 리턴문에서Type X is not assignable to type 'never'
라는 타입 에러가 발생합니다. 분기에 따라X
는 string, number, boolean이 됩니다.타입스크립트는 우리 프로그램이 문제를 일으킬 가능성을 줄여주기 위해 도움을 주려고 하기 때문에 발생합니다. 각 리턴 값은
ReturnTypeByInputType[T]
에 할당할 수 있어야 하는데, 이 값은 런타임 때 number, string, boolean이 될 수 있습니다.리턴 타입이 모든 가능한
ReturnTypeByInputType[T]
에 할당할 수 있다고 보장할 수 있을 때 타입 안정성이 지켜집니다. 즉 number, string, boolean의 intersecion일 때가 그런 경우지만 이는 서로 공존할 수 없는 타입이기 때문에never
가 되는 것이죠. 그래서 에러 메시지에never
를 보게 되는 겁니다.이를 해결하려면, 여러분은 타입 단언(type assertions) (또는 함수 오버로딩)을 사용해야만 합니다.
return Math.floor(Math.random() * 10) as ReturnTypeByInputType[T]
return Math.floor(Math.random() * 10) as never
다른 분명한 예시가 있을 수 있습니다.
obj[key]
는 런타임에key
의 값에 따라 string 또는 number가 됩니다. 이러한 제약조건이 더해진 타입스크립트는 타입의 안정성을 위해 string과 number 두 타입 모두 공존가능한 값만 쓰게끔 합니다. 그래서 두 타입의 교차된 타입인never
타입이 전달되는 것입니다.never 확인하기
어떤 타입이
never
이어야 하는지 확인하는 것보다never
인지 확인하는 것이 더 어렵습니다. 다음 코드 예시를 봅시다.Res
는true
또는false
가 될까요? 놀랍게도 둘 다 정답이 아닙니다. 실제로는never
가 됩니다.저도 이걸 처음 접했을 때 정신이 나가는 듯 했습니다. Ryan Cavanaugh가 여기서 이를 설명해주었습니다.
요약을 해보자면 다음과 같습니다.
never
는 빈 유니온입니다.never
가 됩니다.여기서 유일한 해결책은 암시적으로 합치는 것을 선택하고 튜플로 타입 매개변수를 감싸는 것입니다.
이는 실제로 타입스크립트 소스 코드의 일부이며 타입스크립트가 이 코드를 외부에 노출시키면 더 좋을 듯 합니다.
요약
이 글에서 정말 많은 것을 다루었네요.
never
타입의 정의와 목적에 대해 다뤘습니다.never
가 빈 타입인 점을 활용하여 함수에 제약사항 추가하기never
가 나타나는 이유에 대해서도 다뤘었죠never
타입인지 확인하는 방법까지 살펴봤습니다.The text was updated successfully, but these errors were encountered: