【React】 変数を更新したり、ifやforで表示するには
Reactを扱うのに、変数をDOMに埋め込むのは必須です。配列なども合わせてどのように埋め込むのか見ていきましょう。
目次
変数の宣言場所と表示
Reactを導入した直後では、どこに変数を埋め込むのかも中々分かりません。変数は、コンポーネントの関数内に埋め込みます。
ここで、単なる定数なら普通の宣言で良く、変数の場所もどこでも良いです。
const y = "fuga"; function App() { const x = "hoge"; return ( <div className="App"> <p>{ x }</p> <p>{ y }</p> </div> ); }
しかし、普通に変数を更新しても何も起こりません。どのようにすればよいのでしょうか。
export default function App() { let x = 10; const addValue = () => { x += 5 }; // x += 5 しても表示が変わらない return ( <div> <p>{x}</p> <button onClick={addValue}>addValue</button> </div> ); }
useState
useState
を用いて変数を作成することで、リアクティブな変数にすることができます。
引数には初期値を入れます。
import { useState } from 'react'; export default function App() { const [x, setX] = useState(10); const addValue = () => { setX(x + 5); }; // xの値が変わると、自動で表示も変わる! return ( <div> <p>{x}</p> <button onClick={addValue}>addValue</button> </div> ); }
useState(初期値)
は[var, setter]
の形で返ってきます。上の例ではx
は読み取り用で、値を更新するにはsetX
を用いています。
Reactは、そうして更新された値を検出し、再描画してくれます。
値更新の挙動
Reactでは、useState
で使用した値を更新したなどで再描画が行われる際、その関数が再度実行されるという挙動を取ります。
export default function App() { const [x, setX] = useState(10); function addValue() { setX(x + 5); }; // チェック用 console.log("render!!"); return ( // ... ); };
上の例の下の方にある”Console”を開いてみると、addValue
ボタンをクリックする度に、addValue
の外にあるはずのrender!!
が毎回表示されることがわかります。
Reactでは、useState
などのstate
を更新するとコンポーネントの関数自体が再実行されるということになります。これは極めて重要な性質なので必ず覚えておきましょう。このとき、DOMが更新された場合は全部更新されるわけではなく、必要な部分だけを更新してくれます。
2回render!!
が表示される (App()
が2回実行される) のは仕様です。React.StrictMode
を使用している際は、何らかの理由で複数回呼ばれた場合に起こるであろう問題点を見つけやすくするため、意図的に2回実行されます。
これは開発時のみで、Productionモードでビルドすれば普通の1回呼び出しの挙動になります。
関数全体が実行されるということは、再度useState
されるのではと考えたのではないでしょうか。しかし、Reactは何をuse
したかを覚えています。useState
自体は再度実行されますが、それによって値が初期値に戻ってしまうなどといったことはありません。
変数の更新タイミング
useState
で作成した変数でset
した場合、変数が更新されるのはレンダリングするタイミングになってからです。
export default function App() { const [x, setX] = useState(10); const addValue = () => { setX(x + 5); // 足す前の数値が表示される console.log(x); }; const getX = () => { return x; } return ( <div> <p>{x}</p> {/* このgetXは足した後の数値 */} <p>{getX()}</p> <button onClick={addValue}>addValue</button> </div> ); }
もし更新直後に何かを実行したい場合、
() => { const newValue = x + 5; setX(newValue); // newValueで処理する }
のようにしたり、そもそも直後である必要性をなくしてsetEffect
を用いるなどの方法があります。
JSX内に式を書く
forやif文といったJavascriptの構文は、全て{}
内に書きます。上の例では{ x }
のように書いていますが、これはx
の値を表示しているというより、{}
内の計算結果を出力しているといったほうが正しいです。
例えば、
<p>{ 10 + 20 }</p>
は、30
と表示されます。また、即時関数も式として扱えますね。
<p> {(() => { const s = " abcde "; return s.replace(/\s/g, "_"); })()} </p>
<p> _abcde____ </p>
見づらいですが、{}
内に即時関数を入れています。{ (() => { /*...*/ })() }
ですね。
これらを応用してfor
やif
を書きましょう。
if文
JSXをreturn文で返すことになります。if (...) return <p>hoge</p>
のような感じです。
{(() => { if (x === 20) { return <p>xは20</p>; } else { return <p>xは20ではない</p>; } })()}
これは、{}
の中に即時関数を入れ、その即時関数の中身が
if (x === 20) { return <p>xは20</p>; } else { return <p>xは20ではない</p>; }
となっています。また、このような単純な例では、三項演算子を用いると即時関数が不要になるため、スッキリします。
<p> xは20{ x === 20 ? "である" : "ではない" } </p>
1 + 2
という”式”と同じように、三項演算子を用いたx === y ? "a" : "b"
もまた”式”であるためです。式は計算され、演算子に応じたただ1つの結果が取得できます。
for文
1つの値を出すだけなら、単にreturn文で返すだけというのが上の例でわかりました。では複数出力するにはどうすればよいのでしょうか。
複数出力するには、配列を用います。
<ul> {(() => { const d = []; for (let i = 0; i < 5; i++) { d.push(<li>i = {i}</li>); } return d; })()} </ul>
<ul> <li>i = 0</li> <li>i = 1</li> <li>i = 2</li> <li>i = 3</li> <li>i = 4</li> </ul>
つまり、配列に入れたものがズラッと並びます。push
にはJSXで書いた値を入れています。
実際に動かすと、key
を使用したほうが良いといったメッセージがconsoleに出力されています。これについては、下の記事を参考にしてください。
そもそもifもforも関係ない
これまでの例を見るとわかりますが、if文はこうする、for文はこうするではなく、波括弧内に即時関数を入れて、出力したいものをreturn
する、という書き方です。
関数の中でifやforをどうやって書くかと言うは意識しませんよね。JSX内でifやforの書き方を覚えるのではなく、即時関数を埋め込んで実行できるということを覚えておきましょう。
複雑な処理を埋め込んでしまうと、パフォーマンス的に不利になります。useCallback
などを用いて再計算を防ぐべきですが、後の紹介になります。
属性に埋め込む
これまで、値は普通に表示するもので書きました。タグの属性に埋め込むためには、<a href={x}>...</a>
のように書きます。
export default function App() { const url = "https://example.com"; return ( <div> <a href={url}>example.com</a> </div> ); };
つまり、普通のHTMLではダブルクォーテーションで囲みましたが、JSXでは波括弧で囲むということです。波括弧内には式を書けます。
class属性
ここで、class属性はclassName
を用います。
<a href={url} className={"hoge fuga hige"}>example.com</a>
もし配列や連想配列で管理したい場合はjoin
を用いてスペースで連結すれば良いです
/** * クラス用の連想配列をクラス名に変換 * * @param {{[key: string]: boolean}} classes 値がtrueであるキーをクラス名として採用 * @returns */ function getClassName(classes) { return Object.entries(classes) .filter(([_, v]) => v) .map(([k, _]) => k) .join(" "); } export default function App() { const url = "https://example.com"; const x = 10; const classes = { "hoge": true, "fuga": url.indexOf("https") !== -1, "hige": x === 10 } return ( <div> <a href={url} className={getClassName(classes)}>example.com</a> </div> ); };
まとめ
変数をJSXに埋め込むためには、波括弧で囲み、その中に式を入れます。単に値を入れたいだけの場合は{ x }
のようにシンプルにかけますが、即時関数を入れて複雑な処理を埋め込むこともできますが、再計算と描画が増えないよう、パフォーマンスに注意が必要です。
属性に埋め込む際はhref="..."
のようなダブルクォーテーションの代わりに、href={...}
のように波括弧で囲み、式を入れます。
