📘

Web ComponentのModalは自分で自分を閉じるな

2024/01/10に公開

例えばx-modalというCustom Element

例えばこういうopenのattributeのboolean値で出現と隠蔽を表現しているModalがあるとします。

<x-modal open>
  contents...
</x-modal>

open attributeを付けると開き、取ると閉じる。

Modalと言えば閉じるボタンとScrim(黒い背景のこと)があり、どちらかをクリックすればModal自身を閉じられます。Scrimによる閉じる機能は無いヤツもたまにあるかも。

閉じるボタンを押した時、open attributeを消しているのは誰でしょうか。

コンポーネントの予測可能性と保守性のためにも、以下の2つの原理に基づいて設計するのを推奨します。

Single Source of Truth

この原則によれば、アプリケーション内のあらゆるデータは一つの場所にのみ存在すべきです。
つまり、Modalの開閉状態は一つの場所から管理されるべきで、それがModal自身か、それとも親コンポーネントか、もしくはアプリケーションの状態管理の一部かもしれません。少なくともStoreを持ったフレームワークライブラリを使っているならば、状態と機能は両方フレームワーク側が持つべきです。
Modal自身がその状態を直接変更すると、状態の一貫性や予測可能性が損なわれる可能性があります。

単方向のデータフロー

このアプローチは、データは一方向にのみ流れるべきという原理です。つまり、データ(この場合はopen属性)は上位のコンポーネントから下位のコンポーネントへと流れる。Modalコンポーネントは、与えられたopen属性を表示する役割を担い、その状態を変更する責任は持たない。状態の変更は通常、親コンポーネントや状態管理システムによって行われるべきです。
これによってデータの流れが明確になり、コンポーネント間の依存関係が減少します。

解決策

this.querySelector('#close_button').addEventListener('click', () => {
  this.dispatchEvent(new CustmoEvent('close'));
});
// 各フレームワークのStoreのロジックに変えてください
document.querySelector('x-modal').addEventListener('close', (e) => {
  e.target.removeAttribute('open');
});

<x-modal>closeイベントを発行し、Light DOM側のスクリプトがattributeの消去を決定します。

例外:閉じたコンポーネント

例外として、外部が状態を気にしないことを前提にしたコンポーネントは自分で自分の状態を管理することが出来ます。

<select>は代表的な例です。<select>のドロップダウンが現在開いているか閉じているか、直接JSから検出することはできません。頑張ればできるでしょうが、基本的にはできなくても問題ないように各フレームワークも対応しています。例えばこのElementをCustomElementとして再実装した<x-select>を作るとして、それが外部から開閉の状態を検出できなくても問題ないならば、自分で自分の状態を管理しても問題ありません。勿論その時はattributeで外部から操作する方法は実装しない方が良いです。よしんばあるとしたらdefault状態のためのattributeか、開閉のためのpublic methodでしょうか。ともかく「元となる状態のデータ」を内部に閉じた状態にするべきです。

参考

Salesforceが提供しているLightning Web Componentなどではルールではなく明確に制限として存在したりする説明があるので読みねえ

Discussion