maesblog

Reactでリスト(配列)のFilter機能とSort機能を実装する

Reactである配列を元にリストを表示させることは多いかと思います。双方向データバインディングのAngularだと、このリストを簡単に絞り込んだりソートしたりすることができますが、Reactでリストを絞り込んだり、並べ替えたりする場合は、どうすればよいのかということを試してみました。自分なりの実装方法をまとめてみたのでご紹介します。

サンプル

まずはサンプルをご確認ください。keyとvaluを持ったオブジェクトの配列を、入力フォームに入力したワードで絞り込んだり、さらにkeyとvalueを昇順、降順で並び変えたりすることができます。これをReactで実装しています。

JavaScriptで配列の絞り込み機能を実装する

まずはReactで絞り込み機能を実装する方法を説明する前に、一般的なJavaScriptでの絞り込み機能を実装する方法を説明します。

JavaScriptでは配列の要素を絞り込むためのメソッドが用意されています。

array.filter(callback[, thisObject]);

filter()メソッドに指定する引数は以下のようになっています。

callback
返される配列にarrayの要素を含めるかどうかを判断するために呼び出すコールバック関数。callbackは以下の3つの引数で実行されます。
callback(array[i], i, array);
thisObject
callbackを呼び出す際にthisとして使用するオブジェクトの値(オプション)

filter()メソッドは、配列の各要素に対してcallback関数を1回ずつ実行します。そしてcallback関数がtrue、またはtrueに変換される値を返したarrayのインデックスiの要素だけを元に新たな配列を作成します。新たに作られた配列はfilter()メソッドのreturn値として返されます。

例えば、filter()メソッドを使って、いくつかの数値を含んだ配列から10以上の数字だけを取り込んだ配列を作る場合は以下のようにします。

var list = [1, 4, 30, 140, 54, 29, 8];

var filteredList = list.filter(function (item, index, array) {
  return (item >= 10);
});

console.log(filteredList); => [30, 140, 54, 29]

[参考]

Reactで絞り込み機能を実装する際も、このJavaScriptのfilter()メソッドを使うことになります。

AngularJSで実装すると

ちなみに、Reactでの実装方法を説明する前に、参考としてAnglularJSで絞り込み機能を実装する場合の例を挙げておきます。AnglularJSを使うと以下のようにシンプルに書くことができます。AngularJS側でほとんどの処理が行われるようになっているので、実装は配列を用意するのと、タグにちょっとした属性を付けるだけで済んでしまいます。

<!doctype html>

<html ng-app>

<head>
  <meta charset="utf-8">
  <title>Filter by AngularJS</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.29/angular.min.js"></script>
</head>

<body>
<div ng-controller="ctrList">
  <input ng-model="value" />
  <p ng-repeat="item in list | filter:value">{{item.id}}: {{item.name}}</p>
</div>
<script>
function ctrList($scope) {
  $scope.list = [
    { id: 1, name: 'foo' },
    { id: 2, name: 'bar' },
    { id: 3, name: 'baz' },
    { id: 4, name: 'qux' },
    { id: 5, name: 'quux' },
    { id: 6, name: 'foobar' }
  ];
};
</script>
</body>

</html>
AngularJS(v1.2)で絞り込み機能を実装した場合

AngularJSでは以上のようにとても簡単に絞り込み機能を実装することができますが、Reactではどのように実装していくことになるでしょうか。早速以下よりReactでの実装方法を説明していきます。

Reactで配列の絞り込み機能を実装する

それでは、早速機能の実装について説明していこうと思います。これから先は、ES6の構文で書いていきます。ReactのES6での書き方については、以下を参照してください。

Reactで配列を表示するコンポーネントを作る

絞り込み機能を実装するに当たって、機能を実装するための配列を表示するコンポーネントを作ります。

reactとreact-domを読み込みます。

import React, { Component } from 'react';
import { render } from 'react-dom';

idとnameを持ったオブジェクトの配列dataを用意し、この後作っていくAppコンポーネントに渡すようにします。

// idとnameを持ったオブジェクトの配列を作成
let data = [
  { id: 1, name: 'foo' },
  { id: 2, name: 'bar' },
  { id: 3, name: 'baz' },
  { id: 4, name: 'qux' },
  { id: 5, name: 'quux' },
  { id: 6, name: 'foobar' }
];

render(
  // 配列をAppコンポーネントに渡す
  <App data={data} />, document.querySelector('.content')
);

今回の機能の親コンポーネントとなるAppコンポーネントを作ります。上記で渡された配列をpropで受け取り、初期state値として設定します。続いて、そのstateを通して配列をmapして動的にVirtualDOMを作るようにします。

class App extends Component {
  // Appに渡された配列をpropで受け取り、初期state値として設定
  constructor(props) {
    super(props);
    this.state = {
      data: props.data
    };
  }
  render() {
    // stateを通して配列をmapして動的にVirtualDOMを作成し、list変数に格納
    let list = this.state.data.map((data) => (
      <li key={data.id}>
        {data.id}: {data.name}
      </li>
    ));
    return (
      <div>
        <ul>
          {/* list変数を展開 */}
          {list}
        </ul>
      </div>
    );
  }
}
// propの型を指定
App.propTypes = {
  data: React.PropTypes.array.isRequired
};

これで用意した配列を表示するコンポーネントは完成です。このコンポーネントに絞り込み機能を実装していきます。

Reactで配列を絞り込む機能を実装する

Appコンポーネント内に配列を絞り込むためのhandleFilterVal()メソッドを実装します。上記で説明したJavaScriptのfilter()メソッドを使ってpropで受け取った配列を絞り込むようにし、絞り込んだ配列(filter()メソッドでreturnされた配列)をsetState()してstateにセットします。

class App extends Component {

/* 省略 */

  handleFilterVal(val) {
    // JavaScriptのfilter()メソッドで絞り込み、絞り込んだ配列をline変数に格納
    const line = this.props.data.filter((item) => (
      // idまたはnameにキーワードが含まれていればtrueを返す
      item.id.toString().indexOf(val) >= 0
      || item.name.toLowerCase().indexOf(val) >= 0
    ));
    // setStateを実行
    this.setState({
      data: line
    });
  }

/* 省略 */

}
Appコンポーネント

ここでのポイントは、filter()メソッドを実行する配列を、this.state.dataではなくthis.props.dataとすることです。常に完全な配列をチェックさせることで、入力されたキーワードが含まれていない場合、表示を元の配列に戻すような動きを実現させることができるようになります。

絞り込み用の機能を実装したら、次に配列を絞り込むためのキーワードを入力するための入力フォームを実装します。Appコンポーネントに子コンポーネントとしてFormコンポーネントを追加し、上記で定義したhandleFilterVal()メソッドを渡すようにします。

class App extends Component {

/* 省略 */

  render() {

    /* 省略 */

    return (
      <div>
        {/* Formコンポーネントを追加し、上記で定義したhandleFilterVal()メソッドを渡す */}
        <Form onFilterVal={this.handleFilterVal.bind(this)} />
        <ul>
          {list}
        </ul>
      </div>
    );
  }

/* 省略 */

}

Formコンポーネントを作り、配列を絞り込むためのキーワードを入力するためのフォームを実装します。キーワードを入力するためのinput要素と入力された値を受け取り親コンポーネントから受け取ったhandleFilterValを実行するためのメンバメソッド_filterVal()を用意します。

class Form extends Component {
  _filterVal() {
    // refsを通してinput要素に入力された値を取得
    const val = this.refs.myinput.value;
    // propsを通して受け取ったonFilterVal()メソッド(handleFilterVal()メソッド)を実行
    this.props.onFilterVal(val);
  }
  render() {
    return (
      <div>
        <span style={{ marginRight: '8px', fontSize: '12px' }}
          キーワードで絞り込む:
        </span>
        {/* 配列を絞り込むための値を入力するためのinput要素 */}
        <input
          type="text"
          ref="myinput"
          defaultValue=""
          onKeyUp={this._filterVal.bind(this)}
        />
      </div>
    );
  }
}
// propの型を指定
Form.propTypes = {
  onFilterVal: React.PropTypes.func.isRequired
};

input要素は、いわゆるReactによって状態を管理されないUncontrolled Componentsとして実装しています。そのため初期値をdefaultValueで指定しています。

Reactで配列を絞り込む機能の実装はこれで完了です。引き続きソート機能を実装する方法を以下より説明していきます。

JavaScriptで配列のソート機能を実装する

まず、Reactでソート機能を実装する方法を説明する前に、一般的なJavaScriptでのソート機能を実装する方法を説明します。

JavaScriptでは配列の要素をソートするためのメソッドが用意されています。

array.sort(compareFunction);

sort()メソッドに指定する引数は以下のようになっています。

compareFunction

ソート順を指定するための関数を指定します(オプション)。省略した場合は、配列の要素はアルファベット順(文字エンコーディングによって決定される順序)にソートします。配列はその場でソートされ、配列のコピーは作成されません。

compareFunction関数は、2つの引数aとbを取り、以下のいずれかを返させるようにします。
compareFunction(a, b);

  • aがbより前に現れるべき場合: compareFunction(a, b)のreturn値は‘0未満の値’
  • bがaより前に現れるべき場合: compareFunction(a, b)のreturn値は‘0より大きい値’
  • aとbが等しい場合: compareFunction(a, b)のreturn値は‘0’

compareFunction関数は以下のような形式となります。

function compare(a, b) {
  if (aがbより前に現れるべき場合) {
     return -1;
  }
  if (bがaより前に現れるべき場合) {
     return 1;
  }
  // aとbが等しい場合
  return 0;
}

例えば、sort()メソッドを使って、いくつかのkeyとvalueを含んだオブジェクトを要素にもった配列を、keyのidで昇順にソートする場合と、valueのnameで降順にソートする場合は以下のようにします。

var list = [
  { id: 1, name: 'foo' },
  { id: 2, name: 'bar' },
  { id: 3, name: 'baz' },
  { id: 4, name: 'qux' },
  { id: 5, name: 'quux' },
  { id: 6, name: 'foobar' }
];

// idを昇順にソートする場合
list.sort(function (a, b) {
  if (a.id < b.id) {
    return -1;
  }
  if (a.id > b.id) {
    return 1;
  }
  return 0;
});

// nameを降順にソートする場合
list.sort(function (a, b) {
  if (a.name < b.name) {
    return 1;
  }
  if (a.name > b.name) {
    return -1;
  }
  return 0;
});

[参考]

Reactで配列のソート機能を実装する

Appコンポーネント内に配列をソートするためのhandleSortByAscend()メソッドとhandleSortByDescend()メソッドを用意し、それぞれ配列を昇順と降順に並び替える機能を定義します。上記で説明したJavaScriptのsort()メソッドを使ってstateに格納されている配列をソートするようにし、ソートした配列をsetState()して再びstateにセットします。

class App extends Component {

/* 省略 */

  // 昇順で並び替えるメソッドを定義
  handleSortByAscend(key) {
    const line = this.state.data.sort((a, b) => {
      if (a[key] < b[key]) return -1;
      if (a[key] > b[key]) return 1;
      return 0;
    });
    this.setState({
      data: line
    });
  }
  // 降順で並び替えるメソッドを定義
  handleSortByDescend(key) {
    const line = this.state.data.sort((a, b) => {
      if (a[key] < b[key]) return 1;
      if (a[key] > b[key]) return -1;
      return 0;
    });
    this.setState({
      data: line
    });
  }

/* 省略 */

}
Appコンポーネント

配列の要素はオブジェクトになっているので、それぞれのメソッドの引数にはオブジェクトのkeyをセットするようにし、引数で受け取ったkeyの値をソートできるようにしておきます。それから、絞り込み機能ではthis.props.dataに対してfilter()メソッドを実行しましたが、こちらではthis.state.dataに対してsotr()メソッドを実行するようにしています。配列を絞り込んだ状態でソートできるようにするためです。

ソート用の機能を実装したら、次に配列をソートするためのボタンを実装します。Appコンポーネントに子コンポーネントとしてSortButtonコンポーネントを追加し、上記で定義したhandleSortByAscend()メソッドとhandleSortByDescend()メソッドを渡すようにします。

class App extends Component {

/* 省略 */

  render() {

  /* 省略 */

    return (
      <div>
        <Form onFilterVal={this.handleFilterVal.bind(this)} />
        {/* SortButtonコンポーネントを追加し、上記で定義したhandleSortByAscend()メソッドとhandleSortByDescend()メソッドを渡す */}
        <SortButton
          onSortByAscend={this.handleSortByAscend.bind(this)}
          onSortByDescend={this.handleSortByDescend.bind(this)}
        />
        <ul>
          {list}
        </ul>
      </div>
    );
  }

/* 省略 */

}

SortButtonコンポーネントを作り、配列をソートするためのボタンを実装します。昇順、降順に並び替えるためのbutton要素と、それらのボタンがクリックされた際に親コンポーネントから受け取ったhandleSortByAscend()メソッドとhandleSortByDescend()メソッドを実行するためのメンバメソッド_sortByAscend()_sortByDescend()を用意します。

class SortButton extends Component {
  _sortByAscend(e) {
    e.preventDefault();
    // button要素に設定したvalue値を引数にセットし、handleSortByAscend()メソッドを実行
    this.props.handleSortByAscend(e.target.value);
  }
  _sortByDescend(e) {
    e.preventDefault();
    // button要素に設定したvalue値を引数にセットし、handleSortByDescend()メソッドを実行
    this.props.handleSortByDescend(e.target.value);
  }
  render() {
    return (
      <div>
        <div>
          <span style={{ marginRight: '8px', fontSize: '12px' }}>
            idでソート:
          </span>
          {/* idで昇順にソートするボタン(valueにidを設定) */}
          <button onClick={this._sortByAscend.bind(this)} value="id">
            昇順
          </button>
          {/* idで降順にソートするボタン(valueにidを設定) */}
          <button onClick={this._sortByDescend.bind(this)} value="id">
            降順
          </button>
        </div>
        <div>
          <span style={{ marginRight: '8px', fontSize: '12px' }}>
            nameでソート:
          </span>
          {/* nameで昇順にソートするボタン(valueにnameを設定) */}
          <button onClick={this._sortByAscend.bind(this)} value="name">
            昇順
          </button>
          {/* nameで降順にソートするボタン(valueにnameを設定) */}
          <button onClick={this._sortByDescend.bind(this)} value="name">
            降順
          </button>
        </div>
      </div>
    );
  }
}
// propの型を指定
SortButton.propTypes = {
  handleSortByAscend: React.PropTypes.func.isRequired,
  handleSortByDescend: React.PropTypes.func.isRequired
};

Appコンポーネント内で定義したhandleSortByAscend()メソッドとhandleSortByDescend()メソッドが何を対象としてソートするか判定できるように、button要素のvalue値に配列のオブジェクトのkeyの値をセットしておきます。

Reactで配列をソートする機能の実装はこれで完了です。

ソースコード

最後にソースを一つにまとめると以下のようになります。

import React, { Component } from 'react';
import { render } from 'react-dom';

class App extends Component {
  // Appに渡された配列をpropで受け取り、初期state値として設定
  constructor(props) {
    super(props);
    this.state = {
      data: props.data
    };
  }
  handleFilterVal(val) {
    // JavaScriptのfilter()メソッドで絞り込み、絞り込んだ配列をline変数に格納
    const line = this.props.data.filter((item) => (
      // idまたはnameにキーワードが含まれていればtrueを返す
      item.id.toString().indexOf(val) >= 0
      || item.name.toLowerCase().indexOf(val) >= 0
    ));
    // setStateを実行
    this.setState({
      data: line
    });
  }
  // 昇順で並び替えるメソッドを定義
  handleSortByAscend(key) {
    const line = this.state.data.sort((a, b) => {
      if (a[key] < b[key]) return -1;
      if (a[key] > b[key]) return 1;
      return 0;
    });
    this.setState({
      data: line
    });
  }
  // 降順で並び替えるメソッドを定義
  handleSortByDescend(key) {
    const line = this.state.data.sort((a, b) => {
      if (a[key] < b[key]) return 1;
      if (a[key] > b[key]) return -1;
      return 0;
    });
    this.setState({
      data: line
    });
  }
  render() {
    // stateを通して配列をmapして動的にVirtualDOMを作成し、list変数に格納
    let list = this.state.data.map((data) => (
      <li key={data.id}>
        {data.id}: {data.name}
      </li>
    ));
    return (
      <div>
      <div>
        <Form onFilterVal={this.handleFilterVal.bind(this)} />
        {/* SortButtonコンポーネントを追加し、上記で定義したhandleSortByAscend()メソッドとhandleSortByDescend()メソッドを渡す */}
        <SortButton
          onSortByAscend={this.handleSortByAscend.bind(this)}
          onSortByDescend={this.handleSortByDescend.bind(this)}
        />
        <ul>
          {list}
        </ul>
      </div>
    );
  }
}
// propの型を指定
App.propTypes = {
  data: React.PropTypes.array.isRequired
};

class Form extends Component {
  _filterVal() {
    // refsを通してinput要素に入力された値を取得
    const val = this.refs.myinput.value;
    // propsを通して受け取ったonFilterVal()メソッド(handleFilterVal()メソッド)を実行
    this.props.onFilterVal(val);
  }
  render() {
    return (
      <div>
        <span style={{ marginRight: '8px', fontSize: '12px' }}
          キーワードで絞り込む:
        </span>
        {/* 配列を絞り込むための値を入力するためのinput要素 */}
        <input
          type="text"
          ref="myinput"
          defaultValue=""
          onKeyUp={this._filterVal.bind(this)}
        />
      </div>
    );
  }
}
// propの型を指定
Form.propTypes = {
  onFilterVal: React.PropTypes.func.isRequired
};
class SortButton extends Component {
  _sortByAscend(e) {
    e.preventDefault();
    // button要素に設定したvalue値を引数にセットし、handleSortByAscend()メソッドを実行
    this.props.handleSortByAscend(e.target.value);
  }
  _sortByDescend(e) {
    e.preventDefault();
    // button要素に設定したvalue値を引数にセットし、handleSortByDescend()メソッドを実行
    this.props.handleSortByDescend(e.target.value);
  }
  render() {
    return (
      <div>
        <div>
          <span style={{ marginRight: '8px', fontSize: '12px' }}>
            idでソート:
          </span>
          {/* idで昇順にソートするボタン(valueにidを設定) */}
          <button onClick={this._sortByAscend.bind(this)} value="id">
            昇順
          </button>
          {/* idで降順にソートするボタン(valueにidを設定) */}
          <button onClick={this._sortByDescend.bind(this)} value="id">
            降順
          </button>
        </div>
        <div>
          <span style={{ marginRight: '8px', fontSize: '12px' }}>
            nameでソート:
          </span>
          {/* nameで昇順にソートするボタン(valueにnameを設定) */}
          <button onClick={this._sortByAscend.bind(this)} value="name">
            昇順
          </button>
          {/* nameで降順にソートするボタン(valueにnameを設定) */}
          <button onClick={this._sortByDescend.bind(this)} value="name">
            降順
          </button>
        </div>
      </div>
    );
  }
}
// propの型を指定
SortButton.propTypes = {
  handleSortByAscend: React.PropTypes.func.isRequired,
  handleSortByDescend: React.PropTypes.func.isRequired
};

// idとnameを持ったオブジェクトの配列を作成
let data = [
  { id: 1, name: 'foo' },
  { id: 2, name: 'bar' },
  { id: 3, name: 'baz' },
  { id: 4, name: 'qux' },
  { id: 5, name: 'quux' },
  { id: 6, name: 'foobar' }
];

render(
  // 配列をAppコンポーネントに渡す
  <App data={data} />, document.querySelector('.content')
);

GitHubにもアップしていますので、併せてご確認ください。

まとめ

双方向データバインディングのAngularJSと比べると、Reactでリストの絞り込みやソートの機能を実装するのはだいぶ大変だったりします。ただ、Reactの基本的なデータの流れとそれに伴うUIの実装方法さえ抑えておけば、問題なく実装できるのではないかと思います。実装していて思ったのは、JavaScriptのArrayオブジェクトのfilter()メソッドやsort()メソッドをいかに使うかが肝になってきたりするので、Reactというよりも、この辺りの使い方を覚えておくことも大事なのかなと思いました。

入門 React ―コンポーネントベースのWebフロントエンド開発
  • 『入門 React ―コンポーネントベースのWebフロントエンド開発』
  • 著者: Frankie Bagnardi, Jonathan Beebe, Richard Feldman, Tom Hallett, Simon HØjberg, Karl Mikkelsen, 宮崎 空(翻訳)
  • 出版社: オライリージャパン
  • 発売日: 2015年4月3日
  • ISBN: 978-4873117195

関連記事

コメント

  • 必須

コメント