728x90
๐ ์์ฝ ์คํจ โ PDP ์๋ก๊ณ ์นจ, ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฉ์์ง ํต์ ์ผ๋ก ํด๊ฒฐํ๊ธฐ
๋ฌธ์ ์ํฉ
๋์ฑ์ผ๋ก ์์ฝ์์ฑ ์คํจ ์๋ฟ ํด๋ฆญ์ ํ์ฌ ํ์ด์ง(์์ฝ๊ฒฐ์ ํ์ด์ง)๊ฐ ๋ซํ๊ณ PDPํ์ด์ง๋ก ๋์์ค๊ฒ ๋๋ค. ์ด๋ PDP๋ ์ํ์ ์ต์ ์ ๋ณด(์ํ/์ต์ /์์ดํ ํ๋งค ๊ฐ๋ฅ, ๊ฐ๊ฒฉ ๋ฑ)๋ฅผ ๋ค์ ๋ถ๋ฌ์์ผํ๋ค. ๊ทธ๋ ์ง์์ผ๋ฉด ์ ์ ๋ ์์ฝ์ด ๋ถ๊ฐ๋ฅํ๋ ๊ธฐ์กด ์ค๋ ์ท ์ ๋ณด๋ฅผ ๊ณ์ ์ฌ์ฉํ๊ฒ๋์ด ์ข์ง๋ชปํ UX๋ฅผ ๊ฒฝํํ๊ฒ ๋๋ค.
๋์ ๋ถ์
- visibilityChange ์ด๋ฒคํธ ํ์ฉ
- ์ฅ์ : ๊ฐ๋จํ ๊ตฌํ (ํ์ด์ง๊ฐ ๋ค์ ํ์ฑํ ๋ ๋ ์ํ ์ ๋ณด API ํธ์ถ)
- ๋จ์ : ์์ฝ ์คํจ ์ํฉ๊ณผ ๋ฌด๊ดํ๊ฒ ๋จ์ํ ํญ ์ ํ๋งํด๋ API๋ฅผ ํธ์ถํ๊ฒ ๋๋ค. ์ด๋ ์ค์๊ฐ์ผ๋ก ์กฐํํ๋ API ํน์ฑ์ ๋ถํ์ํ ๋คํธ์ํฌ ๋น์ฉ์ด ๋ฐ์ํ๊ฒ ๋์ด ์๋ฒ/ํด๋ผ์ด์ธํธ ๋ชจ๋ ๋ถ๋ด์ด ํฌ๋ค.
useEffect(() => {
const handleVisibliityChange = () => {
// ์ํ์ ๋ณด ์ฌํธ์ถ
queryClient.invalidateQueries({ queryKey: [FETCH_PRODUCT_KEY] })
queryClient.invalidateQueries({ queryKey: [FETCH_ITEMS_KEY] })
}
addEventListener('visibilitychange', handleVisibliityChange)
return () => {
removeEventListener('visibilitychange', handleVisibliityChange)
}
}, [id, queryClient])
- Observer ํจํด์ eventBus + postMessage๋ก ํ์ด์ง ํต์
- ์ฅ์ : ๋ฐํ/๊ตฌ๋ ๊ธฐ๋ฐ ๊ตฌ์กฐ๋ผ ํน์ ์ด๋ฒคํธ์๋ง ๋ฐ์์ด ๊ฐ๋ฅํ๋ค.
- ๋จ์ : (1๋ฒ๋๋น) ๋ณต์กํ ์ฝ๋ ๋ฐ ๋ฆฌ์์ค ์์ (๋ฐํ/๊ตฌ๋ ๋ก์ง ์ถ๊ฐ), ์ฝ๋ ํํธํ(ํ๋์ ๋น์ฆ๋์ค ๋ก์ง์ด ๊ตฌ๋ /๋ฐํ ์ฝ๋๋ก์ธํด ์ฌ๋ฌ ๊ณณ์ ํฉ์ด์ ธ์์ด ์ ์ฒด ํ๋ก์ฐ๋ฅผ ํ๋ฒ์ ์ดํดํ๋๋ฐ ์ด๋ ค์), ๊ฒฐ์ ์ ์ผ๋ก ์ด ๋ฐฉ๋ฒ์ ํ์ฌ ํด๋ผ์ด์ธํธ์์ ๋ฏธ์ง์ํ๊ธฐ๋๋ฌธ์ ์ฌ์ฉ๋ถ๊ฐ
// eventBus.ts
type EventCallback = (...args: any[]) => void;
export class EventBus {
private events: Record<string, EventCallback[]> = {};
subscribe(event: string, callback: EventCallback): () => void {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// Return unsubscribe function
return () => {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(cb => cb !== callback);
if (this.events[event].length === 0) {
delete this.events[event];
}
};
}
publish(event: string, ...args: any[]): void {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
callback(...args);
});
}
}
export const EVENTS = {
SERVICE_UPDATED: 'SERVICE_UPDATED',
// Add more event constants as needed
};
// ์์ฝ๊ฒฐ์ ํ์ด์ง (๋ฐํ)
const handleConfirm = () => {
eventBus.publish(EVENTS.BOOKING_ERROR, { reason: "์์ฝ ์์ฑ ์คํจ" });
window.postMessage(
{ type: EVENTS.BOOKING_ERROR, reason: "์์ฝ ์์ฑ ์คํจ" },
window.location.origin
);
};
// PDP (๊ตฌ๋
)
useEffect(() => {
const unsubscribe = eventBus.subscribe(EVENTS.BOOKING_ERROR, (data) => {
queryClient.invalidateQueries({ queryKey: [FETCH_PRODUCT_KEY] });
queryClient.invalidateQueries({ queryKey: [FETCH_ITEMS_KEY] });
});
const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
if (event.data?.type !== EVENTS.BOOKING_ERROR) return;
queryClient.invalidateQueries({ queryKey: [FETCH_PRODUCT_KEY] });
queryClient.invalidateQueries({ queryKey: [FETCH_ITEMS_KEY] });
};
window.addEventListener("message", handleMessage);
return () => {
unsubscribe();
window.removeEventListener("message", handleMessage);
};
}, [queryClient]);
- โ storage ์ด๋ฒคํธ
- ์ฅ์ : ๋์ผ ์ถ์ฒ(sameOrigin)๋ด์์ localStorage ๋ณ๊ฒฝ์ ์ค์๊ฐ ๊ฐ์ง ๊ฐ๋ฅ. ๊ฐ๋จํ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ์ ์ ์ ํ๋ค.
- ๋จ์ : ์ผํ์ฑ ํต์ ์ ์ ํฉํ๋ฏ๋ก, ์ด๋ฒคํธ ์ฒ๋ฆฌ ํ ๋ฐ๋์ ํด๋น key๋ฅผ ์ ๊ฑฐํด์ผ ํ๋ค.
// ์์ฝ๊ฒฐ์ ํ์ด์ง
const handleConfirm = () => {
const destination = `/products/${productId}?startDate=${startDate}&endDate=${endDate}`
if (yaNative?.isEnabled) {
localStorage.setItem(BOOKING_ERROR_STORAGE_KEY, 'true')
yaNative.dismiss()
} else {
// ๊ธฐ์กด ์ฒ๋ฆฌ
}
}
// PDP
useEffect(() => {
const handleStorageChange = (event: StorageEvent) => {
const isBookingErrorEvent = event.key === BOOKING_ERROR_STORAGE_KEY
if (!isBookingErrorEvent) {
return
}
// ์ํ์ ๋ณด ์ฌํธ์ถ
queryClient.invalidateQueries({ queryKey: [FETCH_PRODUCT_KEY] })
queryClient.invalidateQueries({ queryKey: [FETCH_ITEMS_KEY] })
localStorage.removeItem(BOOKING_ERROR_STORAGE_KEY)
}
window.addEventListener('storage', handleStorageChange)
return () => {
window.removeEventListener('storage', handleStorageChange)
}
}, [queryClient])
- BroadCastChannel API
- ์ฅ์ : ๊ตฌํ์ด ๊ฐ๋จํ๊ณ , ์ด๋ฒคํธ ์ ์ก ํ ๋ณ๋์ cleanup(storage key ์ ๊ฑฐ ๋ฑ)์ด ํ์ ์๋ค.
- ๋จ์ : iOS safari ๋ธ๋ผ์ฐ์ fallback ์ฒ๋ฆฌํ์(2022๋ ๋ถํฐ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.)
// ์์ฝ๊ฒฐ์ ํ์ด์ง
const handleConfirm = () => {
const destination = `/products/${productId}?startDate=${startDate}&endDate=${endDate}`
if (yaNative?.isEnabled) {
const channel = new BroadcastChannel('booking_channel')
channel.postMessage({ type: 'BOOKING_ERROR' })
channel.close() // cleanup
yaNative.dismiss()
} else {
// ๊ธฐ์กด ์ฒ๋ฆฌ
}
}
// PDP
useEffect(() => {
const channel = new BroadcastChannel('booking_channel')
const handleMessage = (event: MessageEvent) => {
if (event.data?.type !== 'BOOKING_ERROR') return
// ์ํ์ ๋ณด ์ฌํธ์ถ
queryClient.invalidateQueries({ queryKey: [FETCH_PRODUCT_KEY] })
queryClient.invalidateQueries({ queryKey: [FETCH_ITEMS_KEY] })
}
channel.addEventListener('message', handleMessage)
return () => {
channel.removeEventListener('message', handleMessage)
channel.close()
}
}, [queryClient])
๊ฒฐ๋ก
- ์ด๋ฒ ์คํ๋ฆฐํธ์ ํ ๋น๋ฐ์ ๋ฆฌ์์ค, ํธํ์ฑ, ๋ชฉ์ ์ฑ์ ๊ณ ๋ คํ์๋ storage ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ 3๋ฒ๋์์ด ์ ์ ํ๋ค. ๋ฌผ๋ก ,
BroadCastChannel
์ ๋ ์ง๊ด์ ์ด๊ณ ์ ์ง๋ณด์๊ฐ ์ฌ์ด ๋ฐฉ์์ด์ง๋ง, iOS Safari์ ํธํ์ฑ ์ด์๊ฐ ์์ด ๊ณง๋ฐ๋ก ๋์ ํ๊ธฐ๋ ์ด๋ ค์ ๋ค.
ํฅํ ๊ฐ์ ๋ฐฉํฅ์ ๋ค์๊ณผ ๊ฐ๋ค.
- BroadCastChannel์ ์ฐ์ ์ ์ฉํ๋ค.
- BroadCastChannel์ ์ง์ํ์ง ์๋ ๋ธ๋ผ์ฐ์ ์์๋ storage ์ด๋ฒคํธ๋ก fallback ํ๋ค.
- ์ถํ ๋ณ๋์ ์ด๋ฒคํธ ํต์ ํจํค์ง๋ฅผ ๋์ ํ๋ค๋ฉด EventBus๋ก ์ผ์ํ ํ ์ ์๋ค.
๋ฐ์ํ
'Frontend > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[React] ์ฟ ํฐ ์ ์ฒด ๋ฐ๊ธฐ ๊ธฐ๋ฅ์ ๊ฐ์ ํด๋ณด์! (0) | 2025.09.15 |
---|---|
[์ฌ๋ด๋ฐํ] ํ์ ์์ ์ฒ๋ฆฌ๋ก Braze Feature flag ๋ก๋ฉ UX ๊ฐ์ ํ๊ธฐ (0) | 2025.08.20 |
[ ๋ฆฌ์กํธ(React) ] Higher Order Component ์ฌ์ฉํ๊ธฐ (0) | 2023.04.13 |
[ ๋ฆฌ์กํธ(React) ] && ๋์ ์ผํญ์ฐ์ฐ์ ์ฌ์ฉํ๊ธฐ (0) | 2022.11.07 |
[ ๋ฆฌ์กํธ(React) ] ๋ถํ์ํ prop drilling ์ ๊ฑฐํ๊ธฐ (0) | 2022.07.27 |
๋๊ธ