【初心者向け】Reactでmapを使って配列の内容を一覧表示する方法と注意点

2020年5月16日土曜日

Web技術 プログラミング

Reactを使って開発をする場合は、JSXという構文を用いる場合がほとんどだと思います。

JSXで書かれたプログラムを見ていると、以下のような書き方を目にすることがあるかも知れません。

class App extends React.Component {
    render() {
        const arr = ["リンゴ", "パイナップル", "ペン"];
        return (
            <ul>
                {arr.map((fruit, i) => <li key={i}>{fruit}</li>)}
            </ul>
        );
    }
}

配列のmapメソッドが用いられていますね。

上記のReactコンポーネントは、ブラウザ上では以下のように表示されます。


配列の中身が、一覧表示されています。

こちらの記事では、上記のように、mapメソッドを用いてJavaScriptの配列の中身を描画する方法と、その注意点について記載していきたいと思います。

(実際に手元でソースコードを動かしながら見ていきたい人は、この記事の最下部に、動かせるサンプルコードがあるので、そちらをローカルPCに取得して、動かしてみてください。)

なぜReactでは配列のmapメソッドを用いるか


なぜReactで配列の中身を一覧表示するときに、mapメソッドが用いられるのでしょうか。

理由は以下の2点です。
  • {}内に記述した配列の要素が並べて表示されるから
  • mapメソッドが、戻り値として新しい配列を返すから
ひとつずつ見ていきましょう。


{}内に記述した配列の要素が並べて表示される

たとえば、以下のようなコンポーネントを見てみましょう。

class App extends React.Component {
    render() {
        const arr = [<li>リンゴ</li>, <li>パイナップル</li>, <li>ペン</li>];
        return (
            <ul>
                {arr}
            </ul>
        );
    }
}


ブラウザ上に表示してみると、以下のようになります。


表示結果は、さっきと一緒ですね。

ソースコードで何をやっているのかを見てみると、

const arr = [<li>リンゴ</li>, <li>パイナップル</li>, <li>ペン</li>];

の部分では、配列内に、JSXの<li>要素を、配列に突っ込んでいることが分かります。

このarrという配列を、

            <ul>
                {arr}
            </ul>

の部分で、{}内に、そのまま記載しています。

Reactでは、配列内にJSXの要素を入れて、その配列を{}内に記載するだけで、一覧表示ができるのです。

余談になりますが、ReactではなくVueを用いた場合は、v-forなどのVue独自の構文を覚えて使うことになると思います。

Reactの素晴らしいところは、そういった新しい構文を覚える必要が一切なく、あくまでも全てJavaScriptの世界の中で実現できてしまうことだと思います…

少し話が逸れましたが、Reactでは、
JSXの要素を含んだ配列を{}で囲むだけで、一覧表示ができる
という点を覚えておいて頂けたらと思います。


mapメソッドは、戻り値として新しい配列を返す


上記のような方法を使えば、Reactで一覧表示ができることが分かりました。

しかし、配列を以下のように宣言しなければいけないのは、少し面倒です。

const arr = [<li>リンゴ</li>, <li>パイナップル</li>, <li>ペン</li>];

何が面倒なのかと言うと、いちいち全ての要素を、<li>タグで括る必要があるからです。

今回は配列要素をソースにべた書きしているので、一つずつ地道に<li>タグで括ることができましたが、

たとえば

要素が1000個あるような配列の場合はどうするのでしょうか

もしくは、

JavaScriptを実行するまでは、いくつの要素が配列に格納されるか分からないような場合はどうするのでしょうか。

そこで登場するのが、JavaScriptのmapというメソッドです。

このmapというメソッドは、特にReact独自の構文などではなく、あくまでも、JavaScriptの配列のメソッドです:
  MDN web docs | Array.prototype.map() >>


このmapというメソッドは、
配列内のそれぞれの要素用いて、新しい配列を作る
ことができます。

上記のサイトの例をそのまま拝借すると、以下のような感じです。

const array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

上記のプログラムでは、
[1, 4, 9, 16]
という配列を元にして、それぞれの要素の数字を2倍した
[2, 8, 18, 32]
という、新しい配列を生成しています。

ミソとしては、元の配列そのものを変更しているわけではなく、
要素の数字を2倍した新しい配列が、戻り値として返される点です。


上述の例では、mapメソッドを用いて「それぞれの要素の数字を2倍する」という操作をしていますが、
この記事で実現したいこととしては、「それぞれの要素を<li>タグで括る」という操作になります。


それを踏まえて、この記事の一番最初に見た例を、もう一度見てみましょう:

class App extends React.Component {
    render() {
        const arr = ["リンゴ", "パイナップル", "ペン"];
        return (
            <ul>
                {arr.map((fruit, i) => <li key={i}>{fruit}</li>)}
            </ul>
        );
    }
}

上記のプログラムの「key={i}」という記述については後述しますが、
それ以外の部分は大体わかって頂けるかと思います。

{arr.map((fruit, i) => <li key={i}>{fruit}</li>)}
と記述することによって、配列に文字列として格納されている各要素を、<li>タグで括った、新しい配列を作っているのです。

その新しい配列が{}内に記載されていることによって、一覧として表示されるような仕組みです。


配列の一覧表示時の"key"について


こちらは、Reactのルールとして覚えておいて頂ければと思うのですが、

上述のkeyを記載しないと、開発環境では以下のようなエラーが表示されます。


実は、React上で{}内の配列を一覧表示する際には、keyという属性を付与することが義務付けられています。

これは、配列の要素が動的に変更された時に、変更前の配列の要素との対応関係をReact側が認識するためです… が、細かいことは気にせず、
{}を用いて配列の要素を展開する時は、keyを付ける必要があるのだ
と、覚えておけば良いと思います。

今回の例では以下のように、<li>要素の属性として、keyを付与していましたね。
{arr.map((fruit, i) => <li key={i}>{fruit}</li>)}

この、
key={i}
の「i」は、配列要素のインデクス番号です。

mapメソッドの第二引数には、各配列要素のインデクス番号が渡されてくるので、それをそのままkeyとして流用しています。

一意であれば何でも良いのですが、インデクス番号であれば、各要素で一意であることが保証されているので、使いやすいと思います。


メソッドチェーンを用いた一覧のカスタマイズ


さて、上記の内容が分かれば、いろいろな応用が可能です。

上記のmapはReact固有の構文などではなく、ただのJavaScriptの配列のメソッドですから、JavaScriptのその他のメソッドと組み合わせることができます。

たとえば、以下のようなことが可能です。

class App extends React.Component {
    render() {
        const arr = ["リンゴ", "パイナップル", "ペン"];
        return (
            <ul>
                {arr.filter(fruit => fruit.includes("ル")).map((fruit, i) => <li key={i}>{fruit}</li>)}
            </ul>
        );
    }
}

上記のソースコードでは、JavaScriptの配列のfilterメソッドを用いて、「ル」という文字を含む要素だけの配列を生成しています。
プログラムの実行結果は、以下のようになります。


パイナップルだけが表示されていますね。


以下のように記述すれば、一覧の並び替えも可能です。

class App extends React.Component {
    render() {
        const arr = ["リンゴ", "パイナップル", "ペン"];
        return (
            <ul>
                {arr.sort().map((fruit, i) => <li key={i}>{fruit}</li>)}
            </ul>
        );
    }
}

上記では、JavaScriptの配列のsortメソッドを用いて、配列要素を辞書順に並び替えています。
実行結果は以下のようになります。



並び替えられた結果が表示されていることが分かります。
(JavaScriptのsortメソッドは、引数なしの場合はUnicodeに変換した値での辞書順のソートを行う)


配列のメソッドチェーンで一覧を表示する際の注意点

さて、上記のような方法を用いれば、React上で配列の要素を展開して表示できることが分かりました。

しかし、1点注意しないといけないと思うことがあるので、記載しておきます。

それは、上記のような記述を行う場合は、
配列メソッドが、元の配列に影響を与えないか
ということを気にした方が良いという点です。


たとえば、以下のプログラムを見てみます。

class App extends React.Component {
    render() {
        const arr = ["リンゴ", "パイナップル", "ペン"];
        return (
            <div>
                ソートした一覧
                <ul>
                    {arr.sort().map((fruit, i) => <li key={i}>{fruit}</li>)}
                </ul>
                ソートしてない一覧
                <ul>
                    {arr.map((fruit, i) => <li key={i}>{fruit}</li>)}
                </ul>
            </div>
        );
    }
}

上記のプログラムでは、同じ配列を用いて、
「ソートした一覧」と「ソートしてない一覧」という、2つの一覧を表示しています。

上記のプログラムの結果は、どのように表示されるでしょうか。

よかったら、ちょっと考えてみてください。



結果は、以下のようになります。


どうでしょう。
予想通りでしょうか?

結果としては、両方ソートされてしまっています。

なぜこのような結果になるのかと言うと、JavaScriptの配列のsortというメソッドが、「元の配列を直接いじる」ようなメソッドだからです。

つまり、1つ目の一覧を表示する時にsortが行われたことによって、元の配列自体の並び順が変わってしまい、後続の処理でその配列を使う時点で、並び順が変わってしまっていたということになります。


Reactで配列を展開して一覧表示する際は、「元の配列に変更を加えてしまっていないか」という点は、気にかけながらソースを書いていく必要があるかと思います。


まとめ

如何でしたでしょうか。

「全てがJavaScriptで記述できる」というのは、Reactの素晴らしい点です。

mapというJavaScriptのメソッドを用いるだけでなく、JavaScriptに元々備わっている多様な機能をフル活用して、柔軟なプログラミングをすることができます。

その一方で、JavaScriptの理解が浅いと、思わぬ落とし穴にハマってしまうかも知れません。。。

本記事が、ReactやJavaScriptを学習中の方々の参考になれば幸いです。

ソースコードサンプル

以下は、本記事で用いたソースコードのサンプルです。

ひとつのHTMLファイル上に全て記載しているので、読者さんのPC上にindex.htmlなどの名前でファイルを作って頂いて、下記のソースコードを丸ごとコピペするだけで、動かすことができます。

ソースを編集する時は、そのHTMLファイルをエディタ(メモ帳など)で開いて編集してください。
結果を確認したいときは、HTMLファイルをウェブブラウザ(Chromeなど)で開いてみてください。

(CDNという仕組みを用いているため、Reactの初期セットアップなども必要ありません)

<!DOCTYPE html>
<html>

<head>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>

<body>
    <div id="root"></div>
    <script type="text/babel">
        class App extends React.Component {
            render() {
                const arr = ["リンゴ", "パイナップル", "ペン"];
                return (
                    <ul>
                        {arr.map((fruit, i) => <li key={i}>{fruit}</li>)}
                    </ul>
                );
            }
        }
        ReactDOM.render(<App />, document.getElementById('root'));
    </script>
</body>

</html>