Typescriptで学ぶReact 入門 Propsの渡し方と定義

Typescriptで学ぶReact 入門 Propsの渡し方と定義

このチュートリアルでは、Reactアプリケーションの構築にTypeScriptを使用する方法を、Create-React-App (CRA)を利用して簡単に始められる手順とともに解説します。

また、TypeScriptを使用した独自のReact関数コンポーネントでのPropsの定義方法も紹介します。

前提として、あなたがNode.jsを既にインストールしていることを仮定しています。

また、このチュートリアルはTypeScriptの基本的な知識があることも前提としています。

ReactとTypeScriptの組み合わせについて学ぶことで、より堅牢なアプリケーションを開発するためのツールとしての利用価値を高めることができます。

この記事を参考にして、ReactとTypeScriptの基本をマスターしましょう。

TypeScriptとReactの利点

Reactアプリケーションを構築するための新しい方法として、TypeScriptが広く採用されています。

TypeScriptはJavaScriptのスーパーセットであり、型付き構文をサポートしています。

コンパイルすることで、ブラウザで実行できるプレーンなJavaScriptに変換されます。

TypeScriptは読みやすさと型チェックの恩恵により、フロントエンドWeb開発でますます人気が高まっています。

静的型システムは問題を早期に発見するために役立ちます。

例えば、必要なPropsを提供していない場合、TypeScriptコンパイラはこれを検出して報告します。

VS Codeを使用する場合は、赤い波線でそれを指摘してくれます。

TypeScriptとReactは完璧に相性が良く、両方を組み合わせることでアプリケーション開発の生産性を大幅に向上させることができます。

クリーンなコードでバグを減らし、よりシンプルなドキュメントを作成することができます。

開発者がコードを保守しやすくなり、コンポーネント間で渡されるプロパティとデータ型をチェックすることでバグを防止し、実装の意図を明確にすることができます。

大規模なReactプロジェクトにとって、TypeScriptは価値のあるオプションであることがわかります。

静的型チェックは、プロジェクトの拡大に伴い、開発者が迅速かつ正確に修正できるようにしてくれます。

TypeScriptを利用することで、Reactアプリケーションの品質と保守性を向上させることができます。

CRAのインストール

Create-React-App(CRA)は、コマンドラインCLI)ツールで、WebpackやBabelなどに気を使うことなく、デフォルトとゼロの構成で新しいReactアプリを簡単に作成し、開発を開始できます。

下記のコマンドを使用して、グローバルにインストールすることができます。

npm install -g create-react-app

TypeScriptは次のステップで、npxを使用してインストールが可能です。

プログラムを開発する際におすすめのエディタはVSCodeです。軽量で高速であり、必要なすべての機能を提供してくれます。

ただし、SublimeTextWebStormAtomなどの他のエディタを使用したい場合は、自由に選択することができます。

この記事ではエディタの選択は重要ではありません。

TypeScriptでReactアプリを初期化

TypeScriptを使用したReactアプリケーションを作成するためには、CRAコマンドに--template typescriptオプションを追加します。

例えば、下記のコマンドを実行すると、TypeScriptを使用した新しいReactアプリケーションが作成されます。

npx create-react-app my-app --template typescript

npxコマンドは、CLIコマンドやレジストリにホストされている実行ファイルを実行するために使用されます。

これにより、グローバルインストールは必要ありません。

プロジェクトが作成されると、プロジェクトディレクトリに移動して任意のコードエディタで開くことができます。

プロジェクトファイル構成は、下記のようになります。

my-app/
├─ node_modules/
├─ public/
    | — favicon.ico
    | — index.html
    | — logo192.png
    | — logo512.png
    | — manifest.json
    | — robots.txt
├─ src/
    | — App.css
    | — App.test.tsx
    | — App.tsx
    | — index.css
    | — index.tsx 
    | — logo.svg
    | — react-app-env.d.ts
    | — reportWebVitals.ts
    | — setupTests.ts
    | — registerServiceWorker.ts
├─ .gitignore
├─ package.lock.json
├─ package.json
├─ README.md
└─ tsconfig.json

tsconfig.json

tsconfig.jsonファイルは、TypeScriptプロジェクトのコンパイラ設定とファイルを示す設定ファイルであり、JavaScriptプロジェクトのpackage.jsonと同様の役割を果たします。

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

CRAによって生成されたtsconfig.jsonは、TypeScriptのコンパイラが使用する設定を含んでいます。

例えば、"target": "es5"は、コードをどのECMAScriptバージョンにコンパイルするかを指定するオプションです。

この場合、古いブラウザとの互換性を高めるために、ECMAScript 5にコンパイルされます。

また、"lib": ["dom", "dom.iterable", "esnext"]は、コードがどのライブラリを使用するかを示すオプションです。

ここでは、DOMとDOMのイテレータ(配列)を使用することを指定しています。

また、"esnext"を含めることで、最新のJavaScript機能を使用できるようになります。

さらに、"jsx": "react-jsx"は、JSXを使用してReactコンポーネントを記述するための設定です。

このオプションは、Reactが提供するReact.createElement()を呼び出す代わりに、JSXシンタックスを使用してコンポーネントを記述することを可能にします。

"include": ["src"]は、TypeScriptコンパイラに含めるファイルを指定するオプションです。

ここでは、srcフォルダー内のすべてのファイルを含めるように指定されています。

これらのオプションは、TypeScriptコンパイル時に重要な役割を果たします。

設定ファイルを変更することで、TypeScriptプロジェクトのコンパイル設定をカスタマイズすることができます。

以上が、CRAでTypeScriptを使用する場合の基本的な設定とファイル構成についての解説です。

JSXには.tsxファイルを使用

TypeScriptを使用してReactを開発する場合、通常のReactアプリの作成方法にいくつかの変更が必要です。

.ts拡張子ファイルに加えて、.tsxという新しいファイル拡張子があります。

この拡張子は、JSX構文を含むReactコンポーネントファイルを作成する場合に使用します。

TypeScriptは、JSXを直接JavaScriptコンパイルできるため、TypeScriptを使用することでReactアプリケーションをより型安全に、かつコードの品質を高めることができます。

TypeScriptを使用することで、パッケージをインストールする必要がなくなるため、開発プロセスがスピードアップすることもあります。

.tsxファイルを使用することによって、WebpackやES BuildなどのJavaScriptバンドラーは、.tsxファイルに対して特別なプラグインを実行することができ、Jestのようなテストランナーは.tsxファイルのテスト環境を実行することができます。

また、.tsxファイル拡張子のサポートにより、コードエディターが新しい言語機能をサポートすることができます。

.tsファイル拡張子では、JSX構文や要素の使用を必要としない関数、クラス、レデューサーなどを作成する場合に使用されます。

一方、.tsxファイル拡張子は、Reactコンポーネントを作成し、JSX要素および構文を使用する場合に使用されます。

PropsとStateにTypeScriptを使用する場合、学習コストがかかるかもしれませんが、一度理解するとReactのデフォルトの方法よりもはるかに強力であることがわかるでしょう。

TypeScriptコンポーネント作成

まず、Reactアプリケーションの初期ファイルであるCRAデフォルトのApp.tsxファイルを開きます。

このファイルには、Reactアプリケーションのルートコンポーネントが含まれています。

最初に表示されるロゴやCSS、およびimgタグやaタグは必要ないので、それらを削除して、下記のように簡略化できます。

function App() {
  return (
<div className="App">
  <header className="App-header">
  </header>
</div>
  );
}

export default App;

それでは、このまま、関数コンポーネントを作成していきましょう。

まずは、TypeScriptを使用しない例として Propsという機能の流れを掴んでいきます。

下記の例では、Appコンポーネントが親として、Childコンポーネントが子の関数コンポーネントとなります。

userというオブジェクトと、Numを作る関数、そして2つの変数の値を宣言しています。

コンポーネントで、子コンポーネントに渡すPropsを設定し、それをChildコンポーネントに渡しています。

// 親コンポーネント

function App() {
  const user = { age: 30, name: "Taro" }

  const funcNum = num => {
    return num + 2
  }
  const title = "Hello!!, React with TypeScript."
  const content = "propsを送ります"
  return (
    <div>
      <Child
        title={title}
        content={content}
        user={user}
        num={funcNum}
      />
    </div>
  )
}

子の関数コンポーネントは、最初の引数としてPropsをオブジェクトとして受け取ります。

コンポーネントで設定された各属性は、子コンポーネントのPropsの名前となります。

一般的な方法では、Propsを分解して取り出すことができます。

下記の例では、Propsの各プロパティを分解して、それぞれを変数に割り当てています。

// 子コンポーネント

export default function Child({ title, content, user, num }) {

  console.log(num(3)) // 5

  return (
    <div>
      <h2>{title}</h2>
      <p>{content}</p>
      <p>{user.age}</p>
      <p>{user.name}</p>
    </div>
  )
}

See the Pen React with Typescript interface 拡張 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


このように、受け取ったPropsオブジェクトから必要な値を分解して取り出すことで、コードの可読性が向上します。

また、Propsにはデフォルト値を設定することもできます。

これは、Propsが渡されなかった場合にその値が使用されるため、コンポーネントの使用時に必須の値であるかどうかを明示することができます。

export default function Child({ title = "Default Title", content = "Default Content", user = {}, num = () => {} }) {

  console.log(num(3)) // 5

  return (
    <div>
      <h2>{title}</h2>
      <p>{content}</p>
      <p>{user.age}</p>
      <p>{user.name}</p>
    </div>
  )
}

ここでは、Propsの値が渡されなかった場合に、titleには"Default Title"contentには"Default Content"userには空のオブジェクト、numには空の関数が使用されるように設定しています。

以上のように、Propsを適切に扱うことで、Reactコンポーネントの再利用性と保守性を高めることができます。

Propsの渡し方

ReactでTypeScriptを使用して関数コンポーネントを作成する際には、Propsの型チェックを行うことが推奨されます。

コンポーネントは下記となります。

// 親コンポーネント

function App() {
  return (
    <div className="App">
      <header className="App-header">

<MyMessage  />

      </header>
    </div>
  );
}


MyMessageという子の関数コンポーネントを作成します。

// 子コンポーネント
const MyMessage = ( ) => {
  return <div><h2>Welcome to the {message} of React and TypeScript.</h2></div>;
}

全てのPropsに対して、オブジェクトとして型を定義をしなければいけません。

Propsの型を定義する方法はいくつかあります。

まず、インラインで型を定義する方法があります。

下記のように、Propsをオブジェクトとして定義し、関数の引数として渡します。

const MyMessage = ({ message }: { message: string }) => {
  return <div>

<h2> Welcome to the {message} of React and TypeScript.</h2>

</div>;
}

See the Pen React with Typescript interface by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


次に、Propsオブジェクトで型を定義する方法があります。

下記のように、Propsオブジェクトを引数として渡します。

const MyMessage = (props: { message: string }) => {
  return <div>

<h2>Welcome to the {props.message} of React and TypeScript.</h2>

</div>;
}

See the Pen React with Typescript inlineで定義 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


また、interfaceを使用して、Propsの型を定義する方法があります。

下記のように、Propsの型をinterfaceとして定義し、関数の引数として渡します。

interface MessageProps {
  message: string;
};

const MyMessage = (props: MessageProps) => {
  return<div>

<h2>Welcome to the {props.message} of React and TypeScript. </h2>

</div>;
}

コンポーネントには、messageというプロパティが必要であり、その値は文字列である必要があります。

コンポーネントからPropsを渡す場合、下記のようにmessageプロパティに文字列の値を渡すことができます。

function App() {
  return (
    <div className="App">
      <header className="App-header">

<MyMessage message="world" />

      </header>
    </div>
  );
}

export default App;

See the Pen React with Typescript interface by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


同様に、下記の例では、3つのPropsを持つコンポーネントに型注釈を付けています。

interface UserProps {
  name: string;
  age: number;
  country: string;
};

function UserData({name, age, country}: UserProps) {
  return (
    <div>
      <h2>{name}</h2>
      <h2>{age}</h2>
      <h2>{country}</h2>
    </div>
  );
}

export default function App() {
const obj = {...{name: 'Taro', age: 30, country: 'Japan'}};

  return (
    <div>
      <UserData {...obj} />
    </div>
  );
}

See the Pen React with Typescript interface 複数 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


UserPropsは、nameagecountryの3つのプロパティを持つオブジェクトを表しています。

UserDataコンポーネントは、この3つのプロパティをPropsとして受け取り、それらを表示します。

コンポーネントでは、UserDataコンポーネントにPropsとしてオブジェクトを渡す際に、スプレッド構文(...)を使用してオブジェクトのプロパティを展開しています。

Propsの型を正確に定義することで、コンポーネント間での値の受け渡しを安全に行うことができます。

また、TypeScriptを使用することで、開発時のデバッグが容易になり、バグを防ぐことができます。

エイリアス(type alias)

エイリアス(type alias)は、TypeScriptで型を再利用するために使用されます。

interfaceを使うこともできますが、一貫性と明確性を保つために、型エイリアスを使用することが推奨されます。

Reactアプリケーションで、特定のコンポーネントが受け取るPropsを定義する必要がある場合、型エイリアスではなくinterfaceを使用することが理想的です。

ただし、Propsの関係が複雑な場合は、型エイリアスを使用することがあります。

エイリアスは、プリミティブ型だけでなく、オブジェクト型、ユニオン型、タプル型、および交差型を表すために使用できます。

エイリアスは、typeキーワードを使って宣言します。

type Props = {
  title: string,
  score: number
}

const App = ({ title, score }: Props) => (
  <h1>{title} = {score}</h1>
)

また、型に名前を与えることで可読性が向上します。

type userName = string;
type userId = string | number; 
type arr = number[];

変数と同様に、同じ型のエイリアスを複数回宣言することはできません。

また、TypeScriptがエイリアスを推論することはなく、明示的に型注釈しなければなりません。

以下は、型エイリアスを使った例です。

type PersonAge = number;
type PersonName = string;
type PersonMail = string;
type Person = {
  age: PersonAge,
  name: PersonName,
  mail: PersonMail
};

const personAge: PersonAge = 30
const personName: PersonName = "Taro"
const personMail: PersonMail = "xxx.xxx@gmail.com"
const person: Person = {
  age: personAge,
  name: personName,
  mail: personMail
};

console.log(person);

コンソールの出力は下記で確認可能です。

https://codepen.io/enjinia_f/pen/YzawvzP/?editors=1011

上記の例では、型エイリアスを使用して、Personオブジェクトのプロパティの型を明示的に指定しています。

これにより、コードの可読性が向上し、保守性が向上します。

宣言のマージと拡張

ReactとTypeScriptの組み合わせで、Propsを扱う際に非常に重要な機能があります。

それは、型の定義方法に関するものです。

TypeScriptでは、型エイリアスinterfaceの2つの型の定義方法があります。

エイリアスは、既存の型に対して別名を付けるためのもので、下記のように定義することができます。

type MyString = string;

一方、interfaceは、オブジェクトの形状を定義するためのもので、下記のように定義することができます。

interface MyObject {
  name: string;
  age: number;
}

このように、型エイリアスとinterfaceはそれぞれ異なる目的を持っていますが、いくつかの点で異なる振る舞いをすることがあります。

まず、宣言のマージについて考えてみましょう。

宣言のマージとは、TypeScriptコンパイラが同じ名前を持つ2つ以上のinterfaceを1つにマージすることです。

例えば、下記のように2つのinterfaceを定義することができます。

interface User {
  name: string;
}

interface User {
  age: number;
}

この場合、TypeScriptは自動的に両方のinterfaceをマージして、下記のように解釈します。

interface User {
  name: string;
  age: number;
}

一方、型エイリアスでは宣言のマージは行われません。

同じ名前の型エイリアスを複数回定義すると、エラーが発生します。

type MyType = {
  name: string;
}

type MyType = {
  age: number;
}
// Error: Duplicate identifier 'MyType'

次に、型の拡張について考えてみましょう。

interfaceは、他のinterfaceを拡張することができます。

これにより、元のinterfaceに新しいプロパティを追加したり、既存のプロパティを上書きすることができます。

下記は、interfaceを拡張する例です。

interface Person {
  name: string;
}

interface Employee extends Person {
  salary: number;
}

const employee: Employee = {
  name: "Alice",
  salary: 1000
};

一方で、型エイリアスは拡張ができません。

同じ名前の型エイリアスを定義することはできますが、既存の型に対して新しいプロパティを追加することはできません。

type MyType = {
  name: string;
}

type MyType = {
  age: number;
  // Error: 'name' is already declared in the same scope

Children props

Reactにおいて、Propsを受け取ってレンダリングするChildren機能は、最も価値のある機能の1つです。

このChildren propsを使用することで、コンポーネントをまとめて構成することができます。

この概念は、再利用可能なコンポーネントを構築するために非常に重要です。

コンポーネントが子コンポーネントに直接何らかのコンテンツを渡すためには、マークアップまたはアクションでカプセル化するだけでよく、子コンポーネントはprops.children変数を通じて、渡されたchildrenにアクセスすることができます。

ほとんどのJavaScriptのデータ型を渡すことができます。

数値、文字列、真偽値、要素の配列、あるいは他のコンポーネントも可能です。

しかし、型を明示的に定義することが望ましいです。

これにより、TypeScriptを使用して型チェックを行うことができます。

propsの型を定義する場合、いくつかのオプションがあります。

JSX.Element

Reactでは、Props.childrenプロパティの多くはJSX要素で構成されており、これらの要素を型付けするためにJSX.Elementという特別な型を使用できます。

React要素は、コンポーネントやDOM要素を表すオブジェクトであり、JSX要素がJavaScriptに変換された結果として生成されます。

JSX.Elementは、React環境でグローバルに利用できる特別な型です。

コンポーネントレンダリングされるとき、開始タグと終了タグの間に含まれるコンテンツを保持します。

例えば下記のように、Propsにtitlechildrenを定義した場合です。

type Props = {
  title: string,
  children: JSX.Element;
};

const Component = ({ title, children }: Props) => (

  <div>
    <h1>{title}</h1>
    {children}
  </div>
);

const App = () => {
return(

<Component>
<span>React children</span>
</Component>
);
};

JSX.Element型は、子コンポーネントが受け取るReact要素の配列を表します。

コンポーネントは、この配列内のReact要素にアクセスし、それらをレンダリングすることができます。

ただし、JSX.Elementは、childrenに使用できる最も制限的な型であり、Propsが単一のReact要素である場合にのみ機能します。

他のプリミティブな値、例えば文字列などは、childrenに許可されていません。

もし、コンポーネントの消費者がこれをオプションにしたい場合は、?を付けた型注釈を使用することができます。

type Props = {
  title: string;
  children?: JSX.Element;
};

ただし、JSX.Elementは複数の子要素を許可しません。複数の子要素を持つ場合、または文字列などのプリミティブなものを許可したい場合は、他のオプションを検討する必要があります。

JSX.Element [ ]

JSX.Element[]は、複数のJSX要素を含む配列を表します。

この型は、Reactで複数の要素をレンダリングする際によく使用されます。

複数のJSX要素を許可しますが、文字列、数字などは許可しません。

下記では、JSX.Element[]の型は明示的に定義していない事に注意してください。

interface Fruit {
  name: string;
  color: string;
}

interface FruitListProps {
  fruits: Fruit[];
}

function FruitList({ fruits }: FruitListProps) {
  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>
          <span>{fruit.name}</span>
          <span>{fruit.color}</span>
        </li>
      ))}
    </ul>
  );
}

上記の例では、FruitListコンポーネントfruitsプロパティが渡されます。

fruitsプロパティは、Fruit型の配列であり、Fruit型は、namecolorの2つのプロパティを持ちます。

FruitListコンポーネントでは、fruitsプロパティの各要素をレンダリングするために、mapメソッドを使用しています。

mapメソッドは、各要素に対して、keyプロパティを指定したli要素を返します。

li要素には、各フルーツのnamecolorが含まれています。

JSX.Element[]はReactの型の一つで、Reactライブラリの中で定義されています。

FruitList関数コンポーネントは、JSX.Element[]の型を返すことが期待されており、JSX構文を使用してUIをレンダリングしています。

そのため、TypeScriptはこの関数がJSX.Element[]の型を返すと推論し、型チェックを行います。

したがって、上記のコードではJSX.Element[]の型を明示的に定義する必要はありません。

このように、JSX.Element[]を使うことで、Reactで複数の要素を効率的にレンダリングすることができます。

ですが、もちろん明示的にJSX.Element[]の型を定義する必要がある場合があります。

例えば、下記のような場合です。

返される要素がJSX.Element[]ではない場合

たとえば、Reactコンポーネントが単一の要素を返すように定義されている場合、型推論が期待通りに機能しない場合があります。

この場合、返される要素の型をJSX.Element[]として明示的に定義する必要があります。

ネストされたコンポーネントの型が異なる場合

ネストされたコンポーネントの型がJSX.Element[]と一致しない場合、明示的に型を定義する必要があります。

外部ライブラリで定義されたコンポーネントを使用する場合

外部ライブラリで定義されたコンポーネントを使用する場合、そのコンポーネントの型がJSX.Element[]と一致しない場合があります。

この場合、明示的に型を定義する必要があります。

以上のような場合、JSX.Element[]の型を明示的に定義することで、TypeScriptはコードをより厳密にチェックできるようになります。

JSX.Element | JSX.Element []

一方、JSX.Element | JSX.Element[]は、単一または複数のJSX要素を表します。

この型は、コンポーネントのプロパティで単一の要素または複数の要素の配列を受け取ることができる場合に使用されます。

ただし、文字列や数字などのプリミティブ型は許可されず、JSX要素のみが許可されます。

例えば、MessageコンポーネントchildrenプロパティはJSX.Element | JSX.Element[]型であり、単一のJSX要素やJSX要素の配列を受け取ることができます。

これを使って、Reactアプリケーションでメッセージを表示する場合、下記のように記述することができます。

interface MessageProps {
  children: JSX.Element | JSX.Element[];
  notice: boolean;
};

function Message({ children, notice }: MessageProps) {
  return (
    <div>
      {notice ? 'お知らせ: ' : 'メッセージ: '}
      {children}
    </div>
  );
}

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Message notice={false}>
          <span>Hello World. </span>
          <span>こちらは、複数のJSX要素を許可します。</span>
          <i>アイコン</i>
        </Message>
      </header>
    </div>
  );
}

上記のコードでは、Messageコンポーネントを定義し、childrenプロパティで受け取った要素を表示しています。

Appコンポーネントでは、Messageコンポーネントに複数のJSX要素を渡しています。

Messageコンポーネントは、受け取ったnoticeプロパティに応じてメッセージを表示するようになっています。

これにより、Reactアプリケーションで複数の要素を組み合わせたメッセージを表示することができます。

上記以外の、選択できる型はございます。

React.ReactChild

1つのReact要素、文字列、または数値を許可します。

import React, { ReactChild } from 'react';

interface GreetingProps {
  children: ReactChild;
}

function Greeting({ children }: GreetingProps) {
  return <div>Hello, {children}!</div>;
}

上記の例では、Greetingコンポーネントchildrenプロパティが渡されます。

childrenプロパティは、ReactChild型であり、文字列、数値、React要素など、さまざまな種類の要素を受け取ることができます。

Greetingコンポーネントでは、childrenプロパティを含むdiv要素を返します。

childrenプロパティの値が、Hello,という文字列の前に表示されます。

React.ReactChild [ ]

ReactChild[]は、単一の要素や配列を含むことができるReact要素の配列を表します。

import React, { ReactChild } from 'react';

interface MessageProps {
  children: ReactChild[];
}

function Message({ children }: MessageProps) {
  return (
    <div>
      {children.map((child, index) => (
        <span key={index}>{child} </span>
      ))}
    </div>
  );
}

上記の例では、 Messageコンポーネントchildrenプロパティが渡されます。

childrenプロパティは、 ReactChild型の配列であり、文字列や数値、React要素など、複数の種類の要素を含むことができます。

Messageコンポーネントでは、 childrenプロパティの各要素をレンダリングするために、mapメソッドを使用しています。

mapメソッドは、各要素に対して、 keyプロパティを指定した span要素を返します。

この、 span要素には、各子要素が含まれています。

上記のように、 ReactChild[]を使うことで、Reactで複数の要素を効率的にレンダリングすることができます。

React.ReactChild | React.ReactChild [ ]

単一の要素または要素の配列を含むReact要素を表します。

import React, { ReactChild } from 'react';

interface MessageProps {
  children: ReactChild | ReactChild[];
}

function Message({ children }: MessageProps) {
  return (
    <div>
      {Array.isArray(children)
        ? children.map((child, index) => <span key={index}>{child} </span>)
        : children}
    </div>
  );
}

上記の例では、Messageコンポーネントchildrenプロパティが渡されます。

childrenプロパティは、ReactChild型またはReactChild型の配列であり、文字列や数値、React要素など、複数の種類の要素を含むことができます。

Messageコンポーネントでは、childrenプロパティの型によって、単一の要素をレンダリングするか、配列をmapメソッドで反復処理してレンダリングするかを判断しています。

ReactのisArrary関数を使用して、childrenが配列であるかどうかを判定し、配列の場合はmapメソッドを使用して各要素をレンダリングします。

単一の要素の場合は、childrenを直接レンダリングします。

上記のように、ReactChild | ReactChild[]を使うことで、Reactで単一の要素または複数の要素をレンダリングすることができます。

React.ReactNode

数の子、文字列、数値、フラグメント、ポータルを許可します。

import React from 'react';

interface MessageProps {
  message: React.ReactNode;
}

function Message({ message }: MessageProps) {
  return (
    <div>
      <h1>Welcome to the world of React and TypeScript.</h1>
      {message}
    </div>
  );
}

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Message message={<h3>ReactNode</h3>} />
      </header>
    </div>
  );
}

See the Pen React with Typescript Props React.ReactNode by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


上記の例では、Messageコンポーネントmessageプロパティが渡されます。

messageプロパティは、React.ReactNode型であり、文字列や数値、React要素など、複数の種類の要素を含むことができます。

Messageコンポーネントでは、messageプロパティを直接レンダリングしています。

上記のように、React.ReactNodeを使うことで、Reactでさまざまな種類の要素をレンダリングすることができます。

React.ReactNode | React.ReactNode[ ]

React.ReactNodeは、Reactコンポーネントレンダリング可能な全てのオブジェクトを表す TypeScriptの型定義です。

これは、文字列、数値、Reactコンポーネントnullundefined、またはこれらの型の配列のいずれかを表すことができます。 

一方、React.ReactNode[]は、ReactNodeの配列を表します。

import React from 'react';

interface ExampleComponentProps {
  text: React.ReactNode;
  list: React.ReactNode[];
}

function ExampleComponent({ text, list }: ExampleComponentProps) {
  return (
    <div>
      <p>{text}</p>
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

function App() {
  return (
    <ExampleComponent
      text="Hello, world!"
      list={[1, 2, 3, 4].map((num) => (
        <span>{num}</span>
      ))}
    />
  );
}

上記では、ExampleComponent関数コンポーネントのプロパティには、 React.ReactNode型のtextプロパティと React.ReactNode[]型のlistプロパティが含まれています。

App 関数コンポーネントは、ExampleComponenttextlistを渡します。

listは、数字の配列を map() メソッドでループし、各要素を<span>要素として含む配列に変換されます。

React childrenのおさらいも含めて、他の例でも見ていきましょう。

type Props = {
  content: string,
  children: JSX.Element;
};

const Child = ({ content, children }: Props) => (
  <div>
    <h1>{content}</h1>
    {children}
  </div>
);

オプションにしたい場合は、型注釈の前にクエスチョンマーク( ? )を付けます。

type Props = {
content: string,
children?: JSX.Element;

};

タグなしでPropsオブジェクトを作成できることをコンパイラに通知します。

JSX.Elementは、子要素が単一のReact要素であることが要求される場合はよいでしょう。

しかし、複数の子要素は許しません。

そこで、下記のように調整します。

type Props = {
content: string,
children?: JSX.Element | JSX.Element[];
};

ですが、JSX.Elementの欠点は文字列を許さないことです。

そこで、ユニオン型に文字列を追加することができます。

type Props = {
  content: string;
  children:
    | JSX.Element
    | JSX.Element[]
    | string
    | string[];
};

ですが、数字についてはどうでしょうか?

幸いなことに、React.ChildというReact要素は、文字列、数字を含む標準の型があります。

そこで、下記のように子要素の型を広げることができます。

type Props = {
  content: string;
  children?:
    | React.ReactChild
    | React.ReactChild[];
};

React.ReactChild|React.ReactChild[]は、必要な値の幅を与えてくれますが、少し冗長となってます。

ですのでReactNodeは、より簡潔なオプションです。

type Props = {
  content: string;
  children?: React.ReactNode;
};

ReactNodeでは、複数の要素、文字列、数値、フラグメント、ポータルを使用できます。

つまり、children propsに渡すことができるすべての要素の型を表します。

他の例でも、見てみましょう。

下記のコードでは、ReactのReactNode型を使用して、子コンポーネント内でprops.childrenの型を正しく指定しています。

これにより、子コンポーネントprops.childrenを使用する場合に、その値の型が適切に推論され、親コンポーネントから渡された要素が子コンポーネント内で適切に型付けされるようになります。

import React, { ReactNode } from 'react';

type Props = {
  children: ReactNode;
}

function ChildComponent(props: Props) {
  return (
    <div>
      <h2>Child Component</h2>
      {props.children}
    </div>
  );
}

これにより、親コンポーネントから渡された要素が、子コンポーネント内で適切に型付けされ、コンポーネント間で正しく伝達されるようになります。

ReactのReactNode型は、複数の要素、文字列、数値、フラグメント、ポータルなど、子要素として使用できるすべての要素の型を表します。

したがって、props.childrenに渡される要素がどのような型であっても、ReactNode型を使用することで、その値を適切に型付けすることができます。

型エラーの検証

TypeScriptを使用することで、ReactアプリケーションにおいてPropsの間違ったセットや値の型に対して、コンパイル時に警告を表示させることができます。

例えば、Propsにstring型が指定されているプロパティに対して数値を渡した場合、TypeScriptはエラーをスローし、このバグを早期に発見することができます。

下記の例では、MyMessageコンポーネントに、messageプロパティに数値型の値が渡されていますが、Propsのインターフェースでstring型が指定されているため、TypeScriptはコンパイルエラーをスローします。

function App() {
  return (
    <div className="App">
      <header className="App-header">

<MyMessage message={100} />

      </header>
    </div>
  );
}

interface MessageProps {
  message: string;
};

const MyMessage = (props: MessageProps) => {
  return <div>Welcome to the {props.message} of React and TypeScript. </div>;
}

上記のように、TypeScriptによってコンパイル時にエラーが検出されるため、このエラーはコードベースに隠れることなく、開発中に早期に修正することができます。

また、Propsの必要なすべてのプロパティを渡さない場合も、コンパイルエラーが発生します。

読み取り専用プロパティの指定

interfaceのプロパティを読み取り専用としてマークすることもできます。

これを行うには、プロパティのキーの前にreadonlyキーワードを追加します。

interface Props {
  content: string;
  totalCost: number;
  readonly dateCreated: Date;
};

読み取り専用プロパティは、オブジェクトが最初に作成されたときにのみ変更可能であり、再割り当てしようとするとコンパイラはエラーをスローします。

このように、読み取り専用プロパティを使用することで、不必要にプロパティが変更されるのを防止し、安全なコーディングを実現することができます。

React.FCとReact.VFCについて

React.FCとReact.VFCは、Reactで関数コンポーネントを記述するための汎用的なインターフェースです。

React.FCはFunctional Componentの略で、アロー関数を使って書かれたコンポーネントの標準的な型です。

React.VFCはVoid Function Componentの略で、戻り値がvoidである関数コンポーネントの型です。

以前は、React.FCは常にコンポーネントにchildren propsを含めていましたが、React 18のリリース以降は含まれなくなりました。

つまり、React.FCを使用していても、children propsを明示的に指定しなければなりません。

一方、React.VFCは常に明示的にchildren propsを含めませんでした。

しかし、React 18のリリース以降、React.FCには暗黙の子が含まれなくなり、React.VFCはCRAのテンプレートで非推奨になり削除されました。

現在、children propsを含むかどうかはReact.FCとReact.VFCの違いではなく、コンポーネントの実装によって異なります。

そのため、React.FCとReact.VFCのどちらを使用するかは好みの問題になります。

パフォーマンスへの影響は大きくはありませんが、React.FCおよびReact.VFCは、特にコードベースが大規模になる場合には制限と欠点があります。

したがって、これらの型に依存せず、状況に応じて最適な型を選択することが重要です。

初学者の方は、これらの型について無理に学ぶ必要はありません。

ただし、これらの型が使用されているコードを読む必要がある場合は、その意味を理解しておくことが役立ちます。

最後に

TypeScriptは、Reactでの開発において優れたツール機能と型の安全機能を提供しています。

そのため、Reactで多くの型付けや定型コードを記述する必要はありません。

Reactでは、コンポーネントが非常に重要であり、必要なものが数回のキーストロークで揃います。

そして、ReactコンポーネントはTypeScriptから大きな恩恵を受けることができます。

TypeScriptの型システムは、コンポーネントのPropsを検証する際に特に役立ちます。

この機能により、入力値が適切かどうかを事前に確認することができます。

以上が本日の記事の内容です。

最後まで読んで頂きありがとうございます。

この記事が役に立ったら、ブックマークと共有をしていただけると幸いです。

プライバシーポリシー

© 2023 Icons8 LLC. All rights reserved.

© [deve.K], 2023. React logo is a trademark of Facebook, Inc. JavaScript is a trademark of Oracle Corporation and/or its affiliates. jQuery and the jQuery logo are trademarks of the JS Foundation. TypeScript and the TypeScript logo are trademarks of the Microsoft Corporation. Next.js and the Next.js logo are trademarks of Vercel, Inc. Firebase and the Firebase logo are trademarks of Google LLC. All logos edited by [deve.K].