📘

イベントハンドラの命名規則を作る上で考えたこと

2024/08/04に公開

はじめに

最近社内のフロントエンドチームでコーディング規約を作っているのですが、 「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 要素)を扱う際にはhandleClickonClickというイベントハンドラを使うことは多々あります。

具体的に関数名のつけ方を考えてみる

ではイベントハンドラにどういう関数名をつければよいのでしょうか?

ここは色々と意見が分かれるところだとは思いますが、handleSomethingonSomethingSomethingには 「イベントを検知してどのような処理が実行されるのか?がイメージしやすい名前」 を入れれば良いと思います。

個人的には

  • 動詞+対象物
  • 対象物+形容詞(=対象の状態)

で良いと思っています。

例として下記のような Todo リストを考えてみましょう。
子のTodoItem は props としてonCompleteItemというイベントハンドラを受け取り、親のTodoListhandleCompleteItemという関数を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;

handleClickonClickにするよりも関数の目的(ここでは Todo リストを完了にする)が明確なのは明らかです。

ただし例外として、下記のようにイベントオブジェクトを使用する必要がある場合は別です。
この場合は次の記事にもある通り、自分もhandleClickで良いと思います。あくまで実行関数(=onCompleteItem)が主体であるためです。もう少し handleClick 内で複雑なコンポーネント起因の処理を行うことになったら命名を考えますが...

https://qiita.com/uhyo/items/bb4f731127bfcc20c754

イベントオブジェクトが必要になるパターン
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 つとっても奥が深いなぁ...

参考文献

https://ja.react.dev/learn/responding-to-events

https://ja.react.dev/learn/tutorial-tic-tac-toe#why-immutability-is-important

https://zenn.dev/frontendflat/articles/react-event-handler-name

https://qiita.com/uhyo/items/bb4f731127bfcc20c754

脚注
  1. こちらの表現は uhyo さん執筆のこちらの記事を参考にさせていただいております。
    https://qiita.com/uhyo/items/bb4f731127bfcc20c754 ↩︎

COUNTERWORKS テックブログ

Discussion