타입을 구체적으로 명시하지 않고 TS
의 추론에 맞긴다면, 타입 매개변수
를 이용해 손쉽게 추론 가능하다.
const MyComponent = <T extends unknown>({ a, b, c }: MyComponentProps<T>) => {
return (
<div>
<h1>{a}</h1>
<h2>{b}</h2>
<ul>
{Array.isArray(c) ? (
c.map((item, idx) => (
<li key={JSON.stringify(item) + `${idx}`}>
{JSON.stringify(item)}
</li>
))
) : (
<strong>{JSON.stringify(c)}</strong>
)}
</ul>
</div>
);
};
interface MyComponentProps<T> {
a: string;
b: number;
c: T;
}
export default MyComponent;
a
와 b
의 타입은 고정이지만, c
는 어떤 타입이 들어와도 받을 수 있다.
type Mappped<T> = {
[P in keyof T]: T[P];
};
interface MyComponentProps<T> {
a: string;
b: number;
c: Mappped<T>;
}
{ [P in keyof T]: T[P] }
는 Mapped Type이라고 불리는 형태이며, 이렇게 작성 시 위의 T
를 그냥 사용한 것과 똑같은 결과를 나타낸다. c
가 primitive 타입이라면 그 타입의 모든 속성을 props로 가지게 되고, object 타입이라면 내부 속성만 값으로 가지게 된다.
각각의 타입에 따라 위 이미지와 같은 속성을 얻게 된다.
primitive나 배열이 아닌 객체 형태의 속성만 전달할 필요가 있을 수도 있다. TS
의 유틸리티 타입 중 Record
를 사용해 객체 타입만 받도록 지정하면 된다.
const MyComponent = <T extends Record<string, unknown>>(
{ a, b, c }: MyComponentProps<T>
) => {
return (
<div>
{/* ... */}
</div>
);
};
interface MyComponentProps<T> {
a: string;
b: number;
c: T;
}
Record<keyType, valueType>
는 속성 key의 타입을 첫 번째 제네릭으로 받고, value의 타입을 두 번째 제네릭으로 받는다. 이렇게 T
를 확장시키면 객체 이외의 c값은 오류를 발생한다.
function App() {
const temp = {
a: "A",
child: [
{
b: "B",
c: "C"
}
]
};
return (
<div className="App">
{/* Type 'string' is not assignable to type 'Record<string, unknown>'. */}
<MyComponent a={"a"} b={1} c={"A"} />
{/* Type 'string[]' is not assignable to type 'Record<string, unknown>'. */}
<MyComponent a={"b"} b={2} c={["A", "B", "C"]} />
{/* Type '(string | number | true)[]' is not assignable to type 'Record<string, unknown>'. */}
<MyComponent a={"b"} b={2} c={["A", 1, true]} />
{/* No Error */}
<MyComponent a={"b"} b={2} c={temp} />
</div>
);
}