React内でリストの要素を前後の要素と入れ替えてみる

更新日時: July 11, 2020

はじめに

Reactで配列の要素を入れ替える実装方法を検索してもドラッグアンドドロップライブラリを使う実装方法や配列の sort メソッドを使う実装方法しかなく、すぐには見つかりませんでした。

そこで今回は、実装時のファイル構造をもとに、どのように振る舞いを実装したのかを紹介します。
また、実際の実装はJavaScriptの記法だけで実装ができたので難しく考える必要はありません。

実装した例は以下のリポジトリから確認できます。


サンプルの説明

やりたかった実装としては、テーブルで表示した行をユーザが選択し、その行の前後順番を操作するという動作を実現したかったです。
実際にどんな動作をするのかについては以下のイメージを参考にしてください。


また、以下の説明はリポジトリのコードを見ながら読んでいただけますと理解しやすいです。

構造

今回の実装でのファイルの構成と役割は以下になります。

.
├── index.jsx // jsxで書いた内容をhtmlにレンダリングする
├── App.jsx // stateの管理、振る舞いの定義を集めたコンポーネント(親)
├── Memes.js // 表示するためのデータ
└── parts
    ├── List.jsx // データをテーブル形式に表示する(子)
    └── ItemControl.jsx // データの順番を入れ替えるイベントを発火する(子)

基本的には親コンポーネントの App.jsx でデータ/stateの管理、振る舞いの定義をします。
子コンポーネントでは親コンポーネントから渡されたデータを選択された状態も含めて表示したり、イベント発火をする入り口として扱います。
データはレスポンスで受けた後の想定であり、 state で管理するデータの形は以下になります。

this.state = {
  memes: {受け取った配列のデータ},
  selectedId: null,
};

振る舞い

リストから要素を選択

今回の実装では、配列の中の各オブジェクトには要素を特定するためのユニークな id を指定しています。

// 例
  {
    id: 1,
    name: "ぴえん",
    category: "流行り語",
  }

テーブルから行を選択すると、親コンポーネントで定義した振る舞いまでイベントが伝播します。
定義した振る舞いでは、現在選択している行を参照して行の id を取得します。


handleCheckedItem = (e) => {
  // eはブラウザのネイティブイベントを操作するSyntheticEventオブジェクトのインスタンス
  this.setState({
    // 選択した行のidを取得し、数値型へ変換してstateを更新する
    selectedId: Number(e.currentTarget.children[0].children[0].id)
  });
}

これで、選択した行の id を親コンポーネントで管理できます。

ちなみに、親コンポーネントへ伝播したときに扱う SyntheticEvent オブジェクトのネイティブイベント属性伝播できるイベントの種類に関しては以下のページから確認ができます。


配列の要素の入れ替え

行を選択した上で、「上へ」もしくは「下へ」をクリックすると親コンポーネントで定義した振る舞いまでイベントが伝播します。
定義した振る舞いでは、クリックしたイベントのタイプをもとに、 state で管理するデータの配列の要素を入れ替えをします。

// ItemControlコンポーネント
// data-typeで配列の順番を替えるタイプを設定

<input type="button" value="上へ" data-type="up" onClick={props.moveItem} />
<input type="button" value="下へ" data-type="down" onClick={props.moveItem} />
︙

// Appコンポーネント

handleMoveItem = (e) => {
  if (!this.state.selectedId) return;

  // クリックしたボタンのタイプをタイプを取得する
  const type = e.currentTarget.dataset.type;
  // stateで管理している配列から新しい配列をコピーする
  const newList = this.state.memes.slice();

  // 選択した行のidをもとに、順番の入れ替えをするオブジェクトの配列の中でのインデックスを取得する
  const targetIndex = newList.findIndex(item => item.id === this.state.selectedId);

  switch (type) {
    case "up":
      if (this._noMoreGoUp(targetIndex, newList)) return;
      // 前のインデックスのオブジェクトと順番を入れ替える
      newList.splice(targetIndex - 1, 2, newList[targetIndex], newList[targetIndex - 1]);
      break;

    case "down":
      if (this._noMoreGoDown(targetIndex, newList)) return;
      // 後のインデックスのオブジェクトと順番を入れ替える
      newList.splice(targetIndex, 2, newList[targetIndex + 1], newList[targetIndex]);
      break;

    default:
      break;
  }

  // 順番を入れ替えた配列をstateに更新する
  this.setState({
    memes: newList
  });
}


ここでのポイントは splice メソッドを使って順番を入れ替えていることです。

配列.splice(入れ替える要素のインデックス, 第1引数から取り除く要素の数, 取り除いた要素の位置から追加する順の要素)

//case "up"
配列.splice(入れ替える要素の1個前のインデックス, 2(1個前の要素と入れ替える要素分), 入れ替える要素 1個前の要素)

//case "down"
配列.splice(入れ替える要素, 2(入れ替える要素と1個後の要素分), 1個前の要素, 入れ替える要素)

このように splice メソッドを使って要素を置き換えるときは以下の引数の順番で指定すれば、前後の入れ替えができます。

最後に

Reactを使って配列の前後要素を入れ替える実装方法を探しましたが、案外実際にコードで書いて見るとJavaScriptの配列のメソッドだけで実装ができました。

筆者のようにReactで配列の前後要素を入れ替える必要な方にこの記事が参考になりましたら幸いです。

コメントする