親のないReact Componentから Snackbar どんどんpush!? Observer Patternでやってみた
挨拶
ポートでフロントエンドを担当している金髪新卒クァドリンガルの @eicho.chin 🌝です。
最近は公式ドキュメント早く読みたい気持ち &自分の言語習得のノウハウをシェアしたい気持ちがあります。
エンジニアでありながら、英語馴れましょ~🥮のりで、英語のサイトを輪読勉強会📚を主催しています。
なんの輪読勉強会?
こちらのサイトをしようさせていただいています。patterns.dev
現代のJsのあれこれを難しくない言葉&サンプルで解説のサイトになります。
(言葉を翻訳し、意味を理解ではなく、口に出して、感で理解&単語フレーズを印象つけする予習復習厳禁な勉強会です。)
今回はそのなかの Observer Pattern を実際使用した実装があります。
wikipediaからの:https://ja.wikipedia.org/wiki/Observer_パターン
Observer パターン(オブザーバー・パターン)とは、プログラム内のクラスインスタンスで起きたイベント(事象)を、他のクラスインスタンスへ通知する処理で使われる、デザインパターンの一種。
実装要望
サーバサイドレンダリングの1画面に、ボタンをボンボン押します。そのたびに、非同期通信がバックエンドへリクエストします。その結果をsnackbarという形式でフィードバックを表示します。 <- 簡単そうに見えます(あげぇ 🤙🤙🤙🤙
- そのボタンちゃんたちは複数個あります、一個に一個は独立したReact component になります(あらま)。
- プロジェクトには snackbarという形式は多く採用されてません、便利そうなライブラリーもインストールされてません。(入れる予定もありません🧏 😉 えっへ)
- 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();
- subscribe => 実行して欲しいものをQueueにpush
- unsubscribe => 実行完了や不要の場合 Queue から削除
- 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