前回からの続き。
クリックしたら何かが起こるイベント
import React from 'react';
export default function ExampleComponent() {
return (
<div>
Example Component
</div>
);
}
前章で作成したコンポーネントにクリックイベントを追加する。
Reactのコードを見るときは、returnの内容(jsx)から見ると理解しやすい。
import React from 'react';
export default function ExampleComponent() {
const clickEventHandle = () => { // 2. イベントを処理関数として記述する
alart('クリックされました。');
}
return (
<div onClick={clickEventHandle}> // 1. クリックしたら clickEventHandle をコールする
Example Component
</div>
);
}
続いて、「Example Component」という文字列をクリックで変化するようにしてみる。
表示する文字列を状態変数として保存し、クリックで状態変数が変わるように処理を行う。
状態変数の定義には、useStateフックを使う。
import React, { useState } from 'react'; // useState をインポートする
export default function ExampleComponent() {
// 状態変数の定義、初期値を「Example Component」と定義する
// 初期値が stateText に格納される
const [stateText, setStateText] = useState('Example Component');
const clickEventHandle = () => {
if (stateText == 'Example Component') {
setStateText('Clicked!');
} else {
setStateText('Example Component');
}
}
return (
<div onClick={clickEventHandle}>
{ stateText } // 状態変数の内容を表示
</div>
);
}
サンプルは以下。文字列をクリックしてみてね!
See the Pen SampleReactClickEvent by yuki sku (@yuki-sku) on CodePen.
useStateはvalue, setValueの書式で定義し、setValueをコールすることでvalueの値を変更する。
const [value, setValue] = useState('初期値'); // set何とか はキャメルケースで記述する
setValue('変更したい値'); // value が「変更したい値」になる
valueの値が変わったらコンポーネント関数全体が再実行される(コンポーネントが再レンダリングされる)が、useStateの初期化処理はスキップされ、valueの値は初期化されずにそのままの値が保存される。後で実験するので「ふーん」と聞き流せばOK。
値を変えたら何かが起こるイベント
次に値を変えたら何かが起こるイベントを考える。最初に機能は何もないコンボボックスのコンポーネントを作成する。
import React from 'react';
export default function ExampleCombobox() { // app.jsx に追記しないとだよー
return (
<div>
<select name="fruits" id="tag-id">
<option value="">選択してください</option>
<option value="apple">りんご</option>
<option value="orange">みかん</option>
<option value="peach">もも</option>
</select>
</div>
);
}
このコンポーネントに値が変わったときに処理を行うイベントを追加する。
せっかくなので、選択された項目のvalueを状態変数として保存し画面に表示するようにもしてみよう。
繰り返すが、Reactのコードを見るときはjsxの内容から見ると理解しやすい。
import React, { useState } from 'react'; // 使うフックのインポート、忘れやすい
export default function ExampleCombobox() {
// 状態変数を定義、初期値はnull
const [selectedItem, setSelectedItem] = useState(null);
const changeEventHandle = (event) => { // 2. イベントを処理関数として定義する
setSelectedItem(event.target.value); // 選択した項目の value を selectedItem に格納
}
return (
<div>
<select name="fruits" id="tag-id"
onChange={changeEventHandle} // 1. 値が変わったら changeEventHandle をコールする
>
<option value="">選択してください</option>
<option value="apple">りんご</option>
<option value="orange">みかん</option>
<option value="peach">もも</option>
</select>
<p>選択した項目値:{selectedItem}</p> // 状態変数の内容を表示
</div>
);
}
サンプルは以下。コンボボックスの値を変えると value 値が表示に反映される。
See the Pen SampleReactChangeEvent by yuki sku (@yuki-sku) on CodePen.
イベントの実装については以上。他にも設定できるイベントはあるので、「React イベント」とかでググれば出てくる。いや、最近は 「AIに聞く」 が自然かな。
超わかりにくい useEffect
useEffectは「副作用を扱う」とか訳のわからない説明をよく見かけるが「コンポーネントを装飾・演出するもの」です。「コンポーネント効果」って言われた方がしっくり来るのになぁ。
以下のコードでuseEffectの動作を体感してみよう。コードを見るときはjsxから、でっせ。
import React, { useState, useEffect } from 'react'; // 使うフック、インポートするよー
export default function ExampleUseEffect() { // app.jsx への追記、忘れないでねー
const [text, setText] = useState('初期表示のテキストです。')
useEffect(() => {
setTimeout(() => { // 3秒待って text を書き換える
setText('useEffectでテキストを書き換えました。');
}, 3000);
}
, []); // 依存配列なるものを第二引数に指定する。今回は空配列を指定
return(
<div>
{text} // 状態変数の内容を表示
</div>
);
}
下部サンプル画面右下の「Rerun」ボタンをクリックすると再読み込みします。
See the Pen SampleReactClickEvent by yuki sku (@yuki-sku) on CodePen.
このコードでは、最初に初期設定された text で画面が表示され、そのあと3秒経過するとテキスト内容が変わる。つまりuseEffectはデフォルトのコンポーネントが表示された後で実行されることがわかる。
Q. これをいつ何のために使うか?
A. 最初は枠組みだけさっさと表示して、あとからデータを取得して表示する
などが例として挙げられる。外部ソースからのデータ取得は環境によって時間がかかることがあるので、軽い部分を先に表示しておくことで、ユーザーから見た技術的な違和感を低減させる、とか。データ取得中はクルクルを表示して1件ずつ表示することで、アプリケーションが頑張ってデータを探してる感を演出する、とか?。どんな実装をするかは設計者次第。
useEffectは以下の書式で記述する。
useEffect( 処理関数 , [依存変数1, 依存変数2, ...]);
- 依存変数を何も指定していないと、コンポーネントが表示されたあとで
useEffectの処理関数が1回だけ実行される。 - 依存変数を何か指定していると、依存変数の値がどれかひとつでも変わっていれば、再レンダリングのときに
useEffectの処理関数が再実行される。
うん、やっぱり文字列だけでの説明はわかりにくいね。
DOMの値、たとえばcsrfトークン取得やdocument.getElementById、data属性の値取得(elementId.dataset.)等をuseEffectで記述するようにcopilotが提案してくることがあるけど、今回の環境では blade が出力された後にコンポーネントが描画されるので、わざわざuseEffect内で処理する必要は無い。
useEffectは画面の演出効果的位置づけ(と理解している)なので、業務アプリとかにはあまり複雑に使うことは少ないかも。apiとのget, postのやり取りはuseQuery、useMutationっていう専用フックがあり、useEffect不要になるケースも多い。通知メッセージの表示時間制限とかに使ってます。
// メッセージ表示時間を10秒に設定
useEffect(() => {
if (message) {
const timer = setTimeout(() => setMessage(''), 10000);
return () => clearTimeout(timer);
}
}, [message]);
// setMessage で message が変わったら再レンダリングで実行される
// 例えば setMessage('データが正常に登録されました'); とかね
See the Pen Untitled by yuki sku (@yuki-sku) on CodePen.
再描画しない useRef
続いてuseRefフックについて。
useRefは、画面表示に使わないが処理関数で使うための変数が必要なときに使う。書式は以下。
import React, { useRef } from 'react'; // マジでこれよく忘れる
export default function() {
const refText = useRef('参照用の変数内容を定義');
return(
// refText はここで使わない(使っても良いけどあまり勧めない)
);
}
useRefは参照するときちょっとクセがある。
const refText = useRef('参照用の変数内容を定義');
// 変数にアクセスする(例としてコンソールへ出力)
console.log(refText.current);
// 変数の値を変える
refText.current = '別の内容へ再定義';
.currentが必要になるので意識する必要がある。よく忘れる。
定義する内容はどんな型でも入るし、数値型ならそのまま計算にも使える。
const refNum = useRef(1); // 数値型の 1 を定義
refNum.current += 1; // 1をたす
変数の動作の違い
useStateの変数、useRefの変数、jsの変数で挙動がどう違うのかを実験する。
import React, { useState, useRef } from 'react';
export default function DeffArgs() {
const [stateText, setStateText] = useState('stateのテキスト');
const refText = useRef('refのテキスト');
let jsText = 'jsのテキスト';
return(
<div>
<p>{stateText}</p>
<p>{refText.current}</p>
<p>{jsText}</p>
</div>
);
}
これにボタンクリックでテキストを書き換えるイベントを追加する。jsxから見る、もう癖ついたかな?
import React, { useState, useRef } from 'react';
export default function DeffArgs() {
const [stateText, setStateText] = useState('stateのテキスト');
const refText = useRef('refのテキスト');
let jsText = 'jsのテキスト';
// クリックイベント処理
const clickEventHandle = () => {
setStateText('stateテキストを書き換えました。');
refText.current = 'refのテキストを書き換えました。';
jsText = 'jsのテキストを書き換えました';
}
return(
<div>
<p>{stateText}</p>
<p>{refText.current}</p>
<p>{jsText}</p>
<button onClick={clickEventHandle}> // クリックイベント
書き換える
</button>
</div>
);
}
See the Pen Untitled by yuki sku (@yuki-sku) on CodePen.
ボタンをクリックすると、stateテキストとrefテキストが書き換わる。setStateTextをコールすることで、コンポーネントが再レンダリングされる。同じ場所でrefTextの値も書き換えているのでrefテキストも一緒に再レンダリングされる。jsTextは再レンダリングされる際、再びlet jsText =の処理が呼ばれて値が初期化されてしまう。
このことから、コンポーネントの再レンダリングとは、DeffArgs()コンポーネント関数を再実行しており、
const [stateText, setStateText] = useState('stateのテキスト');
const refText = useRef('refのテキスト');
のようにフックで定義されている変数は初期化処理がスキップされる動作であることがわかる。
ここで、setStateをコメントアウトする。さて、ボタンクリックで何が起こるだろうか?
import React, { useState, useRef } from 'react';
export default function DeffArgs() {
const [stateText, setStateText] = useState('stateのテキスト');
const refText = useRef('refのテキスト');
let jsText = 'jsのテキスト';
const clickEventHandle = () => {
// setStateText('stateテキストを書き換えました。'); // コメントアウトしてやったぜ!
refText.current = 'refのテキストを書き換えました。';
jsText = 'jsのテキストを書き換えました';
}
return(
<div>
<p>{stateText}</p>
<p>{refText.current}</p>
<p>{jsText}</p>
<button onClick={clickEventHandle}>
書き換える
</button>
</div>
);
}
See the Pen Untitled by yuki sku (@yuki-sku) on CodePen.
ボタンをクリックすると今度は3つともテキストは書き換わらない。これら2つの実験結果から、refText.currentの値は書き換えられているが再レンダリングされないことがわかる。別の処理などで再レンダリングが起こると、書き換えられたrefText.currentが画面に反映されるので、意図しない動作の原因になりうる。
実験結果から、
- 値の変更を画面に反映させたい変数: useState
- 画面表示に使用しない参照用の変数: useRef
- 毎回再定義処理しても重くない変数: 普通のjs変数(変更は画面に反映されない)
と使い分ければ良いことがわかる。
次へ続く...かなぁ。