한 사이트가 여러 탭에서 떠 있을 때, 탭 사이에서 통신이 필요한 경우가 있을까?
이를 달성할 수 있는 방법이 무엇이 있을까?
놀랍게도 로컬 스토리지에도 event를 지원한다. 이 event를 활용해서 localStorage의 변화를 감지하는 방법이다.
React.useEffect(() => {
function listener(event: StorageEvent) {
if (event.storageArea !== localStorage) return
if (event.key === LOGGINED) {
setLoginTime(parseInt(event.newValue || '0', 10))
}
}
window.addEventListener('storage', listener)
return () => {
window.removeEventListener('storage', listener)
}
}, [])
https://codesandbox.io/s/tab-communications-1-localstorage-5ldjw
잘 작동하는 것 같지만 몇가지 문제가 존재한다.
BroadCast Channel API는 탭, 윈도우, 프레임, iframe 그리고 Web worker 간에 통신을 할 수 있게 해주는 API다.
이 방법을 쓰면, 브라우저 콘텍스트 간에 통신이 가능해진다.
const LOGGINED = "loggedIn";
const channel = new BroadcastChannel(LOGGINED);
export default function App() {
const [loginTime, setLoginTime] = React.useState<number>(() =>
parseInt(window.localStorage.getItem(LOGGINED) || "0", 10)
);
React.useEffect(() => {
function listener(event: MessageEvent) {
setLoginTime(event.data);
}
channel.addEventListener("message", listener);
return () => {
channel.removeEventListener("message", listener);
};
}, []);
return (
// jsx
);
}
https://codesandbox.io/s/tab-communications-2-braodcast-channel-m50d6
코드가 어딘가 이상하다면 그냥 무시해주셈
다만 문제점은 Broadcast Channel Api는 너무 힙한 나머지 사파리와 IE에서 쓸 수 없다는 점ㅣ다.
서비스 워커를 이용하는 방법도 있다.
window.navigator.serviceWorker.controller?.postMessage({
[LOGGINED]: currentDateTime,
})
그리고 이 정보를 서비스워커에서 받으면 된다. 그러나 서비스 워커를 세팅하는 것은 쉽지 않고, 추가적으로 serviceWorker.js
등을 만드는 등의 노력이 필요하다. 그리고 서비스 워커도 마찬가지로 IE에서 지원하지 않는다.
가장 전통적이고도 널리 쓰이는 방식은 window.postmessasge다. 아마 대다수의 서비스들이 이 방식을 쓰고 있을 것이다.
targetWindow.postMessage(message, targetOrigin)
window.addEventListener(
'message',
(event) => {
if (event.origin !== 'http://localhost:8080') return
// Do something
},
false,
)
이 방법의 장점은 cross-origin을 지원한다는 것이다. 그러나 단점은 위 코드에서 알 수 있듯이 브라우저 탭의 레퍼런스를 가지고 있어야 한다. (targetWindow
를 가지고 있는 것 같이) 그래서 이 방식은 window.open()
이나 document.open()
을 통해서 탭을 열었을 때만 사용 가능하다.