TL;DR
export default
とnamespace
を組み合わせて、コンポーネントの名前空間に型定義入れると、情報がまとまってキレイに書けそうです。
function ChildComponent({ label }: ChildComponent.Props) {
return <div>...</div>;
}
namespace ChildComponent {
export interface Props {
label: string;
}
}
export default ChildComponent;
import ChildComponent from 'path/to/child_component'
// 1. 定数としてPropsを入れるパターンや
const ITEMS: ChildComponent.Props[] = [
{ label: "hoge" },
]
function ParentComponent() {
// 2. コンポーネント内で動的に作るならこうだし(変数の命名や以降の擬似コードは適宜読み替えてください!)
const items = useMemo<ChildComponent.Props[]>(() => [{ label: 'hoge' }], [])
return (
<>
{ITEMS.map(item => (
<ChildComponent key={item.label} label={item.label}>
))}
</>
)
}
内部の挙動
詳細な説明は公式 Docs を参照ください。
https://www.typescriptlang.org/docs/handbook/declaration-merging.html
今回のようなコード例(namespace 内が、interface などの値ではなく型の定義のみ)をトランスパイルすると、js ファイルは当然中身はなく、以下のような型定義ファイル(d.ts
)のみ生成されます。
declare namespace ChildComponent {
interface Props {
label: string;
}
}
よって、今回のようなコンポーネントの記述方法によってファイルサイズの増大はなさそうです。
注意点
default export function ChildComponent...
の形ではダメ
こんな感じで怒られます。
Merged declaration 'ChildComponent' cannot include a default export declaration. Consider adding a separate 'export default ChildComponent' declaration instead.
named export
も(多分)ダメ
namespace
を定義したファイルないであれば問題ないのですが、インポートする側は namespace が見つからないため、難しそうです。(このあたりの仕様についてご存じの方いらっしゃいましたら、コメントいただけると幸いです。)
default export
は宗教上の理由で使えないです
ごめんなさい。
もし、import時のTypoがどうしても怖いという場合は、default-import-match-filenameというeslintのルールで、ファイル名と同じ名前でimportするよう制約付けることもできるそうですが、まあこのためにわざわざやるのは見合わないかもですね。
参考資料
https://www.typescriptlang.org/docs/handbook/namespaces.html
https://www.typescriptlang.org/docs/handbook/declaration-merging.html