React コンポーネントのプロパティには、文字列や数値などの単純なスカラ値だけではなく、配列などの複雑なオブジェクトを渡すことができます。 使用イメージは次のような感じです。
<MyComponent values={配列変数} />
配列型のプロパティを扱うコンポーネントを作成する
次の Books
コンポーネントは、プロパティ titles
で文字列配列を受け取り、それぞれの値を li
要素で描画します。
配列の map
メソッドを使用すると、複数の li
要素を簡単に生成することができます。
import * as React from 'react';
// Books コンポーネントのプロパティの型
export interface BooksProps {
titles: string[]
}
// Books コンポーネントの定義
export const Books: React.FC<BooksProps> = (props) => {
// titles プロパティの要素数が 0 であれば何も描画しない
if (props.titles.length == 0) return null;
// titles プロパティの値を使って、複数の li 要素を作る
const listItems = props.titles.map((title: string) =>
<li>{title}</li>
);
// ul 要素の描画
return <ul>{listItems}</ul>;
};
この Books
コンポーネントは次のような感じで使用します。
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Books } from './components/books';
const titles = ['タイトル1', 'タイトル2', 'タイトル3'];
ReactDOM.render(
<Books titles={titles} />,
document.getElementById('root')
);
これで、次のような HTML 要素が生成されます。
<ul>
<li>タイトル1</li>
<li>タイトル2</li>
<li>タイトル3</li>
</ul>
リスト要素に key を与える
上記のコードを実行すると、React は次のような警告を出力します。
Warning: Each child in a list should have a unique "key" prop.
React は 配列要素の再描画処理を効率的に行うため、要素を特定するための key
プロパティ(数値 or 文字列)を要求します。
配列の map
メソッドで複数の要素を生成するとき に、それぞれの要素の key
プロパティとして何らかのユニーク値を設定してやれば、この警告は消えます。
これは li
要素以外の要素(例えば div
要素)であっても同様です。
const listItems = props.titles.map((title: string, index: number) =>
<li key={index}>{title}</li>
);
key
プロパティの値は、同階層のリスト要素間で一意であればよいので、上記のようにリスト要素のインデックスを key
に指定することである程度うまく動作します。
ただし、この方法ではリスト要素の順番が入れ替わった場合などに効率の悪い再描画が発生します。
よりよい方法は、次のように、表示するデータが持っている一意な ID やハッシュ値を指定することです。
const listItems = books.map((book: Book) =>
<li key={book.id}>{book.title}</li>
);
このような書き方を可能にするには、元の Books
コンポーネントのプロパティで、データオブジェクト (Book
) の配列を受け取れるように修正する必要があります。
下記に修正後の全体のコードを示します。
import * as React from 'react';
// Books コンポーネントの items 配列プロパティの要素の型
export interface Book {
id: number;
title: string;
}
// Books コンポーネントのプロパティ
export interface BooksProps {
items: Book[]
}
// Books コンポーネント
export const Books: React.FC<BooksProps> = (props) => {
if (props.items.length == 0) return null;
const listItems = props.items.map((book: Book) =>
<li key={book.id}>{book.title}</li>
);
return <ul>{listItems}</ul>;
};
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Book, Books } from './components/books';
const books: Book[] = [
{ id: 100, title: 'タイトル1' },
{ id: 200, title: 'タイトル2' },
{ id: 300, title: 'タイトル3' },
];
ReactDOM.render(
<Books items={books} />,
document.getElementById('root')
);
(おまけ)コンポーネント内で自主的にデータを取得する
下記の Books
コンポーネントは、DOM 要素として追加された段階 (componentDidMount) で、自分が表示すべきデータを取得して描画を行います。
コンポーネントのライフサイクルメソッドをオーバーライドするため、関数コンポーネントではなく、クラスコンポーネントとして定義する必要があります。
ここでは固定のデータを state
に設定していますが、ネットワークからデータ取得するようなケースでも、このタイミングでデータ取得するとよいでしょう。
import * as React from 'react';
interface Book {
id: number;
title: string;
}
// Books コンポーネントの state の型
interface BooksState {
books: Book[]
}
// Books コンポーネントの定義
export class Books extends React.Component<{}, BooksState> {
constructor(props: {}) {
super(props);
// 初期状態のデータは空っぽでOK
this.state = { books: [] };
}
public render(): React.ReactNode | null {
const books = this.state.books;
if (books.length == 0) return null;
const listItems = books.map((b: Book) =>
<li key={b.id}>{b.title}</li>
);
return <ul>{listItems}</ul>;
}
// 描画準備ができたときに呼び出される
public componentDidMount() {
// 実際はここでデータフェッチを行う想定
const newBooks: Book[] = [
{ id: 100, title: 'タイトル1' },
{ id: 200, title: 'タイトル2' },
{ id: 300, title: 'タイトル3' },
];
// データ取得が完了したら state を更新して再描画
this.setState({ books: newBooks });
}
};
Books
コンポーネントは自らデータを取得するため、Books
コンポーネントを使用する側ではデータを渡す必要はありません。
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Books } from './components/books';
ReactDOM.render(<Books />, document.getElementById('root'));
関連記事
- React コンポーネント実装の基本(関数コンポーネントとクラスコンポーネント)
- React + TypeScript の環境を整える (1) 自力で webpack 設定する方法
- webpack と TypeScript を組み合わせて使用する
- Electron で Hello World (3) React を使えるようにする
- Electron で Hello World (2) TypeScript で開発できるようにする
- Node.js で GitHub REST API を使用する (@octokit/rest)
- TypeScriptの型: インタフェースのプロパティを読み取り専用にする (readonly)