[!NOTE] TypeScriptで書き換えたため加筆・修正しました。オリジナルの記事は2019/09/19に公開されました。
ブログの記事や商品の一覧ページなどに、日付順、タイトル順、カテゴリー順など特定の基準に従って並べ替えができる機能が実装されている場合があります。配列の要素を、あるプロパティを基準に昇順または降順に並べ替えをするには sort()
メソッドを使用します。
本記事ではReactでリストを表示し、ボタンをクリックすると特定のプロパティでソート(並べ替え)ができるページを作成します。ソートするプロパティ名のボタンを用意し、一度目のクリックで昇順に、続けて同じボタンをクリックすると降順になるようにします。
※ 本記事はReactでコードを書ける環境が整っていることを前提とします。
※ CSSは省略します。適宜スタイルを追加してください。
環境
- Vite
- React
- TypeScript
実現したいこと
- Reactでリストを表示するページを作成
- ボタンを設置し、特定のプロパティを基準にソート
- ボタンをクリックする度に昇順・降順を切り替える
配列のリストを表示するReactコンポーネントを作成
Listコンポーネントを作成
List.tsx
を作成して、配列の項目を表示するための List
コンポーネントを作成します。
/** List.tsx */
import React from "react";
const List = () => (
<li>
<p>ID : 1</p>
<p>The Dark Knight(2008)</p>
<p>Category : Action</p>
</li>
);
export default List;
App
コンポーネントに List
コンポーネントをimportして表示します。
/** App.tsx */
import { ReactElement } from "react";
import List from "./List";
const App = (): ReactElement => {
return (
<ul>
<List />
</ul>
);
};
export default App;
ローカルのwebサーバーを起動するとListコンポーネントの内容が表示されます。
propsを通してリストにデータを表示させる
配列を用意してApp
コンポーネントから props
を通して List
コンポーネントに渡します。配列の要素をそれぞれ表示するには map()
メソッドを使用します。
/** App.tsx */
const DATA = {
movies: [
{
id: 1,
title: "Dunkirk",
category: "Action",
year: "2017",
},
{
id: 2,
title: "Interstellar",
category: "Sci-Fi",
year: "2014",
},
],
};
export interface Movie {
id: number;
title: string;
category: string;
year: string;
}
const App = () => {
return (
<div>
<ul>
{DATA.movies.map((movie: Movie) => (
<List key={movie.id} movie={movie} />
))}
</ul>
</div>
);
};
List
コンポーネントでは、受け取った値を表示するように書き換えます。 {data}
を props
として、表示する部分を {props.data.id}
などとしても同じ表示結果になります。
/** List.tsx */
import { ReactElement } from "react";
import { Movie } from "@/src/App";
interface Props {
movie: Movie;
}
const List = ({ movie }: Props): ReactElement => (
<li>
<p>
<span>ID :</span> {movie.id}
</p>
<p>
{movie.title}({movie.year})
</p>
<p>
<span>Category :</span> {movie.category}
</p>
</li>
);
配列の各要素がリストになって表示されました。
ソート用のボタンを表示するReactコンポーネントを作成
Button.tsx
を作成し、 Button
コンポーネントを作成します。
/** Button.tsx */
const Button = (): ReactElement => <button>ID</button>;
export default Button;
Appコンポーネントでimportして button
を表示します。
/** App.tsx */
import Button from "./Button";
...
const App = () => {
return (
<div>
<Button />
<ul>
{DATA.movies.map((movie: Movie) => (
<List key= { movie.id } movie = { movie } />
))}
</ul>
</div>
);
};
ソートに使用するプロパティをボタンに渡すために、データの配列から基準になるプロパティ名のみを抽出します。指定したオブジェクトのプロパティ名の配列を取得できる Object.keys()
メソッドを使用します。
/** App.tsx */
const KEYS = Object.keys(DATA.movies[0]);
取得したプロパティ名の配列をリストと同様に props
として map()
メソッドを使用して Button
コンポーネントに渡します。
/** App.tsx */
{
KEYS.map((key: string) => <Button key={key.id} button={key} />);
}
Button
コンポーネントを props
を通して受け取った内容を表示するように書き換えます。
/** Button.tsx */
...
interface Props {
button: string;
}
const Button = ({ button }: Props) => <button>{button.toUpperCase()}</button>;
配列の各オブジェクトのプロパティ名になっている「id」「title」「category」「year」の名前のボタンが表示されました。
ソート機能の追加
ボタンをクリックする度にソートされるようにしたいので、App.tsx
にボタンをクリックした際に実行される関数を作成します。ソートに使用するプロパティ名を引数に指定します。関数は props
として Button
コンポーネントに渡します。
/** App.tsx */
const handleSort = (key: string) => {
console.log("click : " + key);
};
/** App.tsx */
<Button key={index} button={key} handleSort={handleSort} />
Button
コンポーネントでは、 props
として渡された関数を onclick
イベントに指定します。画面上のボタンをクリックすると console
に「click : プロパティ名」と表示されるのが確認できます。
/** Button.tsx */
...
interface Props {
button: string;
handleSort: (key: string) => void;
}
const Button = ({ button, handleSort }: Props) => (
<button onClick={() => handleSort(button)}>{button.toUpperCase()}</button>
);
handleSort
の中身を実際の処理に書き換えます。ボタンをクリックした際に「どのプロパティ名」を基準に「昇順 or 降順」に並び替えるか、ソートの条件を保有するための state
を設定します。 state
の設定・更新には hook
の useState
を使用します。 state
の初期値は空にします。ソート用の state
の更新は setSort
で行います。
/** App.js */
import { useState } from 'react';
export interface Sort {
key: string;
order: number | undefined;
}
const App = () => {
const [sort, setSort] = useState<Sort>({
key: "",
order: 0,
});
return(
...
);
};
同じボタンを続けてクリックしたか、直前とは異なるボタンをクリックしたかで処理を分けるため if/else
で条件分岐します。同じボタンを続けてクリックした場合は、ソートの並びを反転させたいので state
の order: 1
の数値にマイナスを付与します。直前とは異なるボタンをクリックした場合は、ソートの基準になるプロパティ名( key
)の値を更新して、昇順になるよう order: 1
を設定します。
/** App.tsx */
const handleSort = (key: string) => {
if (sort.key === key && sort.order) {
setSort({ ...sort, order: -sort.order });
} else {
setSort({
key: key,
order: 1,
});
}
};
並び替えには sort()
メソッドを使用します。昇順・降順に並べ替えるには、戻り値の数値の大小によって順番を決める比較関数を指定します。ソート後のデータを格納する変数 sortedMovies
を宣言して、 sort()
メソッドの返り値が格納されるようにします。このとき配列に変更があった場合にのみ、 sortedMovies
の値を更新したいので、hook
の useMemo
を使用します。useMemo
はメモ化された値を返す機能で、指定した配列の要素に変化があった場合にのみ値を更新します。Listに渡していた DATA
を sortedMovies
に置き換えます。
/** App.tsx */
s
import { useState, useMemo } from 'react';
...
const App = () => {
const [sort, setSort] = useState<Sort>({
key: "",
order: 0,
});
const sortedMovies = useMemo(() => {
let newSortedMovies = DATA.movies;
if (sort.key) {
newSortedMovies = newSortedMovies.sort((a, b) => {
const c = a[sort.key as keyof Movie];
const d = b[sort.key as keyof Movie];
if(c === d) {
return 0;
}
if(c > d) {
return 1 * sort.order;
}
if(c < d) {
return -1 * sort.order;
}
return 0;
});
}
return newSortedMovies;
}, [sort]);
...
return (
<div>
...
<ul>
{sortedMovies.movies.map((movie: Movie) => (
<List key= { movie.id } movie = { movie } />
))}
</ul>
</div>
);
}
デモ
デモページではボタンに昇順・降順かわかる矢印を付け、アクティブ/非アクティブかで色が変わるようにしています。
参考サイト
k.ishiwata「React Hooks でリストの絞り込み検索と並び替え機能のサンプル」(参照2019-09-15)