Reactの最新版(v18)において、イベントハンドラ関数に引数を渡す方法および基本的なイベント処理について説明します。
ボタンやリンクなど、あらゆる要素をクリックした後に何らかの処理を実行する場合は、常にonClickイベントハンドラを使用します。
これはReactで最も強力で、最も頻繁に使用される機能の一つです。
また、この記事ではonChangeイベント処理についても解説しますが、ほとんどはonClickイベントハンドラを重点的に学習していきます。
SyntheticEvent(合成イベント)
イベントは、ユーザーが行う操作やシステムが生成するトリガー動作によって発生するもので、キーを押す、マウスをクリックする、ウェブページを読み込む、ウィンドウのサイズを変更するなどがその例です。
しかし、これらのイベント処理はブラウザの互換性に問題があることが知られています。
Reactをはじめとするフレームワークがなければ、ユーザーのあらゆる操作に反応するレスポンシブなWebアプリケーションを構築することは難しいでしょう。
Reactは、DOM要素がイベントを処理する方法と同様のイベント処理システムを持っています。
しかし、Reactは合成イベントシステムを使用して、Reactアプリとインターフェースの一貫性と高速性を確保します。
合成イベントシステムは、ブラウザとシステム間でイベントを正規化して同じプロパティを持つようにすることで、一貫性を提供します。
このようにすることで、ReactはモバイルアプリのElectron、iOS、AndroidのReact Native、そしてReactを動作させることができるようになりました。
また、合成イベントシステムにより、1つのネイティブイベントから複数の合成イベントを生成することができるため、新しいイベントを素早く作成できるようになりました。
イベントハンドリングは、Reactがユーザーによって行われた各動作を確実に追跡するために不可欠な要素となっています。
前述した通り、Reactでは、イベント処理において Synthetic(合成)イベントオブジェクトが使われます。
この合成イベントオブジェクトは、W3C仕様に従って定義されているため、ブラウザ間の互換性については心配する必要がありません。
合成イベントオブジェクトには、以下のような属性があります。
bubbles: イベントがバブリングイベントであるか否かを示すtrue
またはfalse
を返します。
つまり、イベントをキャンセル可能かどうかをtrue
またはfalse
で返します。
currentTarget: ハンドラがアタッチされている要素を示します。
defaultPrevented: preventDefault()
によりイベントがキャンセルされたか否かを示すtrue
またはfalse
を返します。
eventPhase: イベントがユーザーによって生成された場合はtrueを返し、ブラウザとスクリプトによって生成された場合はfalseを返します。
type: イベントの種類を示す文字列を返します。
ただし、注意しなければならないのが、イベントプーリングという仕組みです。
Reactでは、イベント処理の最適化のために、イベントハンドラの呼び出し時に合成イベントオブジェクトが再利用される仕組みがあります。
そのため、イベントハンドラ内で合成イベントオブジェクトのプロパティを変更した場合、予期しない動作を引き起こす可能性があるため、注意が必要です。
イベントプーリング(Event Pooling )
イベントプーリング(Event Pooling)とは、Reactにおいて、イベントハンドラが呼び出されたオブジェクトのすべてのプロパティを無効化し、再利用可能な合成イベントインスタンスを準備する技術です。
これにより、Reactはパフォーマンスの向上を実現し、同時にネイティブイベントとの互換性を保ちます。
ただし、このプーリングにより、イベントオブジェクトは後で使用することができなくなるため、非同期にアクセスしてタイムアウトを実行することはできません。
Reactでは、イベントハンドラ終了後にイベントオブジェクトのプロパティにアクセスする必要がある場合には、e.persist()
を使用することができます。
Reactの目標は、同じプロパティを使用することにより、合成イベントが通常のネイティブイベントとかなり類似していることを保つことです。
合成イベントは、イベントが発生した後にコールバックに送信され、その後クリーンアップされます。
注意点として、React v17ではイベントプーリングが削除されたため、このルールはReact v16以下を使用するプロジェクトにのみ適用されます。
プーリングは、合成イベントが再利用されるプロセスであり、パフォーマンスの最適化に役立ちますが、初心者が学ぶべき主要な概念ではありません。
ただし、React v18においても、Reactのイベントハンドリングの基本的な仕組みや構文は、React v17以前と同様です。
SyntheticEventとイベントプーリングの概念が引き続き使用されています。
したがって、React v18ではSyntheticEventやイベントプーリングに関する知識が依然として重要でもありますので余裕があれば学んでおく事を推奨します。
React合成イベントおよびプーリングに関する詳細は、Reactのリファレンスガイドを参照してください。
また、通常のJavaScriptイベントでの操作と非常に似ていることにも注意してください。
Reactのイベントハンドラ
まずは、フックを使用しないReactを学ぶ上で最初に作成する単純なアプリコンポーネントについて、例を見てみましょう。
下記のコードは、1つの関数greeting
とボタンを含むReactコンポーネントAppです。
ボタンには、onClickイベントハンドラにgreeting()
関数を紐付けています。
const App = () => { function greeting() { alert("Hello!"); } return ( <div> <button onClick={greeting}>Click me!</button> </div> ); };
See the Pen React イベントハンドラ by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
このようにすることで、ボタンをクリックするたびにgreeting()
関数が実行され、アラートで挨拶が表示されます。
greeting
をトリガーにするには、イベントハンドラの後に中括弧内{ }
で呼び出したい関数の名前を渡してあげます。
<button onClick={greeting}>Click me!</button>
ここで、初心者が引き起こす間違いを示します。
イベントハンドラに関数を渡す場合、関数名の後ろに()
をつけて関数を呼び出すのは間違いです。
つまり、以下のように括弧をつけてしまうと、コンポーネントがレンダリングされるたびに関数が実行されてしまいます。
// ✖︎ 間違い <button onClick={greeting()}>Click me!</button> //◎ 正 <button onClick={greeting}>Click me!</button>
コンポーネントを初めてレンダリングするときにイベントハンドラが呼び出されるのは1回だけです。
関数名の後ろに括弧をつけると、関数が実行された結果をonClickに渡してしまい、以降のクリックで関数が実行されることはありません。
関数の戻り値が他の関数である限り、括弧をつけても何も起こりません。
関数を渡すためには、単に関数名のみをonClickに渡しましょう。
以上をまとめると、イベントハンドラに関数を渡す場合は、関数名の後ろに()
をつけずに、関数名のみをonClickに渡すことが重要です。
このようにすることで、コンポーネントが初めてレンダリングされるときに1回だけイベントハンドラが呼び出され、以降のクリックで関数が再度実行されることはありません。
そして、Reactにおいて、イベント名はHTMLとは異なり、小文字ではなくcamelCase
(キャメルケース)で命名されます。
たとえば、下記のように書きます。
// JSX <button onClick={handleClick}> Click me </button>
ここで、注意しなければならないのは、ReactではonClickだけでなく、for
やclass
属性も小文字ではなくcamelCaseで命名しなければならないことです。
HTMLでは、class
属性は以下のように書かれます。
<div class="container"></div>
そして、for
属性は以下のように書かれます。
<label for="your_name"></label>
しかし、Reactではこれらの属性名に大文字が含まれるため、以下のように書かなければなりません。
// JSX <div className="container"></div> <label htmlFor="your_name"></label>
これらの属性名には、HTMLとの互換性を保つためにReactが新たに命名したものが使用されます。
Reactは大文字と小文字を区別するため、必要に応じてcamelCaseで属性名を命名しなければなりません。
なぜ、Reactがこれらの命名規則を採用したのか疑問に思うかもしれません。
これは、JavaScriptに精通している人であれば分かるように、if
やfor
のようなキーワードは、オブジェクトのプロパティ名以外で変数として宣言できないためです。
つまり、これらはJavaScriptの予約語であると言えます。
Reactチームは、このような予約語を避けるために、新しい属性名であるhtmlFor
とclassName
を採用しました。
React初心者は、このような小文字のonclick
によって思い通りに機能しないことにしばしば悩まされます。
そのため、Reactでの属性名の書き方について理解しておくことは非常に重要です。
これらのポイントを理解することで、Reactでスムーズにコーディングすることができるでしょう。
イベントハンドラをインライン関数として呼び出す
インライン関数は、ReactコンポーネントがレンダリングされるときにonClickハンドラ内で定義される関数の事です。
インラインハンドラとも呼ばれる、インラインイベントハンドラはJSXで直接イベントハンドラを使用することにより、多くの新しいオプションを提供します。
関数定義された、コンポーネントはreturn
内にあるonClickハンドラ内にあるため、レンダリング時に作成されます。
インライン関数は、主にJSXの外部での関数の宣言を回避するために使用されます。
まず、JSXで関数ステートメントを使用するのは冗長です。
つまり以下のようなfunction宣言です。
<button onClick={function(){ alert("hello!!") }}>Click me!</button>
したがって、JavaScriptのアロー関数は、インラインハンドラをより簡潔に定義するのに役立ちます。
以下はインラインアロー関数となります。
const App = () => { return ( <div> <button onClick={() => alert("hello!!")}>Click me!</button> </div> ); };
See the Pen React インラインのイベントハンドラー by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
Reactコンポーネントでは、これらの方法を使用して、コードの可読性を向上させ、コードの長さを短くすることができます。
インライン関数にボタンの値を引数として渡す
Reactでは、ボタンの値を引数として渡すために、インライン関数を一般的に使用します。
これは、input
要素やonChange
イベントハンドラを使用する場合にも非常に一般的です。
以下の例をご確認ください。
const App = () => { return ( <div> <button type="button" onClick={(e) => alert(e.target.value)}> Click me </button> </div> ); };
See the Pen React イベントハンドラ 値を引数として渡す by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
この例では、onClickイベントハンドラから返される値のe
に注目してください。
このe
はSyntheticEvent(合成イベント)と呼ばれるもので、JavaScriptプログラマーによって新たに作成されるイベントオブジェクトです。
すべてのイベントハンドラはSyntheticEventに渡されます。
これは、ターゲット要素の値など、イベントに関連する有用なメタデータを含むオブジェクトです。
上記の例では、ターゲット要素の値にアクセスしてブラウザのアラート内に表示していますが、value属性がないため空が通知されます。
次の例では、ボタンにvalue属性を追加し、値を取得することができるようになります。
const App = () => { return ( <div> <button type="button" value="hello!!" onClick={(e) => alert(e.target.value)}> Click me </button> </div> ); };
See the Pen React イベントハンドラ 値を引数として渡す 2 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
上記の例では、e.target.value
は、実際のボタン要素にあるvalue属性から取得されています。
インラインイベントハンドラ内で直接合成イベントを使用することができ、この例では、ボタンの値がe.target.value
経由で取得されています。
この値は、メッセージを警告するために使用されたりします。
それでは、onChangeでのevent.target.value
を見てみましょう。
ReactのonChangeイベントハンドラは、JSXの<input>
要素に渡されるプロパティの一つで、アプリケーションがユーザー入力をリアルタイムでリッスンできるようにReactによって提供されます。
onChangeイベントが発生するたびに、ReactはProps
に渡された関数を呼び出します。
以下は、handleChange
関数をProps
として渡した例です。
const App = ()=> { const handleChange = (event) => { console.log(event.target.value); } return ( <input type="text" name="fullName" onChange={handleChange} /> ); };
上記の例では、<input>
要素に入力があるたびにhandleChange
関数が呼び出され、event.target.value
によって、入力された値を取得し、それをコンソールに表示します。
下記で実際の出力を確認できます。
https://codepen.io/enjinia_f/pen/VwXbmab
Props
内では、関数宣言も可能です。
以下は、onChangeプロパティにインライン関数を渡した例です。
<input type="text" name="firstName" onChange={event => alert("hello!!")} />
このようにすることで、テキストフィールドに何かを入力するたびに、ReactがonChangeプロパティに渡した関数をトリガーしてくれます。
Reactの合成イベントは、Reactの重要な機能であり、Reactのイベントシステムにおいて非常に重要な役割を担っています。
合成イベントは、ネイティブなDOMイベントをラップし、Reactコンポーネント内で処理できるようにします。
以上がReactでのonChangeイベントハンドラと合成イベントの説明です。
これらの機能を活用することで、Reactアプリケーションに動的なユーザー入力を実装することができます。
複数の関数を呼び出す
ボタンをクリックした後、複数の関数を呼び出したいと思われたはずです。
たとえば、コンポーネントの状態を更新すると同時にアラートを表示したりなどです。
以下のように、ReactのonClickイベントハンドラ内にロジックを書き込むのは、最善の方法とはあまり言えません。
// これは避けるべき <button type="button" onClick={(e) => { console.log("hello!"); console.log("Taro"); } }>Click me</button> };
Viewとロジックコードは、可能な限り切り離す必要があります。
それにより、以下のコードのようにすれば、よりクリーンなモジュラーコードが促進され、必要に応じてViewライブラリを切り替えることができます。
const App = () => { const greeting = () => { alert('hello!!'); }; const myName = () => { alert('Taro'); }; return ( <div> <button onClick={() => { greeting(); myName(); }}> Click me </button> </div> ); };
onClick内でインライン関数を使用して、複数の関数を呼び出しています。
この方法も最良の選択肢とは言えませんが、必要な時もございます。
では、他の例でもJSX外部で関数を宣言し、onClickで複数の関数を呼び出すことができます。
以下は、2つの関数を宣言した例です。
ボタンがクリックされるたびに、handleClick
関数が呼び出され、イベントオブジェクトが渡されます。
const App = () => { //function1 const addition = (a, b) => { return a + b; }; //function2 const multiply = (x, y) => { return x * y; }; const handleClick = event => { console.log(event.target); alert(addition(5, 5)); // 10 alert(multiply(5, 5)); // 25 }; return ( <div> <button onClick={handleClick}>Click me</button> </div> ); }
See the Pen React 複数の関数呼び出し イベントハンドラ by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
このように、onClickのProps
に関数を渡しているだけなので、関数を呼び出した結果をonClickのProps
に渡してしまうと、ページが読み込まれるとすぐに関数が呼び出されてしまうことに注意してください。
Reactでは、Viewとロジックコードを切り離し、よりクリーンなコードを実現することが重要です。
JSX外部で関数を宣言し、onClickで複数の関数を呼び出す方法は、コードを見やすく、保守性の高いコードを書くための有効な手段の一つです。
イベントハンドラ内の状態を更新
ReactのonClickイベントハンドラ内で状態を更新する方法について学びましょう。
イベントハンドラ内でのインライン関数の一般的な使用法は、コンポーネントの状態を更新することです。
例えば、useStateフックを使ってカウントアップする場合は、以下のようになります。
const App = () => { const [count, setCount] = useState(0); return ( <div> Count: {count} <button type="button" onClick={() => { setCount(count + 1); }}> Increment </button> </div> ); };
See the Pen React フックのインラインのイベントハンドラー by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
上記の例では、インラインハンドラを使用して、setCount
関数を呼び出して状態を更新しています。
ただし、パラメータを渡す必要がある場合は、代わりにアロー関数を使用する必要があります。
例えば、カウントを減らす場合は以下のようになります。
import { useState } from 'react'; const App = () => { const [count, setCount] = useState(0); const handleDecrement = () => { setCount(count - 1); }; return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={handleDecrement}>Decrement</button> </div> ); }; export default App;
上記の場合、handleDecrement
関数を定義して、その関数をonClickハンドラに渡します。
この方法であれば、コードをクリーンに保つことができます。
また、イベントと引数を並列に渡すこともできます。
例えば、以下のようになります。
const App = () => { const [count, setCount] = useState(0); const handleCount = (event, num) => { setCount(count + num); }; return ( <div> Count: {count} <button type="button" onClick={event => handleCount(event, 1)}> Increment </button> <button type="button" onClick={event => handleCount(event, -1)}> Decrement </button> </div> ); };
See the Pen React フックでのイベントハンドラー by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
上記の例では、handleCount
関数を定義して、引数として新しいカウント値を渡します。
開発者は怠惰な人々であるため、JSXの外部で余分な関数宣言を回避するために、インラインイベントハンドラーが使用されることが多々あります。
ただし、イベントハンドラ内で多くのロジックを持ち過ぎると、コンポーネントの可読性や保守性が損なわれることがあります。
よって、可能な限りコードをクリーンに保つことが重要です。
また、Reactを使用してフォームを作成するとき、入力フィールドの値を追跡するために状態変数を宣言する必要があります。
状態変数を更新するために、onChangeプロパティを設定し、フックを使用することができます。
以下のコードを使用して、テキストフィールドに何かしら入力された値を追跡し、event.target.value
を使用して値を取得し、状態変数を更新する方法を説明します。
import { useState } from 'react'; function App() { const [text, setText] = useState(''); const handleChange = event => { setText(event.target.value); console.log('value is:', event.target.value); }; return ( <form> <label> Text: <input type="text" name="text" value={text} onChange={handleChange} /> </label> </form> ); } export default App;
See the Pen React フック onChange event.target.value by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
ここでは、useStateフックを使用して、text
という名前の状態変数を宣言し、初期値として空の文字列を設定しています。
handleChange
関数は、イベントが発生したときに呼び出され、setText
関数を使用して、event.target.value
を使用して入力フィールドの値を取得し、text
の値を更新します。
また、console.log
関数を使用して、入力値がコンソールに表示されるようにしています。
onChangeプロパティを使用して、入力フィールドが変更されたときにhandleChange
関数が呼び出されるようになっています。
また、value
プロパティを使用して、text
の値を表示しています。
これで、Reactでフォームを作成して、入力フィールドの値を追跡するための状態変数を宣言し、onChangeプロパティを使用して値を更新することができます。
次は、useStateフック、onChange、onClickを使用した単純なリストコンポーネントでのコード例を示します。
テキスト入力からアイテムを追加し、リストに表示する機能として実装します。
import React, { useState } from 'react'; function SimpleList() { const [items, setItems] = useState([]); const [inputValue, setInputValue] = useState(''); const handleInputChange = (e) => { setInputValue(e.target.value); }; const handleAddItem = () => { if (inputValue.trim() !== '') { setItems([...items, inputValue]); setInputValue(''); } }; return ( <div> <input type="text" value={inputValue} onChange={handleInputChange} placeholder="Enter an item" /> <button onClick={handleAddItem}>Add Item</button> <ul> {items.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); } export default SimpleList;
上記のコードでは、useStateを使用してitems
とinputValue
の状態を管理しています。
handleInputChange
関数は、テキスト入力の変更を処理し、inputValue
を更新します。
const handleAddItem = () => { if (inputValue.trim() !== '') { setItems([...items, inputValue]); setInputValue(''); } };
上記の、setItems([...items, inputValue])
は、新しいアイテムをリストに追加するためのコードです。
この行では、現在のitems
配列とinputValue
を組み合わせて、新しい配列を作成しています。
具体的には、スプレッド演算子(...)の...items
を使って現在のitems
配列を展開し、その後ろにinputValue
の値を追加しています。これにより、新しいアイテムがitems
配列に追加されます。
setInputValue('')
では、入力フィールドの値をリセットするためです。この行により、inputValue
の状態が空の文字列に設定されます。これにより、入力フィールドは空になります。
つまり、handleAddItem
関数は、onClickイベントで呼び出され、入力されたアイテムをリストに追加します。
<ul> {items.map((item, index) => ( <li key={index}>{item}</li> ))} </ul>
上記のitems.map
の中で各リストアイテムにkey={index}
を追加しています。
indexを使用しているため、リストの順序が変更されると一意性が保証されません。
一意な識別子(例: ID)が利用可能な場合には、それをkeyとして使用することをおすすめします。
一意な識別子を使用することで、Reactが要素の正しい変更と再描画を管理できます。
そして、map関数を使用してアイテムごとに<li>
要素を生成し表示します。
useStateフックを使用して、子コンポーネントから親コンポーネントにデータをコールバックで渡したい場合は、React Propsの渡し方チュートリアルを参照してください。
当ブログでは、この方法を詳しく解説しています
最後に
これら、イベントハンドラは、特定のDOM要素がクリックされた際に実行されるアクションを決定するために使用されます。
関数コンポーネントにおいては、onClickやonChangeなどのイベントハンドラを使用することが一般的です。また、インラインハンドラも使用されます。
これらのイベントハンドラは、フック、状態の更新、複数の関数の呼び出し、合成イベント、入力フィールドの使用など、さまざまな目的で使用されます。
ただし、コードの可読性と保守性を確保するために、できるだけ簡潔かつ明確なコードを書くことが重要です。
以上が本日の記事となります。
この記事が役に立ったら、ブックマークと共有をしていただけると幸いです。