クモのようにコツコツと

フロントエンドエンジニア イイダリョウのブログ。略称「クモコツ」

【React】ループの書き方(for文エラー回避、配列、map())

Reactの続きです。前回は条件分岐のやってみました。今回はループ(繰り返し)です。条件分岐のif文と同様、JSXの中でfor文をそのまま書くとエラーになるので、その回避法とfor文以外の配列やmap()を使った書き方をやってみます。それでは行きましょう!

【目次】

前回記事
※参考:【React】条件分岐の書き方(if文エラー回避、論理演算子、三項演算子) - クモのようにコツコツと

Reactを習得するためにやったことまとめ
qiita.com

JSXの中ではfor文も動かない

前回の「条件分岐編」に引き続き掌田さんの「React.js & Next.js超入門」を参考に「ループ編」に入る。条件分岐はif文を使わない方法が紹介されていたが、やはりループでもfor文を使わない方法が紹介されている。

React.js&Next.js超入門

React.js&Next.js超入門

前回のif文でも触れたように、JSXの中ではfor文は使用できないためだ。

if statements and for loops are not expressions in JavaScript, so they can’t be used in JSX directly.

「ifステートメントとforループはJavaScriptの式ではないため、JSXで直接使用することはできません。」

※参考:JSX In Depth – React

しかし、if文と同様にfor文でもエラーを回避する方法があると思っているので、まずはそこからトライしてみる。

JSXでfor文:失敗例(returnの使い方に注意!)

JSX内に普通にfor文書いてみる(動かない)

まず、何も考えずにJSX内に普通にfor文を書いてみる。

See the Pen React 15 roop-5-ng1 by イイダリョウ (@i_ryo) on CodePen.

結果は…うーむ。やはり動いてないな。

HTMLコード

<section>
    <h1>はっぴいえんど</h1>
    <div class="text">読み込み中…</div>
</section>

. textのdivタグをReactで書き換えている。「読み込み中…」が表示されていると言うことはReactが動いていない。

JSコード

//DOM取得
const text = document.querySelector('.text');

//JSXに埋め込む値
const h2Text = "風街ろまん";
const pText = "(1971年発売)";

let song = [
    '抱きしめたい',
    '空いろのくれよん',
    '風をあつめて',
    '暗闇坂むささび変化',
    'はいからはくち',
    'はいから・びゅーちふる',
    '夏なんです',
    '花いちもんめ',
    'あしたてんきになあれ',
    '颱風',
    '春らんまん',
    '愛餓を'
];

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul>{
    for (let i = 0; i <song.length; i++) {
        return <li>{ song[i] }</li>
    }
    }</ul>
</section>
);

//レンダリング
ReactDOM.render(elm, text);
  • 変数text.textのDOMを取得
  • 変数h2Texth2タグに表示したいテキスト
  • 変数pTextpタグに表示したいテキスト
  • 変数songは配列でループで表示したいテキスト
  • 変数elmでJSXを設定
    h2タグにh2Textを読み込み
    pタグにpTextを読み込み
    ulタグの中にfor文
    for文の条件:変数iを0から開始、songの配列の回数まで繰り返し、iを1ずつ加算
    for文の処理:liタグの中にsongi番目のテキストを入れて返す
  • ReactDOM.renderelmをレンダリングしてtextに返す

はっぴいえんどのセカンドアルバム「風街ろまん」の情報を表示する、と言う内容。しかし、JSXの中にfor文を直書きするとif文と同様動かないことを確認できた。ここまでは予想通り。

なお、前回のif文ではJSXのタグをreturnで返す必要があったため、今回もJSXのタグの前にreturnを書いている。

※参考:【React】条件分岐の書き方(if文エラー回避、論理演算子、三項演算子) - クモのようにコツコツと

for文を即時関数に入れる(1回しか実行されない?)

if文では即時関数で囲うことで実行ができた。即時関数はこんな形。

//即時関数
(()=> {
    //処理
 })();

※参考:JavaScriptで即時関数を使う理由 - Qiita

やってみる。

See the Pen React 15 roop-5-ng2 by イイダリョウ (@i_ryo) on CodePen.

お、動いた!が、1回しか実行されない。。

JSコード

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul>{(() => {
    for (let i = 0; i <song.length; i++) {
        return <li>{ song[i] }</li>
    }
    })()}</ul>
</section>
);
  • ulタグの中のfor文を即時関数で囲った。

むむむ、1回しか実行されないのはなぜか?

for文をJSX外部の関数として書く(結果は同じ)

前回、即時関数意外にJSX外部の関数として書いてもうまくいった。やってみる。

See the Pen React 15 roop-5-ng3 by イイダリョウ (@i_ryo) on CodePen.

ま、まあね。先程と結果は同じ。1回しか実行されない。

const roop = () => {
  for (let i = 0; i <song.length; i++) {
    return <li>{ song[i] }</li>
  }
};

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul>{ roop() }</ul>
</section>
);
  • 変数roopの値は無名関数で処理はfor文
  • 変数elmのJSXのulタグの中でroop()を実行

JSXは動かないがconsole.log()はループする

原因を調べる。まず、for文の中のJSXをコメントアウトして、代わりにconsole.log()を実行してみる。

See the Pen React 15 roop-5-ng4 by イイダリョウ (@i_ryo) on CodePen.

JSコード

const roop = () => {
    for (  var i in song  ) {
     console.log ( song[i] );
    // return <li>{ song[i] }</li>;
    }
};

JSXをコメントアウトしているため画面上では何も表示はされないが、デベロッパーツールのコンソールで見てみるとこんな実行結果になった。

"抱きしめたい"
"空いろのくれよん"
"風をあつめて"
"暗闇坂むささび変化"
"はいからはくち"
"はいから・びゅーちふる"
"夏なんです"
"花いちもんめ"
"あしたてんきになあれ"
"颱風"
"春らんまん"
"愛餓を"

ちゃんとループでsongの配列の値を読み込めている。

と言うことはsong[i]と言う書き方自体には問題はない。

console.log()にもreturnをつける(一回しか実行されない!)

See the Pen React 15 roop-5-ng5 by イイダリョウ (@i_ryo) on CodePen.

JSコード

const roop = () => {
    for (  var i in song  ) {
     return console.log ( song[ i ] );
    // return <li>{ song[i] }</li>;
    }
};

console.log()にもreturnをつけてみるとコンソールの結果はこうなった。

"抱きしめたい"

おお、一回しか実行されない!原因はreturnか。

for文の中にreturnがあると処理が止まる

returnの役割について改めて調べると…

『return文』を使用すると関数(メソッド)が実行されたときにどの値を返すか指定することができます。そしてそれと同時にその関数の処理も終了させることができますので非常によく使われる構文になります。

そうか!値を返すだけでなく処理を終了させる、と言う役割もあるんだった!for文で何回分の処理を条件で設定しても、returnがあると1回目の処理で終了してしまったわけだ。

これはこれで「trueの場合は以降の処理は無視」みたいな使い道には便利なのだが、今回の目的には相性が悪い。。

JSXのreturnを削除(予想どおり動かない)

for文の中にreturnがあると処理が止まる。では、returnを無くしてみようホトトギス。

See the Pen React 15 roop-5-ng6 by イイダリョウ (@i_ryo) on CodePen.

やはり、returnがないとJSXは動かない。

JSコード

const roop = () => {
    for (  var i in song  ) {
        <li>{ song[i] }</li>;
    }
};

さて、どうしたものか。

JSXでfor文:成功例(push()を使う!)

調べたら回避方法があった!

空の配列にpush()で値を追加する方法

こちらの記事にreturnをfor文の外で使う方法があった。

即時関数の中でfor文を使う事例

    {(() => {
        const items = [];
        for (let i = 0; i < 5; i++) {
            items.push(<li>{i}</li>)
        }
        return <ul>{items}</ul>;
    })()}

お、一手間かけているぞ。

変数itemsに空の配列を作って、その中にpush()メソッドで値を追加している。
そして値が入った配列itemsreturnで返している。

これによってfor文の外でretuenを使えるわけか!

※参考:React, JSX で条件分岐(If)やループ(For)を使う方法 │ Web備忘録

push() メソッドは、配列の末尾に 1 つ以上の要素を追加することができます。

※参考:Array.prototype.push() - JavaScript | MDN

さっそくやってんみる。

for文の中で直書きでやってみる(動かず)

まずはダメ元でfor文の中に直書きでやってみる。

See the Pen React 15 roop-3-ng by イイダリョウ (@i_ryo) on CodePen.

JSコード

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
        {
            const items = [];
        for (let i = 0; i <song.length; i++) {
            items.push(<li>{ song[i] }</li>)
        }
        return <ul>{ items }</ul>;
    }
</section>
);
  • pタグの下、変数itemsに空の配列を入れる
  • for文でsongの値の回数繰り返す
    itemspush()liタグを追加。中身はsongi番目のテキスト
  • ulの中にitemsを入れてreturnで返す

まあこれが動かないことは想定内なので、確認作業。

for文を即時関数に入れる(成功!)

ここから本題。先程の処理全体を即時関数に入れてみる。

See the Pen React 15 roop-3 by イイダリョウ (@i_ryo) on CodePen.

やた!成功!風街ろまんの全曲目が表示された。

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
        {(() => {
            const items = [];
        for (let i = 0; i <song.length; i++) {
            items.push(<li>{ song[i] }</li>)
        }
        return <ul>{ items }</ul>;
    })()}
</section>
);
  • pタグの下のfor文の処理一式を即時関数で囲う

即時関数で囲う方法はやはり有効なんだな。

for文をJSX外部の関数として書く(成功!)

続いて、外部関数を読み込む書き方をやってみる。

See the Pen React 15 roop-4 by イイダリョウ (@i_ryo) on CodePen.

こちらも成功!

var roop = () => {
    const items = [];
  for (let i = 0; i <song.length; i++) {
    items.push(<li>{ song[i] }</li>)
  }
  return <ul>{ items }</ul>;
};

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    { roop() }
</section>
);
  • 変数roopの中でfor文の処理一式を書く
  • pタグの下でroop()を読み込む

この方がJSXの中はシンプルになって見通しは良い。

配列の値をそのままJSXで読み込む

ここから先、掌田さんの「React.js & Next.js超入門」に書いてあったfor文を使わない繰り返しをやってみる。

テキストだけを読み込むと全部繋がってしまう

そもそもの話として、配列の値をJSX内でそのまま読み込む方法が紹介されていた。え?そんな簡単な話?

See the Pen React 15 roop-1-ng by イイダリョウ (@i_ryo) on CodePen.

こちらはあえて作った失敗例。HTMLのレンダリング結果はこちら。

HTMLコード

<ul>
    <li>抱きしめたい空いろのくれよん風をあつめて暗闇坂むささび変化はいからはくちはいから・びゅーちふる夏なんです花いちもんめあしたてんきになあれ颱風春らんまん愛餓を</li>
</ul>

配列の値は読み込まれたがひと繋がりになっている。

JSコード

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul><li>{ song }</li></ul>
</section>
);

ulタグ内のliタグでsongの配列を直に読み込む。

liタグの中でテキスト読み込みを繰り返すので当然こうなるな。

配列の値をliタグにする(成功!)

掌田さんの本ではこれに少し手を加えることで成功させている。

See the Pen React 15 roop-1 by イイダリョウ (@i_ryo) on CodePen.

おお!全曲がliタグとして表示された!どうやっているのか?

let song = [
    <li>抱きしめたい</li>,
    <li>空いろのくれよん</li>,
    <li>風をあつめて</li>,
    <li>暗闇坂むささび変化</li>,
    <li>はいからはくち</li>,
    <li>はいから・びゅーちふる</li>,
    <li>夏なんです</li>,
    <li>花いちもんめ</li>,
    <li>あしたてんきになあれ</li>,
    <li>颱風</li>,
    <li>春らんまん</li>,
    <li>愛餓を</li>
];

まず、変数songの中の値をテキストではなくliタグにしている。

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul>{ song }</ul>
</section>
);

ulタグの中で配列songの値を読み込む。

これはめちゃめちゃシンプル!無理やりfor文を使わないでもこれで解決できるんだ!

ただ、配列songの中の値にliが入っているのは重複だなー。このタグに修正が生じると手間になりそう。

map()メソッドを使う

掌田さんの本にはもう一つmap()メソッドを方法が紹介されていた。これによって上記の問題が回避できる!

map()メソッドとは

map()メソッドとはなんぞや。配列を繰り返し処理してくれる関数のようだ。

「map」は配列データに使うメソッドであり、各要素1つずつに対して「コールバック関数」を実行し、その結果を新しい配列として返すことが出来るようになっています。つまり、この関数内に実行したい処理を書いておくことで、配列の各要素に対して好きな操作をすることが出来るというわけです!

var array = [ 配列データ ];
 
array.map( コールバック関数 );

※参考:【JavaScript入門】配列処理をするmap()の使い方とMapオブジェクトの解説! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

map()メソッドでループを実行

さっそくやってみる

See the Pen React 15 roop-2-err by イイダリョウ (@i_ryo) on CodePen.

見ての通り、風街ろまんの全曲が表示されている!どんな書き方か?

JSコード

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul>
        {song.map((val) => 
                <li>{val}</li>
        )}      
    </ul>
</section>
);
  • ulタグの中、songmap()メソッドを実行
  • map()メソッドの引数は無名関数
  • 無名関数の引数はval
  • liタグの中でvalを読み込む

for文とpush()メソッドを使った方法よりもグッとスッキリしている!空の配列を作る必要もないしreturnもいらない。

keyがないとコンソールエラーに。。

ただし、上記のコードだとコンソールにこんなエラーが表示された。

"Warning: Each child in a list should have a unique 'key' prop.%s%s See https://fb.me/react-warning-keys for more information.%s" "

Check the top-level render call using <ul>." "" "
    in li"

翻訳すると

「警告:リスト内の各子には一意の「キー」プロパティが必要です。%s%s詳細については、https://fb.me/react-warning-keysを参照してください。%s ""

<ul>を使用して最上位のレンダリング呼び出しを確認します。 "" ""
     李で」

うーん、よくわからんがkeyと言うものが必要らしい。

React公式サイトにもこうあった。

このコードを実行すると、「リスト項目には key を与えるべきだ」という警告を受け取るでしょう。“key” とは特別な文字列の属性であり、要素のリストを作成する際に含めておく必要があるものです

※参考:リストと key – React

keyとは何か(indexは非推奨)

先程のReact公式サイトにkeyを含める方法が書いてあった。

Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。配列内の項目に安定した識別性を与えるため、それぞれの項目に key を与えるべきです。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

※参考:リストと key – React

toString()は数値を文字列に変換するメソッド。toString()で配列のindex(1、2、3などの連番)をわざわざ文字列に変換している。

※参考:【JavaScript入門】toStringで数値を文字列に変換(日付/基数変換) | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

React公式サイトによると、indexをそのまま使うのは推奨しないらしい。配列の値の順番や数が変わる可能性があるためとのこと、

レンダリングされる要素に安定した ID がない場合、最終手段として項目のインデックスを使うことができます

要素の並び順が変更される可能性がある場合、インデックスを key として使用することはお勧めしません。パフォーマンスに悪い影響を与え、コンポーネントの状態に問題を起こす可能性があります。

※参考:リストと key – React

実際に、indexを使った場合に生じ得る事態がこちらで解説されている。

※参考:Reactのリストコンポーネントのkeyにindexを使ってはいけない - 天の光はすべてバグ

keyを追加する(コンソールエラーなし!)

keyを追加してみる。

See the Pen React 15 roop-2 by イイダリョウ (@i_ryo) on CodePen.

結果は変わらない。

JSコード

//JSXの中身
const elm = (
<section className="h2_elem">
    <h2>{ h2Text }</h2>
    <p>{ pText }</p>
    <ul>
        {song.map((val) => 
            <li key={val.toString()}>{val}</li>
        )}       
    </ul>
</section>
);
  • liタグにkey属性、valのインデックスをtoString()メソッドで文字列に変換

これによってコンソールエラーは無くなった!

なお、レンダリング結果のliタグの中にkey属性は描画されないようだ。内部的な処理なのかな。

最後に

と言うことでReactでループ処理をいくつか試してみました。

方法 備考
即時関数にfor文 push()メソッドで空の配列に追加。JSXの構造が膨らむので直感的じゃない。
外部の関数にfor文 push()メソッドで空の配列に追加。それぞれの関数の中がシンプルなので見通しは良さそう。
配列の値を読み込む シンプルな書き方だが、配列の値にliを書くのが重複になる。
map()でループを実行 シンプルな書き方!keyを書かないとコンソールエラー(indexは非推奨)

for文はreturnを使うと処理が止まってしまうため、空のpush()メソッドに入れるのが少し手間に感じました。

配列の値を読み込むのは構造は最もシンプルですが値にliを書くのが重複に感じました。

map()メソッドで書く方法がシンプルで使いやすそうに感じました!ループはこちらをメインで使って行こうかと思います。

それではまた!


Reactを習得するためにやったことまとめ
qiita.com