こんにちは!
Uzabase の SaaS Product Team に所属している樽本と申します。普段は SPEEDA の開発をしています。
今日は「DOM のイベントが発火(ハンドリング)する順序を考慮した実装はやめたほうが良い。」という個人的な教訓と、それを得るに至ったストーリーの紹介をします。タイトルは願望です。
※開発時のブラウザは Chrome でしたので、その他のブラウザでの検証は行っていません
ある日のこと
フロントエンドアプリケーションをカキカキワチャワチャしていたある日、以下のようなコード(諸々省略)で問題は発生しました。
.... <input type="text" onblur="blur()" /> <button class="hoge-button" onclick="click()"></button> ....
同階層に配置されている button と input があり、それぞれ異なるイベントに対してハンドラが書かれていますね。
上記のコードでは input の blur が先に処理され、次に button の click が走ります(ほとんどのブラウザでそうだと思います)。
問題は、「blur()の副作用が click()に影響を与えてしまう」ことでした。 blur()でとある State を更新しており、その副作用が click()の結果に影響を与えてしまっていました。
そして、click()が先に処理されれば E2E テストをパスできることは分かっていため、当時の我々は「イベントの順番をどうにかできないかね?」と調査を開始してしまったのでした...。
たどり着いてしまった解決策
調べていくうちに、「同階層の要素間では、必ずしも先 に(上部に)定義されている要素のイベントが先に発火するわけではなく、イベントによっては後に(下部に)定義されている要素のイベントが先に発火するようだ」ということがわかりました。
そして(当時では)幸運なことに、button の mouseDown イベント(click と似た条件で発火するイベント)は input の blur よりも先に発火しそうなこともわかってきました。
「やった!これで button のイベントは先に検知できるぞ!!」
と、当時の我々は DOM を司る神に感謝を捧げ、意気揚々と mouseDown のハンドリング処理を書き始めました。
冷静になった
書き始めた数秒後、冷静になりました。幸いにも私は冷静になることが得意な人間でした。
「いや、こんな暗黙の了解を実装に含めたらイカンでしょ。」 「そもそも input と button が同階層にいることから見直せばいいじゃん。」
心の中のイマジナリー先輩(めっちゃ強い)も同じ意見でした。
冷静になって mouseDown イベントとお別れを済ませた我々は、Uzabase の Product Team が大事にしている「とりあえず相談する」の精神で社内のフロントエンド強者に声を掛け、設計を見直すことで問題を解決することが出来ました。
とりあえず相談するって本当に大事
今思えば当たり前のことでしたが、問題が発生したときには思いもよらぬ視野の狭窄が生まれがちで、眼前の問題に固執した答えを出してしまうことはよくあると思います。
だからこそ、とりあえず相談する精神で第三者の意見を貰うことは本当に大事です。
「他人の時間を奪ってしまう」と思わず、「ここでベターな設計が出来れば長期的に見て良い物が作れる」と思ってドンドン相談を仕掛けていくことが肝要ですね。
また、本記事のように問題が発生していれば(もしくは可視化していれば)こそ「相談する」というアクションも取りやすいものですが、問題と認識していないことにも「相談する」ことは有効です。
「問題と認識していないこと」は灯台下暗しで「未知の未知」を含んでいる可能性があるからです。
わからないこと、わかっていることに限らず、ある程度の粒度を超えた論点はドンドン相談しに行って、より良いプロダクトを作っていきたいですね。