🕊

親のないReact Componentから Snackbar どんどんpush!? Observer Patternでやってみた

2023/02/24に公開

挨拶

ポートでフロントエンドを担当している金髪新卒クァドリンガルの @eicho.chin 🌝です。
最近は公式ドキュメント早く読みたい気持ち &自分の言語習得のノウハウをシェアしたい気持ちがあります。
エンジニアでありながら、英語馴れましょ~🥮のりで、英語のサイトを輪読勉強会📚を主催しています。

なんの輪読勉強会?

こちらのサイトをしようさせていただいています。patterns.dev
現代のJsのあれこれを難しくない言葉&サンプルで解説のサイトになります。
(言葉を翻訳し、意味を理解ではなく、口に出して、感で理解&単語フレーズを印象つけする予習復習厳禁な勉強会です。)

今回はそのなかの Observer Pattern を実際使用した実装があります。

wikipediaからの:https://ja.wikipedia.org/wiki/Observer_パターン

Observer パターン(オブザーバー・パターン)とは、プログラム内のクラスインスタンスで起きたイベント(事象)を、他のクラスインスタンスへ通知する処理で使われる、デザインパターンの一種。

実装要望

サーバサイドレンダリングの1画面に、ボタンをボンボン押します。そのたびに、非同期通信がバックエンドへリクエストします。その結果をsnackbarという形式でフィードバックを表示します。 <- 簡単そうに見えます(あげぇ 🤙🤙🤙🤙

  1. そのボタンちゃんたちは複数個あります、一個に一個は独立したReact component になります(あらま)。
  2. プロジェクトには snackbarという形式は多く採用されてません、便利そうなライブラリーもインストールされてません。(入れる予定もありません🧏 😉 えっへ)
  3. snackbarちゃんは順番に出して欲しいです

自然に考えると、Queueのような仕組みが頭に浮かび上がります。ちょうど最近の勉強会で似たようなパターンがインプットしました。それを元に作りました。

一個の中枢タワーのようなClassからsnackbar表示を実行

1 画面にロードする 1 js ファイルに、observer パターンの基本な構造はこちらになります。

class Subject {
    constructor() {
      this.observers = [];
    }
    subscribe(observer) {
      this.observers.push(observer);
    }
    unsubscribe(observer) {
      this.observers = this.observers.filter(obs => obs !== observer);
    }
    notify(message) {
      this.observers.forEach(observer => observer(message));
    }
  }

  const subject = new Subject();
  1. subscribe => 実行して欲しいものをQueueにpush
  2. unsubscribe => 実行完了や不要の場合 Queue から削除
  3. notify => 実行してくださいという指令をだす モノホンの 実行メソッド

こちらのsubjectを各ボタンコンポーネントに渡しましょ

  document.querySelectorAll('.js-button-component').forEach((idex, node) => {

    ReactDOM.render(
      <ButtonComponent
        idx={idx}
        snackbarDispatcher={subject}
      />,
      node,
    );

実際snackbar showの処理は各呼び出し元で定義

各button componentで、snackbar show functionを定義。ボタン押したら、snackbar show まで後1歩です㌧!

// button component
const renderFunc = (buttonValue) => {
 const snackbarDiv = document.createElement('div');
 const domId = `js-${Math.random().toString(32).substring(2)}`; // 乱数生成
 snackbarDiv.id = domId;
 document.body.appendChild(snackbarDiv);
 ReactDOM.render(<Snackbar buttonValue={buttonValue} domId={domId} />, snackbarDiv);
};

// ボタンのonClick にsnackbar showロジック
const onClick = (buttonValue) => {
 if (notAbleToVoteCondition) return;

 axios.post(`/api/buttonsssssss_come_on`).then((res) => {
           const callback = () => renderFunc(buttonValue);
           snackbarDispatcher.subscribe(callback);
           snackbarDispatcher.notify();
           snackbarDispatcher.unsubscribe(callback);
    });
  )
};

自分自身domを削除までやる、真面目ちゃんなsnackbar 💖

const Snackbar = ({ value, domId }) => {
  const [show, setShow] = useState(true);
  const snackBar = useRef(null);

  // componentはrender後にtimer開始
  useLayoutEffect(() => {
    setTimeout(() => {
      if (!snackBar.current) return;
      setTimeout(() => {
        setShow(false);
        const renderDom = document.querySelector(`#${domId}`);
        if (renderDom) {
          renderDom.remove();
        }
      }, 1000);
    }, 4000);
  }, [show]);

  return (
      <div className="snack-bar" style={{ display: show ? 'block' : 'none' }} ref={snackBar}>
        <div className="is-fade-up">
          <p>
            <b>{value}</b>押しました
          </p>
        </div>
      </div>
  );
};
コードのイメージ図

実際押してみます、一見処理がバラバラに分けたが、実際順番通りに出ることが感動

感想

functionを変数に保存できることが大好きです。
現代JavaScript のデザインパターンがインプットして、実際業務に役に立てることがやはり嬉しいものです。🐦

Discussion