Skip to content

Reactで配列の要素をソートする機能

[!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 の設定・更新には hookuseState を使用します。 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 で条件分岐します。同じボタンを続けてクリックした場合は、ソートの並びを反転させたいので stateorder: 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に渡していた DATAsortedMovies に置き換えます。

/** 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>
  );
}

デモ

デモページではボタンに昇順・降順かわかる矢印を付け、アクティブ/非アクティブかで色が変わるようにしています。

Source & Demo (CodeSandbox)

参考サイト

k.ishiwata「React Hooks でリストの絞り込み検索と並び替え機能のサンプル」(参照2019-09-15)