💭
Dialog表示のベストプラクティス
概要
こんにちは、めなしといいます。
今回はMUIを利用したダイアログ表示の個人的ベストプラクティスを紹介します。コメントでバシバシ指摘していただけると幸いです。
事の起こり
業務で触れるうちに下記のようなコードに遭遇しました。
const Manager = () => {
const [dialogKind, setDialogKind] = useState(undefined);
return (
<>
<DialogManager dialogKind={dialogKind} />
<Container setDialogKind={setDialogKind} />
</>
);
};
const DialogManager = ({ dialogKind }) => (
<>
{dialogKind === "create" && <Dialog>ダイアログの内容ベタ書き</Dialog>}
{dialogKind === "edit" && <Dialog>ダイアログの内容ベタ書き</Dialog>}
{dialogKind === "delete" && <Dialog>ダイアログの内容ベタ書き</Dialog>}
</>
);
const Container = ({ setDialogKind }) => {
<>
<Button onClick={() => setDialogKind("create")}>作成</Button>
<Button onClick={() => setDialogKind("edit")}>編集</Button>
<Button onClick={() => setDialogKind("delete")}>削除</Button>
</>;
};
この状況では以下のような問題点が挙げられます
- VSCodeのリファレンス機能(定義へ移動)が使えずリーディングに時間がかかる
- 一つのコンポーネントにダイアログが複数記載されており、可読性に欠ける
- ボタンが全て再描画される
リファクタ中にそもそもダイアログを別コンポーネントとして分離するのはもちろんですが、ステートの管理をどうするかという課題が残りました。
そこで提案したいのは
ボタンにダイアログのステートを持たせる
です。
ボタンとダイアログを組み合わせた「ダイアログボタン」について
コンポーネントの関心事を「ひとつのダイアログの開閉状態」に絞るため、「開くためのボタン」とステートを一つのコンポーネントに配置します。
そして表示されるダイアログとセットにすることで、「一つのダイアログの開閉を司るコンポーネント」の完成です。
コード
以下のようになります。
const CreateDialogButton = () => {
const [open, setOpen] = state(false);
return (
<>
<Button>作成</Button>
<CreateDialog open={open} /> {/* 作成ダイアログ */}
</>
);
};
このコードでは、
- ButtonとDialogが近く、どのボタンでどのダイアログが開くか明確
- stateが一つのボタンにしか渡らないためボタンの再描画が抑えられる
というメリットが存在します。しかしながらロジックに関してはまだ万全とは言えません。この構造だと全てのコンポーネントにuseState
を書く必要がありますね。
そこで、高階コンポーネントを採用すると、
const DialogButton = ({ dialogContent }) => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>開く</Button>
<Dialog open={open} onClose={() => setOpen(false)}>
{dialogContent}
</Dialog>
</>
);
};
このような形になるでしょう。この状態であれば、前述のメリットを保ったままstate管理ができるようになります。
課題
この構造上、親のコンポーネントがアンマウントされると表示されたダイアログも消えてしまいます。
ポップアップのメニューアイテムなどでは利用しにくい設計になっています
まとめ
ダイアログの状態管理を子コンポーネントに移譲することで、再描画を抑え可読性の高い実装にすることができ、さらにテストもしやすいコードになりました!
2022/12/29 追記
下記レポジトリに今回作成したコンポーネント類を置いておきます。yarn start
で動作を見ることができるので、確認してみてください。
Discussion