9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

株式会社ゆめみAdvent Calendar 2023

Day 9

いいから黙ってpropsのコールバックは`on`から始めておけ

Last updated at Posted at 2023-12-08

この記事について

弊社(株式会社ゆめみ)のフロントエンドで開催された「私の強い思想を聞いてくれLT会」という勉強会で発表した内容をせっかくなので記事として残しておこうと思い、執筆しました。

勉強会の全体像については、開催してくれた @taka10257 さんによって記事にされているのでぜひこちらをご覧ください。
私の強い思想を聞いてくれLTという勉強会のログ

本題: いいから黙ってpropsのコールバックは on から始めておけ

結論: (いいから黙って強い思想はまず結論から述べておけ)

とにかくpropsの関数の命名は on から始めるようにしましょう。
そうすれば、適切なpropsの命名、コンポーネントの良い設計に自然となっていきます。

コンポーネントに渡すコールバック関数は親コンポーネントと子コンポーネントのインタフェースです。
子は親の実装を意識しなくてよくて、親は子の実装を意識しなくていい状態が理想です。

子コンポーネントの何かしらのイベントによってコールバックが実行されるので、基本的には常に onHogeEvent という on から始まるpropsの命名にできるはずです。

React や Vue.js の公式のドキュメントでも「関数をpropsに渡せるよ〜」というような紹介ではなく、「コンポーネントで起こったイベントにハンドラーを渡せるよ〜」というように紹介されています。

on から始めていない時

on から始めていない時、どういった問題が起こってくるかというのをいくつかのアンチパターンを示しながら紹介したいと思います。

LT開始数時間前に考えたアンチパターンなので最適なパターンではないことをここに保険としてかけておきます。

setHoge のようなpropsを定義したケース

すごくズボラな実装をしたいとき、以下のようにしてしまうことがあります。

const [hoge, setHoge] = useState()
<Hoge setHoge={setHoge}>

親コンポーネント側は setHoge というstateのセッター関数を渡すだけでいいのですごく楽ですね。

これが子コンポーネントで使われるケースについて考えてみると、面倒なことが起きてきます。

const Hoge = ({ setHoge }) => {
  const handleSubmit = (hoge) => {
    setHoge(hoge)
  }
  const handleFetchCompleted = (hoge) => {
    setHoge(hoge)
  }
  // ...
}

setHoge は子コンポーネントのどこででも呼ばれる可能性があります。

hoge stateが更新されていることを前提としている機能を親コンポーネントで実装するときに、どこでそのstateが更新されているか確認するために子コンポーネントを見に行かないといけなくなります。

また、fuga というstateも更新したくなったら setFuga もpropsとして渡さないといけ無くなってしまいます😭

stateのセッター関数を直接コンポーネントのpropsとして渡すような実装は絶対に避けた方が良いです。


saveSometing, resetSometing のような特定の動作をする関数をpropsとして定義したケース

こういう実装をしたとします。

<Hoge saveSomething={saveSomething} resetSomething={resetSomething}>
const Hoge = ({ saveSomething, resetSomething }) => {
  const handleSubmit = () => {
    // ...
    saveSomething(something)
  }
  const handleCancel = () => {
    // ...
    resetSomething()
  }
  // ...
}

特定の動作をする関数をpropsとして受け取っているので、

  • コンポーネントは親コンポーネントからどういう関数を渡されるかに依存した実装になってしまう
  • 親コンポーネントからは渡した関数がどこで呼び出されているかわからない

という2つの問題を抱えることになります。

例えば、以下のコードのように渡している関数が複数の箇所で呼ばれているかもしれません。

const Hoge = ({ saveSomething, resetSomething }) => {
  const handleSubmit = () => {
    saveSomething(something)
    resetSomething()
  }
  const handleCancel = () => {
    resetSomething()
  }
  const handleOutsideClick = () => {
    resetSomething()
  }
  // ...
}

もし渡している resetSomething という関数の機能を変更したいとなったときに、子コンポーネントの実装を確認して呼び出されている場所を確認しながらバグが起こらないように注意しながら更新することになります。

以下のようにイベントベースでpropsを命名しておけば、そのイベントが起こったときに呼び出されることが明確になります。

実行する処理が同じでいいのであれば同じ関数を渡して仕舞えば良いだけなので変更にも耐えやすいコンポーネントとして実装することができます。

const handleSubmit = () => {
  saveSomething()
  resetSomething()
}

const handleCancel = () => {
  resetSomething()
}

const handleOutsideClick = () => {
  resetSomething()
}

<Hoge
  onSubmit={handleSubmit}
  onCancel={handleCancel}
  onOutsideClick={handleOutsideClick}
  // or
  // onOutsideClick={handleCancel}
>
const Hoge = ({ onSubmit, onCancel, onOutsideClick }) => {
  const handleSubmit = () => {
    // ...
    onSubmit(something)
  }
  const handleCancel = () => {
    // ...
    onCancel()
  }
  const handleOutsideClick = () => {
    // ...
    onOutsideClick()
  }
  // ...
}

こうすることで、 Hoge というコンポーネントのインタフェースが明確になりますし、Hoge コンポーネントでイベントが起こった時に実行される処理の内容は親コンポーネント側でハンドリングできるので、お互いに疎結合な良いコンポーネントになります。

(余談) handleHogeEvent っていうprops名はよくないの?

そんなのわかってるよって人も多いと思うのでトグルの中に突っ込んでおきます。

`onHogeEvent` と `handleHogeEvent`の命名について

たまに、

const handleHogeEvent = () => {}
<Hoge handleHogeEvent={handleHogeEvent}>

というような実装をしているケースを見ることがあります。

これは、

const handleHogeEvent = () => {}
<Hoge onHogeEvent={handleHogeEvent}>

のようにしておくべきです。

onHogeEventhandleHogeEventはどう違うのでしょうか?

onHogeEventHogeEvent というイベントが起こったときに、渡されたコールバック関数を実行しますよというのを示しているコンポーネントのpropsの命名です。

handleHogeEvent は、 HogeEvent というイベントをハンドルするためのハンドラー関数だというのを示した命名です。

この2点を覚えて適切に使っておけば、大丈夫です!

これからは handleHogeEvent をprops名にするのはやめて、 onHogeEvent をprops名にしましょう。

まとめ

  • とりあえずonから始まる命名を考えてみる。
  • コンポーネントに関数を渡すときは、イベントハンドラーとして渡す。

この2点を意識することで、かなり良いコンポーネント設計になると思います。

ぜひpropsに関数を渡したくなったときに、 onから命名をはじめられないか というのを最初に考えるようにしてもらえると嬉しいです。

LT後の学び

LT中のコメントでESLintのプラグインでonから始まる命名にするように矯正するルールがあることを教えてもらいました。
これはとても便利ですね。

これはぜひ全プロジェクトに入れましょう。(強い思想)

(ちなみに筆者はまだ使ったことないです😅)

最後に

面白いイベントを社内で企画してくれた @taka10257 さんの記事をもう一度紹介させていただきます。
私の強い思想を聞いてくれLTという勉強会のログ

「私の強い思想を聞いてくれLT会」という企画に対してすぐに話したいことがたくさん出てくるメンバーもたくさんいて、とても面白い勉強会でした。
2回目も企画するかもしれないとのことなので、楽しみにしていたいな〜と思います。

今回の記事の内容はあくまで私の個人的な思想も含んだものなので、異論もあると思います。
個人的には人の強い思想を聞き入れつつ、自分の強い思想を育んでいくのが良いと思ってますので、ぜひ皆さんの強い思想も聞いてみたいと思います。
その際は、ゆめみに入社して話していただくか、ぜひ記事にしていただければと思います。

最後まで読んでいただきありがとうございました。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?