Reactを使った開発において「イベント処理」と「フォームデータの管理」は欠かせないテーマです。本記事では、React特有のイベント処理の仕組み、フォームデータの収集方法(受控コンポーネントと非受控コンポーネント)、さらに高階関数やカリー化を活用した実装方法を解説します。
1. Reactのイベント処理の仕組み
Reactイベントの特徴
-
合成イベント (Synthetic Event)
Reactではブラウザごとに異なるイベント実装を統一するため、独自の「合成イベント」を提供しています。これにより、クロスブラウザで同じ書き方が可能になります。 -
イベント委譲 (Event Delegation)
各DOM要素に直接リスナーをつけるのではなく、Reactはイベントをコンポーネントの最上位要素に委譲します。これにより、イベント処理が効率化されます。 -
イベントハンドラの書き方
JSXではイベント名は キャメルケース で指定する必要があります(例:onClick
,onChange
)。
使用例
class Demo extends React.Component {
myRef = React.createRef()
myRef2 = React.createRef()
showData = (event) => {
alert(this.myRef.current.value)
}
showData2 = (event) => {
alert(event.target.value)
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="クリック後表示" />
<button onClick={this.showData}>左側データを表示</button>
<input onBlur={this.showData2} type="text" placeholder="フォーカス外れ時に表示" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
ポイントは event.target を使ってイベントが発生した要素を直接取得できる点です。これにより不要なrefの乱用を避けられます(基礎第2弾でお話しした通りrefは必要な時だけ使うべきです)。
2. フォームデータの収集方法
Reactではフォームデータを扱う際に 受控コンポーネント と 非受控コンポーネント の2種類の方法があります。
2.1 受控コンポーネント(Controlled Component)
フォームの入力値をすべてReactのstateで管理します。入力が変わるたびにstateを更新し、Reactが「唯一のデータソース」となります。
class Login extends React.Component {
state = { username: '', password: '' }
saveUsername = (event) => {
this.setState({ username: event.target.value })
}
savePassword = (event) => {
this.setState({ password: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
alert(`ユーザー名: ${username}, パスワード: ${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
ユーザー名:<input onChange={this.saveUsername} type="text" />
パスワード:<input onChange={this.savePassword} type="password" />
<button>ログイン</button>
</form>
)
}
}
メリット:
(1) データの流れが一元化され、UIと状態が常に同期する
(2) バリデーションや入力制御が容易
2.2 非受控コンポーネント(Uncontrolled Component)
refを使ってDOM要素に直接アクセスし、入力値を取得します。フォームのデータをReactが直接管理しないため「非受控」と呼ばれます。
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault()
const { username, password } = this
alert(`ユーザー名: ${username.value}, パスワード: ${password.value}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
ユーザー名:<input ref={c => this.username = c} type="text" />
パスワード:<input ref={c => this.password = c} type="password" />
<button>ログイン</button>
</form>
)
}
}
メリット:
(1) 実装がシンプル
(2) 小規模なフォームに向く
デメリット:
(1) Reactの状態管理から外れるため、大規模フォームでは扱いにくい
高階関数とカリー化を使ったフォーム処理
まず高階関数とは?
・他の関数を引数に取る関数
・関数を返す関数
このどちらかを満たす関数を「高階関数」と呼びます。
例:map・setTimeout・Promiseなど
カリー化
定義:1つの関数で複数の引数を処理する代わりに、関数を返す関数として引数を分けて処理する手法です。カリー化は高階関数の一つの具体的な応用例です。
function sum(a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
Reactでの応用:
クラスコンポーネントのイベント処理において、処理関数に引数を渡す場合、必ずカリー化を使う必要があります。
まずコードを見せます:
class Login extends React.Component {
state = { username: '', password: '' }
saveFormData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value })
}
}
handleSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
alert(`ユーザー名: ${username}, パスワード: ${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
ユーザー名:<input onChange={this.saveFormData('username')} type="text" />
パスワード:<input onChange={this.saveFormData('password')} type="password" />
<button>ログイン</button>
</form>
)
}
}
ここでonChange={this.saveFormData('username')}とonChange={this.saveFormData('password')}の二箇所で引数を渡しましたので、実際にsaveformDataの返り値に相当します。このことでsavaDataFormの返り値は必ず関数でなかればならず、しかも外部の関数(saveFormData)と内部の関数(返り値)はそれぞれdataTypeとeventをパラメータとして持ちます。
つまりここでカリー化を使う必要があります。
もちろんこの場合でカリー化を使わず、二つの関数に分けて(例えばsaveFormUsernameとsaveFormPassword)引数を渡さない形に書いても実装できますが、フォームの入力をより効率的にまとめるという点からは、やはりカリー化の書き方のほうが優れます。このようにすれば、複数の入力欄を1つの関数で管理できます。
まとめ
・イベント処理
Reactは合成イベントとイベント委譲を採用しており、効率的かつクロスブラウザ対応が可能です。
・ フォームデータの収集
受控コンポーネント → stateでデータを完全に管理
非受控コンポーネント → refでDOMに直接アクセス
・高階関数とカリー化
フォーム処理をシンプルかつ再利用可能にする強力なテクニックです。
Reactを使ったフォーム開発では、まず受控コンポーネントをベースに設計し、必要に応じて非受控コンポーネントや高階関数を活用するのがベストプラクティスです。