T extends string 이 있어야 넘긴 값을 리터럴 그대로("") 줍는다1 — 그래서 '' 를 넣으면 Exclude<'', ''> 가 never 가 돼 막힌다.
import * as React from 'react'
export type NonEmptyString<T> = Exclude<T, ''>
type Props<T> = {
src: NonEmptyString<T>
}
function Image<T extends string>({ src }: Props<T>) { // 제약을 빼면 T = string 으로 넓어져 '' 도 통과
return <img src={src} alt="" />
}
function App() {
return (
<>
{/* @ts-expect-error */}
<Image src="" />
<Image src="ab" />
</>
)
}
다만 string 변수를 넘기면 T = string 이라 Exclude<string, ''> 도 string — 값이 '' 여도 통과한다. 인자 타입이 리터럴 '' 일 때만 막는 가드다(const e: '' = '' 를 넘겨도 막힌다).
Footnotes
-
객체 속성에 넣은 리터럴은 보통
string으로 넓어지는데(<T>(x: { a: T })에{ a: 'ab' }→string), 들어갈 자리의 타입(여기선Exclude<T, ''>)이string제약을 가지면 리터럴로 남긴다. TypeScript v5.9.3checker.ts—isLiteralOfContextualType: “If the contextual type is a type variable constrained to a primitive type, consider this a literal context for literals of that primitive type. For example, given a type parameter ‘T extends string’, infer string literal types for T.” ↩