イベントハンドラの命名規則を作る上で考えたこと
はじめに
最近社内のフロントエンドチームでコーディング規約を作っているのですが、 「React のイベントハンドラの命名って結局どうすりゃいいん?」 って話で盛り上がりましたので、そのときの内容について少し書いていきたいと思います。
公式ドキュメントにはどのように書かれているのか...?
まずとにもかくにも公式ドキュメントです。
イベントハンドラの命名については何度か言及されており、読んだことがある方も多いのではないでしょうか?
たとえば、「イベントへの応答」には次のように書かれています。
慣習的に、イベントハンドラは handle の後ろにイベントの名称をつなげた名前にすることが一般的です。onClick={handleClick}、onMouseEnter={handleMouseEnter} などがよく見られます。
...
慣習として、イベントハンドラのプロップは on で始まり、次に大文字の文字が続くようにします。たとえば、Button コンポーネントの props である onClick は onSmash と命名することも可能です
また、「チュートリアル:三目並べ」にも同じようなことが書かれています。
React では、イベントを表す props には onSomething という名前を使い、それらのイベントを処理するハンドラ関数の定義には handleSomething という名前を使うことが一般的です。
まとめると
- コンポーネント内部に直接定義する場合は
handleSomething
- props として渡す場合は
onSomething
と分けて命名しようと言っています。非常にシンプルでわかりやすいですね
props としてのイベントハンドラの命名について
実は「イベントへの応答」には次のようなことも書かれています。
コンポーネントが複数種類のインタラクションをサポートする場合、イベントハンドラの props をアプリ固有の概念に基づいて命名することができます。
例えば、この Toolbar コンポーネントは onPlayMovie と onUploadImage というイベントハンドラを受け取ります
サンプルコードexport default function App() { return ( <Toolbar onPlayMovie={() => alert('Playing!')} onUploadImage={() => alert('Uploading!')} /> ) function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Play Movie </Button> <Button onClick={onUploadImage}> Upload Image </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Toolbar が onPlayMovie や onUploadImage をどう扱うのかを、App コンポーネントが知る必要がないことに注意してください。それは Toolbar の実装の詳細です。
ここでは、Toolbar はそれらを Button の onClick ハンドラとして渡していますが、後でキーボードショートカットでもそれらをトリガするようにすることができます。onPlayMovie のようなアプリ固有のインタラクションに基づいて props を名付けることで、後でどのように使用されるかを変更できるという柔軟性が得られます。
要するに
- Toolbar というコンポーネントは DOM レベルの関心事[1](ボタンがどういう実装になっているのか?ボタンでどのようにイベントを検知して処理を実行するのか?)を内包している
- App 側ではそのことを知る必要はない
- そのためイベントハンドラの props に
onClick
のような命名をしてしまうと、Toolbar の役割がボタンをクリックすることに限定されかねないのでアプリ固有のインタラクションに基づいた関数名にしたほうが柔軟性があがる- 限定したいならより適切なコンポーネント名に変えるなどする必要がある
こんな感じのことを言っています。
脳死でonSubmit
などのような props 名を定義しがちな人にとっては耳が痛い話でしょう。なんでもかんでもonイベント名
にちなんだ名前にするなよと釘を指しているわけですね
とはいえ例外も存在している
上記のように言ってはいるものの、直接イベント名にちなんだ名前や動詞を使用するケースは多々あります。
みなさんがもっとも目にすることが多いところでいうとフォームでしょう。
私もフォームを扱うことは往々にしてあるのですが、その場合はonSubmit
という props 名を定義することはあります。
フォームの主な目的としては「情報を送信すること」であるためコンポーネントの主要な動作を表す動詞として submit を解釈すると、onSubmit
として props を渡しても違和感ないでしょう。
const Form = ({ onSubmit }) => {
const handleSubmit = (e) => {
...
// 親コンポーネントに通知
onSubmit();
};
return (
<form onSubmit={handleSubmit}>
{/* フォームの内容 */}
</form>
);
};
また DOM や UI を直接的な責務とするコンポーネント(あるいはネイティブの HTML 要素)を扱う際にはhandleClick
やonClick
というイベントハンドラを使うことは多々あります。
具体的に関数名のつけ方を考えてみる
ではイベントハンドラにどういう関数名をつければよいのでしょうか?
ここは色々と意見が分かれるところだとは思いますが、handleSomething
やonSomething
のSomething
には 「イベントを検知してどのような処理が実行されるのか?がイメージしやすい名前」 を入れれば良いと思います。
個人的には
- 動詞+対象物
- 対象物+形容詞(=対象の状態)
で良いと思っています。
例として下記のような Todo リストを考えてみましょう。
子のTodoItem
は props としてonCompleteItem
というイベントハンドラを受け取り、親のTodoList
はhandleCompleteItem
という関数をonCompleteItem
に渡しています。
import React, { useState } from "react";
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: "買い物に行く", completed: false },
{ id: 2, text: "宿題をする", completed: false },
{ id: 3, text: "運動する", completed: false },
]);
const handleCompleteItem = (todoId) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === todoId ? { ...todo, completed: true } : todo
)
);
};
return (
<div>
<h1>やることリスト</h1>
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onCompleteItem={handleCompleteItem} />
))}
</ul>
</div>
);
}
function TodoItem({ todo, onCompleteItem }) {
return (
<li style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
{todo.text}
{!todo.completed && (
<button onClick={() => onCompleteItem(todo.id)}>完了</button>
)}
</li>
);
}
export default TodoList;
handleClick
やonClick
にするよりも関数の目的(ここでは Todo リストを完了にする)が明確なのは明らかです。
ただし例外として、下記のようにイベントオブジェクトを使用する必要がある場合は別です。
この場合は次の記事にもある通り、自分もhandleClick
で良いと思います。あくまで実行関数(=onCompleteItem
)が主体であるためです。もう少し handleClick 内で複雑なコンポーネント起因の処理を行うことになったら命名を考えますが...
function TodoItem({ todo, onCompleteItem }) {
const handleClick = (e) => {
e.preventDefault();
console.log("Completing todo item:", todo.text);
onCompleteItem(todo.id);
};
return (
<li style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
{todo.text}
{!todo.completed && (
<button onClick={handleClick}>完了</button>
)}
</li>
);
}
結論
まとめると
- コンポーネント内部に直接定義する場合は
handleSomething
-
props
として渡す場合はonSomething
-
Something
にイベント名をいれるケースとしてはより具体的な動作に近いレベルの末端のコンポーネント(フォームや atom レベルのコンポーネント)を扱うとき - それ以外のコンポーネントに関しては、どのような処理が実行されるのか?がイメージしやすい関数名にする
- 動詞+対象物
- 対象物+形容詞(=対象の状態)
おわりに
イベントハンドラの命名 1 つとっても奥が深いなぁ...
参考文献
-
こちらの表現は uhyo さん執筆のこちらの記事を参考にさせていただいております。
https://qiita.com/uhyo/items/bb4f731127bfcc20c754 ↩︎
ポップアップストアや催事イベント向けの商業スペースを簡単に予約できる「SHOPCOUNTER」と商業施設向けリーシングDXシステム「SHOPCOUNTER Enterprise」を運営しています。エンジニア採用強化中ですので、興味ある方はお気軽にご連絡ください! counterworks.co.jp/
Discussion