Slide 1

Slide 1 text

Sansan株式会社 技術本部 Eight Engineering Unit Product Devグループ 藤野 眞人 childrenの順序まで型で縛る ── Branded Typesで実践するJSXの構造安全

Slide 2

Slide 2 text

藤野 眞人 Sansan株式会社 技術本部 Eight Engineering Unit Product Devグループ - 2020年に新卒入社 - 名刺アプリ「Eight」のWeb開発全般を担当しています - 最近は野鳥観察がマイブーム

Slide 3

Slide 3 text

childrenの順序を型で縛りたい! Cardのchildrenには必ずHeader、Bodyの順序で要素を配置したい 例えばこんな構造 body

Slide 4

Slide 4 text

JSXの中身は”Black box” 公式ドキュメントにも記載されている By default the result of a JSX expression is typed as any. You can customize the type by specifying the JSX.Element interface. However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface. It is a black box. 出典元:TypeScript - The JSX function return type この前提とどう戦っていくか

Slide 5

Slide 5 text

function CardHeader(p: HeaderProps) { return

{p.title}

; } function CardBody(p: BodyProps) { return {p.children}; } type CardHeaderEl = ReactElement; type CardBodyEl = ReactElement; type CardProps = { children: readonly [CardHeaderEl, CardBodyEl]; }; ReactElement で型定義し、childrenをtupleにする まずは素直に書いてみる

Slide 6

Slide 6 text

が、全て通ってしまう。。。 不正な構造なのに、コンパイルエラーにならない例 x // ✅ 通る (順序入れ替え) // ✅ 通る
nope
// ✅ これも通る ... // ✅ → JSX式でのreturnは、一律 JSX.Element に変換されてしまうため

Slide 7

Slide 7 text

ではどうするか?

Slide 8

Slide 8 text

Branded Typesを応用する 1. Branded Types の定義 // 馴染みの形 (ID の取り違え防止) declare const __brand: unique symbol; type Brand = T & { readonly [__brand]: B }; type PersonId = Brand; type CardId = Brand; type CardHeaderEl = Brand< ReactElement, 'CardHeader' >; 2. 関数コンポーネントへの適用 function CardHeader(p: HeaderProps) { return (

{p.title}

) as unknown as CardHeaderEl; } ポイント: - 関数の戻り値を CardHeaderEl にキャスト - これにより JSX.Element ではなく 特定の型として保持される - 構造的な型の安全性を担保することが 可能になる

Slide 9

Slide 9 text

呼び出し方のポイント ✅ OK // 関数として直接実行する {CardHeader({ title: 'Profile' })} {CardBody({ children: 'Hello' })} ❌ NG // コンポーネントとして呼び出す Hello 関数呼び出しの形式で子要素を作成 → JSX式でJSX.Elementに潰される事を回避し、Branded Typesの型情報を保持

Slide 10

Slide 10 text

検証の結果 順序エラー 個数エラー inline JSXエラー Brand不正エラー childrenの順序・個数・種別を型で縛る事ができた!

Slide 11

Slide 11 text

今回の記法のメリット・デメリット メリット - children の順序・個数・種別を型のみで縛れる デメリット - DevToolsに子コンポーネント名が表示されない - Hooks / Suspense / Context等が使えないので、Cardのような表示専用のコンポーネントに限定される → 向き・不向きがはっきりしており、採用前に把握するべきトレードオフがある

Slide 12

Slide 12 text

TypeScriptの今後に期待 Wes Wigham 氏による 2018年の提起 (Issue #21699) JSX 式の型を、JSX.Element ではなく factory function の戻り値から解決すべき、という提案。 2019年の実装 PR はクローズ 理由はパフォーマンスの低下: - 型チェック時間・メモリ消費が増加 - JSXのネスト構造が、型推論にとって 構造的に重い TypeScriptはパフォーマンス改善のネイティブ実装に取り組んでおり、今後の改善に期待 ✨

Slide 13

Slide 13 text

まとめ - Brand を被せると JSX.Element には代入できなくなる - = children の順序・個数・種別が型で縛れる - ユースケースは表示専用のコンポーネント - 例: 自社のデザインシステム - Issue #21699 が解決し、今後の TSの進化に期待 ✨

Slide 14

Slide 14 text

No content