React でリストを表示するには?
この記事では、React でリストを表示する方法です。リストのデータの一覧を表示する事は簡単です。しかし、このリストのデータを変更する場合はちょっと複雑です。この記事ではその辺も含めて紹介しています。
React はプログラムで表示データを作る!
前回の条件によって表示を変える場合と同じで、React を利用する場合には、基本的にプログラムで表示データを作ります。今回の、リストのデータを表示する場合も考え方は同じです。
Vue の場合には、「_v-for_」を使って、本来の HTML にはない書き方でリストの中身を表示していました。React の場合は、このような機能が HTML に相当する部分(JSX)にはないので、Typescript(Javascript)で記述する方法を使います。
色々書き方はありますが、比較的よく利用されているのは、「_map()_」という予め用意されている関数を使います。この機能を利用する事で、リストのデータを表示するための HTML に相当する記述(JSX)を作る事ができます。
以下の部分がそれに相当する部分になります。
<ul>
{todos.map(
(todo: Todo): JSX.Element => (
<li>
{todo.text}
<button className="btn btn-secondary" onClick={() => removeTodo(todo)}>
X
</button>
</li>
)
)}
</ul>
今回は、ボタンを一緒に表示して、ボタンがクリックされたら、相当するデータをリストから削除するという機能も含まれています。
以下のコードが一覧の表示と、選択されたデータの削除の機能を React で実装した例です。
import React, { useState } from "react";
import { produce } from "immer";
import "./styles/Step7.css";
interface Todo {
id: number;
text: string;
}
export default function Step7(): JSX.Element {
let id: number = 0;
const [todos, setTodos]: [
Array<Todo>,
React.Dispatch<React.SetStateAction<Array<Todo>>>
] = useState([
{ id: id++, text: "Learn JSX" },
{ id: id++, text: "Learn Typescript" },
{ id: id++, text: "Learn React" },
]);
return (
<React.Fragment>
<div>
<ul>
{todos.map((todo: Todo): JSX.Element => (
<li>
{todo.text}
<button
className="btn btn-secondary"
onClick={() => removeTodo(todo)}
>
X
</button>
</li>
))}
</ul>
</div>
</React.Fragment>
);
function removeTodo(todo: Todo): void {
const updateTodos: Array<Todo> = produce(
todos,
(draft: Array<Todo>): Array<Todo> => {
return draft.filter((t: Todo): boolean => {
return t.id !== todo.id;
});
}
);
setTodos(updateTodos);
}
}
リストのデータの更新は注意が必要!
実装例のコードをみると、ちょっと見慣れない書き方をしているのに気づかれた方もいらっしゃると思います。 実は、この指定したデータの削除のやり方が React でリストの処理をする際の一つの重要なポイントになっています。
Vue の実装では、単純にリストの指定されたデータを含まないような形で作成しておけば良かったわけですが、React で実装する場合には、一旦「_todos_」の複製(コピー)を作ってから改めて、リストを更新するというやり方をします。
これは、React では、リスト(配列)のデータの更新は、ステートで管理していても、検出されないためです。これは、リスト(配列)の場合、データを入れている「箱」を更新の監視対象にしているためで、「中身」の変更は監視されていないためです。従って、「箱」が同じだと変更されたと見なされないため表示の更新が行われません。そこで、一旦別の「箱」を用意して置いて、古い「箱」を新い「箱」に置き換える処理が必要になります。
今回は、「_immer_」という公開されているモジュールを利用してこの処理を行なっています。他にも似たような処理をしてくれるモジュールがありますが、私はこのモジュールを利用する場合が多くなっています。
import { produce } from "immer";
function removeTodo(todo: Todo): void {
const updateTodos: Array<Todo> = produce(
todos,
(draft: Array<Todo>): Array<Todo> => {
return draft.filter((t: Todo): boolean => {
return t.id !== todo.id;
});
}
);
setTodos(updateTodos);
}
これが、その処理を実際に行なっている部分です。指定されたデータの削除は、Vue で実装した場合と同じように、「_filter()_」を使って、指定されたデータ以外のデータを抜き出したリストを作成して、そのリストを新たな「_todos_」として使うような処理になっています。 「_produce()_」を使って、オリジナルの「_todos_」の複製「_draft_」を作ってこれを基に新しいデータを作っています。
ここでもちょっと面倒な React
また、ちょっとわかりにくい部分が出てきました。 React と Vue ではデータの更新を検出する仕組みが違っています。Vue はデータの「箱」の中身の更新も検出できるのに対して、React はデータの「箱」の中身の更新は検出できないので「箱ごと」入れ替えるという処理が必要になります。
このあたりも、初心者には Vue を最初に学習することをお勧めする理由です。 データの構造がきちんと理解できていれば、特に大きな問題ではありませんが、プログラムの学習を始めたばかりの初心者には、ちょっとわかりにくい部分だからです。
すでに C 言語などを学習されて使いこなしている方の場合には、理解しやすい部分です。要は「ポインタ」の考え方と同じだからです。ポインタは、データを格納している場所(「箱」)を指していると考えると理解しやすくなります。利用している言語は、どちらも Typescript(Javascript)なので実際には、プログラミング言語レベルの違いではなく、Vue や React が「裏」でどのように、データの更新を見張っているかの違いです。
React を利用する際のポイントは?
このような事をみると、React は面倒な感じがしますが、実は意外にシンプルでもあります。 ポイントは:
* データの更新を見張る必要がある場合は、「ステート」を使う
* どのような更新が検出できるかを覚えておく
* この二つをきちんと理解しておけば基本的には大きな問題ではありません。
更新の検出が箱単位で行われるお主なパターンは:
* リスト(配列)
* JSON(JavaScript Object Notation)
* クラスのオブジェクト
を抑えておけば基本的に大丈夫なはずです。
今回の例のように、「箱」に入ったデータを更新する場合は、「別の箱」を用意して、「箱ごと入れ替える」方法で更新を行えばあとは、React が面倒を見てくれます。
あとは、知っているかと慣れているかの問題です。幾つか実際に自分で実装してみるとコツが掴めるかと思います。
まとめ
この記事では、リストのデータの一覧を表示する例を紹介してみました。 リストの一覧表示自体は、書き方さえ覚えてしまえば、Vue と比べても特に難しい点はありません。
ただし、リスト(配列)や JSON を扱う場合には、データの検出のやり方が Vue とは少し違っているので、仕組みを理解していないと、思うように表示が更新されない場合があります。これは、React データを入れている「箱」単位で管理しているために、箱ごと入れ替える必要があるからです。
こうした、制限事項や Vue との違いをきちんと理解しておけば、それほど難しい事ではないので、何回か実装の練習をすれば問題はないはずです。この記事をこうした「違い」があることを知るきっかけになればと思います。