メインコンテンツにスキップ

レンダー型

一部のコンポーネントライブラリやデザインシステムでは、コンポーネントの構成方法を制限したい場合があります。たとえば、Menu は常に MenuItem を子としてのみレンダリングする必要があります。レンダー型は、これらの制約をサポートしながら、ユーザーがこれらのコンポーネントをどのように使用するかについて豊かな柔軟性を提供する組み込みの方法です。

基本的な動作

コンポーネントは、renders キーワードを使用してレンダリングするものを宣言できます。

1import * as React from 'react';2
3component Header(size: string, color: string) { return <div /> }4
5component LargeHeader(color: string) renders Header {6  return <Header size="large" color={color} />; // Ok!7}

特定の要素をレンダリングするコンポーネントを宣言する場合、そのレンダリングチェーンで最終的にそのコンポーネントをレンダリングする任意のコンポーネントを返すことができます。

1import * as React from 'react';2
3component Header(size: string, color: string) { return <div /> }4
5component LargeHeader(color: string) renders Header {6  return <Header size="large" color={color} />;7}8
9component LargeBlueHeader() renders Header {10  // You could also use `renders LargeHeader` above11  return <LargeHeader color="blue" />;12}

コンポーネントは、特定の要素をレンダリングするプロパティを指定できます。

1import * as React from 'react';2
3component Header(size: string, color: string, message: string) {4  return <h1 style={{color}}>{message}</h1>;5}6
7component Layout(header: renders Header) {8  return (9    <div>10      {header}11      <section>Hi</section>12    </div>13  );14}

そして、Header の要素、または Header をレンダリングするコンポーネントの要素を、そのプロパティに渡すことができます。

<Layout header={<LargeBlueHeader />} />;

ヘッダーをレンダリングしないコンポーネントを、ヘッダーを期待するレンダー型に渡すことはできません。

1import * as React from 'react';2
3component Footer() {4  return <footer />;5}6
7component Header(size: string, color: string, message: string) {8  return <h1 style={{color}}>{message}</h1>;9}10
11component Layout(header: renders Header) {12  return <div>{header}</div>;13}14
15<Layout header={<Footer />} />; // ERROR Footer does not render Header
15:17-15:26: Cannot create `Layout` element because `Footer` element [1] does not render `Header` [2] in property `header`. [incompatible-type]

デザインシステムとの統合

レンダー型は、デザインシステムとの統合を簡単にするように設計されています。デザインシステムコンポーネントのプロパティがレンダー型を期待している場合、その型をコンポーネントにコピー/ペーストして、デザインシステムと統合できます。

1import * as React from 'react';2
3component Header() {4  return <h1>Header!</h1>;5}6
7component Layout(header: renders Header) {8  return <div>{header}</div>;9}10
11// Copy-paste the header props' type!12component ProductHeader() renders Header {13  // We must return a value that renders a Header to satisfy the signature14  return <Header />;15}16
17// And now you can integrate with the design system!18<Layout header={<ProductHeader />} />; // OK!

オプション要素のレンダリング

最終的に要素をレンダリングする可能性のある子、または何もレンダリングしない子を受け取ることができるコンポーネントを記述したい場合があります。これを実現するには、特殊なレンダー型バリアント renders? を使用できます。

1import * as React from 'react';2
3component DesignSystemCardFooter() {4  return <div>Footer Content</div>;5}6
7component DesignSystemCard(8  children: React.Node,9  footer: renders? DesignSystemCardFooter,10) {11  return <div>{children}{footer}</div>;12}13
14// With these definitions, all of the following work:15
16<DesignSystemCard footer={<DesignSystemCardFooter />}>Card</DesignSystemCard>;17<DesignSystemCard footer={null}>Card</DesignSystemCard>;18<DesignSystemCard footer={undefined}>Card</DesignSystemCard>;19<DesignSystemCard footer={false}>Card</DesignSystemCard>;20
21component ProductFooter(hasFooter?: boolean) renders? DesignSystemCardFooter {22  return hasFooter && <DesignSystemCardFooter />;23}24
25<DesignSystemCard footer={<ProductFooter />}>Card</DesignSystemCard>;

リストのレンダリング

プロパティとして特定の要素をレンダリングする任意の子を任意の数受け取ることができるコンポーネントを記述したい場合があります。これを実現するには、特殊なレンダー型バリアント renders* を使用できます。

1import * as React from 'react';2
3component DesignSystemMenuItem() {4  return <li>Menu Item</li>;5}6
7component DesignSystemMenu(8  children: renders* DesignSystemMenuItem,9) {10  return <ul>{children}</ul>11}12
13// With these definitions, all of the following work:14
15const menu1 = (16  <DesignSystemMenu>17    <DesignSystemMenuItem />18  </DesignSystemMenu>19);20
21const menu2 = (22  <DesignSystemMenu>23    <DesignSystemMenuItem />24    <DesignSystemMenuItem />25  </DesignSystemMenu>26);27
28const menu3 = (29  <DesignSystemMenu>30    {[31      <DesignSystemMenuItem />,32      <DesignSystemMenuItem />,33    ]}34    <DesignSystemMenuItem />35  </DesignSystemMenu>36);37
38component ProductMenuItem() renders DesignSystemMenuItem {39  return <DesignSystemMenuItem />;40}41
42const menu4 = (43  <DesignSystemMenu>44    {[45      <ProductMenuItem />,46      <DesignSystemMenuItem />,47    ]}48    <DesignSystemMenuItem />49  </DesignSystemMenu>50);

透過的なコンポーネント

コンポーネントは「透過的」にすることができます。

1import * as React from 'react';2
3component TransparentComponent<T: React.Node>(children: T) renders T {4  // .. do something5  return children;6}7
8component Header(text: string) {9  return <h1>{text}</h1>;10}11component InstagramHeader() renders Header {12  return <Header text="Instagram" />;13}14component Layout(15  header: renders Header,16) {17  return <div>{header}</div>;18}19
20component Page() {21  const wrappedHeader = <TransparentComponent><InstagramHeader /></TransparentComponent>22  return <Layout header={wrappedHeader} />; // Ok!23}

非コンポーネント構文コンポーネントとの相互運用

renders を使用して、関数コンポーネントにも注釈を付けることができます。

1import * as React from 'react';2
3component Header(text: string) {4  return <h1>{text}</h1>;5}6component InstagramHeader() renders Header {7  return <Header text="Instagram" />;8}9component Layout(10  header: renders Header,11) {12  return <div>{header}</div>;13}14
15function FunctionHeader(): renders Header {16  return <InstagramHeader />;17}18
19function InstagramPage() {20  return <Layout header={<FunctionHeader />} />; // OK!21}

サブタイピングの動作

すべてのレンダー型は React.Node のサブタイプであり、renders Foo のみが React.MixedElement のサブタイプです。

1import * as React from 'react';2
3component Header() {4 return <h1>Hello Header!</h1>;5}6
7declare const rendersHeader: renders Header;8declare const rendersMaybeHeader: renders? Header;9declare const rendersHeaderList: renders* Header;10
11rendersHeader as React.Node;12rendersMaybeHeader as React.Node;13rendersHeaderList as React.Node;14
15rendersHeader as React.MixedElement;16rendersMaybeHeader as React.MixedElement; // ERROR!
17rendersHeaderList as React.MixedElement; // ERROR!
16:1-16:18: Cannot cast `rendersMaybeHeader` to `MixedElement` because renders? `Header` [1] is incompatible with `React.Element` [2]. [incompatible-cast]
17:1-17:17: Cannot cast `rendersHeaderList` to `MixedElement` because renders* `Header` [1] is incompatible with `React.Element` [2]. [incompatible-cast]

renders Foorenders? Foo のサブタイプであり、renders? Foorenders* Foo のサブタイプです。

1import * as React from 'react';2
3component Header() {4 return <h1>Hello Header!</h1>;5}6
7declare const rendersHeader: renders Header;8declare const rendersMaybeHeader: renders? Header;9declare const rendersHeaderList: renders* Header;10
11rendersHeader as renders? Header;12rendersHeader as renders* Header;13rendersMaybeHeader as renders* Header;14
15rendersMaybeHeader as renders Header; // ERROR
16rendersHeaderList as renders Header; // ERROR
17rendersHeaderList as renders? Header; // ERROR
15:1-15:18: Cannot cast `rendersMaybeHeader` to renders `Header` because a value that renders nothing [1] does not render `Header` [2]. [incompatible-cast]
16:1-16:17: Cannot cast `rendersHeaderList` to renders `Header` because `$ReadOnlyArray` [1] does not render `Header` [2]. [incompatible-cast]
16:1-16:17: Cannot cast `rendersHeaderList` to renders `Header` because a value that renders nothing [1] does not render `Header` [2]. [incompatible-cast]
17:1-17:17: Cannot cast `rendersHeaderList` to renders? `Header` because `$ReadOnlyArray` [1] does not render `Header` [2]. [incompatible-cast]