過去に実装したコードを振り返ってみた(Modal編)
はじめに
コンポーネントの実装で「カスタムフックを作って、状態と、ビューとなるコンポーネントを組にして返すパターン」の実装が良さそうだったので、react-hooks-use-modal を参考に、過去に作ったModalの実装を振り返ってみました。
この記事では、react-hooks-use-modal の実装と、過去に作ったModalの実装を読んでみた感想を書いてみます。
過去に作ったときのModalの実装
まず、自分が過去に作ったModalコンポーネントの実装を読み返してみました。Modalコンポーネントに状態とコールバック関数をpropsで渡すパターンの実装をしてます。
Modal Sample コンポーネント
const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
min-width: 640px;
display: flex;
align-items: center;
justify-content: center;
`;
export const Modal: FC<ModalProps> = memo(
({ open, onClose, children }) => {
if (open) {
return (
<Overlay onClick={onClose}>
<BaseModal
onClick={(e: Event) => e.stopPropagation()}
>
{children}
</BaseModal>
</Overlay>
);
}
return null;
}
);
react-hooks-use-modalの実装読んでみる
次に、react-hooks-use-modalを読んでみました。カスタムフックからModalコンポーネントとその状態に関する open, close, isOpen が返されていました。
microcmsの公式ブログ。シンプルな感じで良さそうでした。
react-hooks-use-modalが他のモーダルライブラリと違う点として、hooks自身がコンポーネントを返すところが挙げられます。これによって、本来ライブラリの外側で定義する必要があるisOpenフラグ等を内部で定義できるため、よりシンプルなAPI仕様を実現しています。
過去に作ったModalの実装を書き直してみる
過去に作ったModalの実装を、カスタムフックからModalコンポーネントとその状態に関するstateや関数を返すパターンで書き換えてみます。ビューの部分は過去に作ったModalをほぼそのまま使って、状態や状態を変えるロジックを、Modalコンポーネントと合わせて、カスタムフックに切り出してみました。
カスタムフックに切り出すと、Modalに関するすべての情報がカスタムフック内に集約されるので、Modalコンポーネントを呼び出す側がシンプルになるが良かったです。
useModalSample カスタムフック
- Modal2 は過去に作ったコンポーネントをほぼそのまま使っていて、Modal自身、Modalの状態と状態を変更する関数を返すカスタムフックを作成した
import React, { useCallback, useState } from 'react';
import { Modal } from '../components/Modal2';
export type UseModal = () => [
ModalWrapper: React.FC<{ children: React.ReactNode }>,
open: () => void,
close: () => void,
isOpenModal: boolean
];
export const useModalSample: UseModal = () => {
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
const open = useCallback(() => {
setIsOpenModal(true);
}, [setIsOpenModal]);
const close = useCallback(() => {
setIsOpenModal(false);
}, [setIsOpenModal]);
const ModalWrapper = useCallback(
({ children }) => (
<Modal isOpen={isOpenModal} onClose={close}>
{children}
</Modal>
),
[isOpenModal, close]
);
return [ModalWrapper, open, close, isOpenModal];
};
そのほか細かいところ
「カスタムフックを作って、状態と、ビューとなるコンポーネントを組にして返すパターン」に関すること以外で、react-hooks-use-modal を読んで勉強になったことと、Modal作るときに忘れてたことのメモ書きです。
stopPropagationってなんだっけ
stopPropagationってなんだっけ
Modalを閉じる際、一般的にオーバーレイ部分をクリックするか、閉じるボタンのようなものをクリックしたときに閉じられると思います。
オーバーレイ部分をクリックした時に発火するイベントがトリガーになり閉じる挙動となるのですが、オーバーレイ部分に覆われてるModalコンポーネント自体をクリックした場合もオーバーレイ部分のコンポーネントに伝搬して閉じるイベントが発火してしまいます。
それを防ぐのが、stopPropagationです。子コンポーネント側で親コンポーネントに伝搬しないように記述します。
Event.stopPropagation() - Web API | MDN
JavaScript(jQuery)でモーダルを書く時、背景クリックで閉じて中身クリックでは閉じない方法 - Qiita
createPortalとは?
createPortalとは?
react-hooks-use-modal でModalのコンポーネントで使われてたのをみてはじめて知りました。
ポータル (portal) は、親コンポーネントの DOM 階層外にある DOM ノードに対して子コンポーネントをレンダーするための公式の仕組みを提供します。
たとえば、モーダルを実装するさいにroot要素の直下に直接コンポーネントを配置する必要がなくなります。特定のdomNodeを使いたい場合に使用できます。
disableScroll良さそう
Modalが開いてる時に、Modalの後ろの領域をスクロールさせないようにできるライブラリです。過去にModal実装した際は、Modalの後ろのスクロールについて考慮できてなかったので勉強になりました。
まとめ
冒頭に貼ったTweetが気になって試してみましたが、コンポーネントの分割にカスタムフックを使うと、コンポーネントの状態やロジック部分も合わせて分割できるので、呼び出し側がシンプルに呼び出せて良さそうだと思いました。
このパターンのカスタムフックをこれまで作ってなかったので、今回のようにシンプルな実装ができそうな際にまたやってみようと思いました。
参考
この記事もわかりやすかったです。
Discussion