Pub-Sub는 퍼블리셔라고 하는 메시지 발신자가 특정 수신자(구독자)에게 직접 메시지를 보내도록 프로그래밍하지 않고, 대신 어떤 구독자가 있는지 알 수 없는 상태에서 게시된 메시지를 클래스로 분류하는 메시징 패턴입니다. 마찬가지로 구독자는 하나 이상의 클래스에 관심을 표명하고 관심 있는 메시지만 수신하며, 어떤 게시자가 있는지 알지 못합니다. 출처: Wikipedia
간단하게 말하면 발행자와 구독자는 서로 인식하지 않는다는 뜻이다. 발행자와 구독자는 서로 인식하지 않는데 어떻게 통신을 하고 있을까?
위 그림처럼 중간에 브로커 하나를 두고 중간 다리 역할을 해서 메세지들을 필터링해준다.
//ModalPublisher.jsx
import { event } from "./event";
/** 이벤트 발행하는 곳*/
export const ModalPublisher = {
/** message라는 이름으로 구독이 가능함, content를 인자로 가지는 showModal이라는 이벤트를 등록함 */
message: (content) => {
event.emit("showModal", content);
},
/** 메세지를 지우는 이벤트를 등록 */
clearAllMessage: () => {
event.emit("clearAllMessage");
},
};
//App.jsx
import "./App.css";
import Modal from "./components/Modal";
import { ModalPublisher } from "./components/ModalPublisher";
function App() {
return (
<div className="App">
<Modal showModal={true} />
<h1
onClick={() => {
ModalPublisher.message("요건 앱에서 온 컨텐츠"); // publisher에서 발행한 메세지 사용
}}
>
구독 발행 테스트!
</h1>
</div>
);
}
export default App;
발행된 message()
를 사용하는 App.jsx이다.
Modal 퍼블리셔에서 message와 clearAllMessage라는 이름의 함수를 만들고 event라는 브로커에서 동작을 꺼내오고 message와 clearAllMessage 뿌려준다.
import { useEffect, useState } from "react";
import { event } from "./event";
const Modal = () => {
const [content, setContent] = useState("no content");
const [showModal, setShowModal] = useState(false);
const setMessage = (message) => {
setContent(message);
setShowModal(true);
};
const clearMessage = () => {
setContent("");
setShowModal(false);
};
useEffect(() => {
/** 첫 렌더링 시에 showModal, clearAllMessage 이벤트를 등록한다.*/
event.on("showModal", setMessage).on("clearAllMessage", clearMessage);
}, []);
if (showModal) {
return (
<div>
<h2>{content}</h2>
<button onClick={clearMessage}>모달 닫기</button>
</div>
);
}
return null;
};
export default Modal;
event.on을 통해 브로커에게 해당 이벤트들을 등록 시킨다.
//event.js
// 중간 브로커 역할
export const event = {
list: new Map(),
on(eventType, eventAction) {
// list에 이벤트를 등록하는 메서드 (구독)
this.list.has(eventType) || this.list.set(eventType, []); //eventType은 등록할 함수 이름, eventType에 해당하는 이벤트가 존재하는지 확인 후 없다면 빈 배열로 초기화
if (this.list.get(eventType)) this.list.get(eventType).push(eventAction); //만약 해당 이벤트가 있다면 이벤트 리스트에 eventAction을 추가한다.
return this;
},
emit(eventType, ...args) {
// on으로 등록한 이벤트를 꺼내서 실행시키는 메서드 (발행)
this.list.get(eventType) && // list에 eventType이 있다면 해당 eventType에 있는 eventAction들을
this.list.get(eventType).forEach((cb) => {
// 다 꺼내서 실행한다.
cb(...args); //...args는 cb 이벤트에 들어갈 인자
});
},
};
여기서 보면 on은 구독, emit은 발행 이라고 생각하면 되겠다.
잘 동작하는 것을 볼 수 있다.