비동기 로딩과 여러 채팅방
uchat.js가 불러와지기 전에 이벤트를 거는 방법, 한 페이지의 여러 채팅방에 이벤트를 나눠 거는 방법, SPA에서 채팅방을 붙이고 정리하는 방법, 오른쪽 아래에 떠 있는 위젯을 붙이는 방법을 설명해요.
마지막 수정 2026년 10월 20일
uchat.js는 async로 불러오므로 페이지 스크립트와 실행 순서가 정해져 있지 않아요. 이 문서는 로딩 순서와 관계없이 이벤트를 거는 방법, 채팅방 여러 개를 다루는 방법, SPA에서 채팅방을 붙였다 떼는 방법을 다뤄요.
미리 등록 코드
아래 코드를 uchat.js보다 먼저 넣어요. uchat.js가 준비되기 전에 건 이벤트를 U.events에 보관해 두었다가, 준비되면 그대로 써요.
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
uchat.js가 준비되면 U.chat은 진짜 함수로 바뀌어요. 그 전과 후의 차이는 하나예요.
| 준비 전(미리 등록 코드) | 준비 후 | |
|---|---|---|
on(이름, 콜백) |
보관 | 등록(이미 준비된 방의 after.create는 바로 호출) |
off(이름) |
이름이 글자 그대로 같은 것만 지움 | 이름표·이벤트 이름으로 골라 지움(아래 참고) |
준비 전에 off('#이름표')처럼 이름표만 주면 아무것도 지워지지 않아요. 언제 불릴지 모르는 정리 코드에서는 on에 쓴 이름 전체('after.message#alarm')를 그대로 off에 넘기세요. 이 방식은 준비 전과 후 모두에서 동작해요.
채팅방 고르기
U.chat('*').on('after.join', handler); // 페이지의 모든 채팅방
U.chat('room-a').on('after.join', handler); // room-a만
U.chat('room-b').on('after.join', handler); // room-b만
U.chat('채팅방아이디')는 그 채팅방에만,U.chat('*')는 페이지의 모든 채팅방(나중에 추가되는 채팅방 포함)에 걸어요.- 아이디 여러 개를 한 번에 넣을 수는 없어요. 각각 걸거나
'*'로 걸고 콜백 안에서room.id로 나누세요. - 채팅방이 이미 열려 있으면
U.chat('채팅방아이디')는 그 채팅방의room객체를 돌려줘요. 아직 없으면on·off만 있는 등록기를 돌려줘요. 어느 쪽이든on·off는 같게 써요. → U.chat()과 room 객체
이름표와 해제
이벤트 이름 뒤에 #이름표를 붙이면 나중에 이름표로 골라 해제할 수 있어요.
U.chat('*').on('after.message#alarm', onMessage);
U.chat('*').on('after.user.join#alarm', onJoin);
U.chat('*').off('#alarm'); // 이름표가 alarm인 것 모두
U.chat('*').off('after.message'); // after.message 모두(이름표 상관없이)
U.chat('*').off('after.message#alarm'); // 둘 다 맞는 것만
U.chat('*').off(...)는 '*'로 건 것뿐 아니라 채팅방별로 건 것까지 모두 지워요. 다른 스크립트(플러그인, 다른 위젯)가 건 이벤트까지 지우지 않도록, 내 코드의 이벤트에는 겹치지 않는 이름표(#my-widget처럼)를 붙이고 이름표로 해제하세요.
after.create와 준비 시점
after.create는 채팅방 화면(스킨)이 준비되면 와요. 접속보다 먼저 오므로 스킨 UI API와 채널 등록은 이 안에서 해요.
이미 준비된 채팅방에 after.create를 걸면 기다리지 않고 바로 한 번 불려요. 그래서 스크립트가 늦게 실행되어도 놓치지 않아요.
U.chat('*').on('after.create#my-widget', function (room) {
if (room.id !== 'my-room') return;
// room.skin, room.plugin 사용
});
태그 추가·삭제와 속성 변경
uchat.js는 페이지의 <u-chat> 태그를 지켜봐요.
<u-chat>을 페이지에 넣으면 채팅방이 열리고, 지우면 채팅방이 닫혀요.- 채팅방이 닫히면 그 채팅방 아이디로 건 이벤트는 함께 지워져요.
'*'로 건 이벤트는 남아요. 태그를 다시 넣었을 때도 이벤트가 필요하면 다시 걸거나'*'로 거세요. - 같은
room아이디의 태그는 페이지에 하나만 열려요. 두 번째 태그는 무시되고, 첫 태그를 지워도 두 번째 태그가 대신 열리지 않아요. room,user_data같은 속성을 바꾸는 것은 감지하지 않아요. 값을 바꾸려면 태그를 지우고 새 속성으로 다시 넣으세요.
function replaceRoom(container, roomId) {
container.replaceChildren(); // 기존 <u-chat> 제거 → 채팅방 닫힘
var tag = document.createElement('u-chat');
tag.setAttribute('room', roomId);
tag.style.cssText = 'display:block; width:100%; height:500px;';
container.appendChild(tag); // 새 채팅방 열림
}
SPA에서 쓰기
SPA에서는 화면에 채팅방이 들어올 때 uchat.js를 한 번만 불러오고, 화면을 떠날 때 내가 건 이벤트를 정리해요. <u-chat> 태그는 프레임워크가 지우면 채팅방도 함께 닫혀요.
아래 uchat() 함수는 미리 등록 코드와 같은 일을 하므로 uchat.js보다 먼저 실행되어도 돼요.
// uchat.js — 프레임워크와 관계없이 쓰는 도우미
export function uchat(roomId) {
const U = (window.U = window.U || {});
U.events = U.events || [];
if (!U.chat) {
U.chat = (n) => ({
on: (e, t) => U.events.push([n, e, t]),
off: (e) => {
for (let t = U.events.length; t > 0; )
U.events[--t][0] == n && U.events[t][1] == e && U.events.splice(t, 1);
}
});
}
if (!document.querySelector('script[data-uchat]')) {
const s = document.createElement('script');
s.src = 'https://client.uchat.io/uchat.js';
s.async = true;
s.dataset.uchat = '';
document.head.appendChild(s);
}
return U.chat(roomId);
}
React:
import { useEffect } from 'react';
import { uchat } from './uchat';
export default function ChatRoom({ roomId }) {
useEffect(() => {
const name = 'after.message#my-widget-' + roomId;
uchat('*').on(name, (room, data) => {
if (room.id !== roomId) return;
console.log(data.user.nick, data.content);
});
return () => uchat('*').off(name);
}, [roomId]);
// key를 바꾸면 태그를 새로 만들므로 다른 채팅방으로 바뀐다
return <u-chat key={roomId} room={roomId} style={{ display: 'block', width: '100%', height: '500px' }} />;
}
- 이벤트는
'*'로 걸고room.id로 나눠요. 채팅방 아이디로 건 이벤트는 태그가 지워질 때 함께 지워지므로, 다시 마운트할 때 순서가 꼬이지 않아요. - 정리할 때는
on에 쓴 이름 전체를off에 넘겨요.uchat.js가 아직 준비되지 않았어도 지워져요. room속성만 바꾸면 채팅방이 바뀌지 않으므로key로 태그를 새로 만들어요.
Vue는 onMounted에서 on, onUnmounted에서 off를 부르고, u-chat을 사용자 정의 요소로 알려 줘요(5분 설치 참고). Svelte는 onMount에서 on을 걸고 돌려주는 정리 함수에서 off를 불러요. 채팅방을 바꿀 때는 Vue는 :key, Svelte는 {#key roomId} 블록으로 태그를 새로 만들어요.
떠 있는 위젯
채팅방을 페이지 오른쪽 아래 단추로 붙이려면 npm 패키지 @u-chat/embed의 위젯을 써요. 닫혀 있을 때는 새 메시지를 미리보기와 안 읽은 수로 알려 주고, 누르면 채팅창이 열려요. 관리 페이지 설치 코드 화면의 떠 있는 위젯 탭에서도 이 코드를 만들 수 있고, 아래 속성과 위치·크기를 그 화면의 위젯 설정에서 골라요.
번들러 없이 쓰는 사이트(HTML, 그누보드, 워드프레스 등)는 <script> 한 줄로 불러와요. uchat.js를 이미 붙인 페이지에 그대로 더해도 돼요.
<script src="https://cdn.jsdelivr.net/npm/@u-chat/embed@latest/dist/widget.global.js"></script>
<u-chat-widget launcher-text="현재 {user_count}명">
<u-chat room="my-room"></u-chat>
</u-chat-widget>
채팅 서버에서 불러오려면 jsDelivr 주소 대신 https://client.uchat.io/widget.js를 써요. 독립 서버는 그 서버의 widget.js를 쓰고, 위젯이 같은 서버의 uchat.js에 붙어서 src 속성이 필요 없어요.
React(npm install @u-chat/react):
import { UchatWidget } from '@u-chat/react';
export default function App() {
return <UchatWidget room="my-room" launcherText="현재 {user_count}명" />;
}
Svelte 5(npm install @u-chat/svelte):
<script>
import { UchatWidget } from '@u-chat/svelte';
</script>
<!-- 중괄호가 Svelte 식으로 읽히지 않게 문자열을 {'...'}로 넘겨요 -->
<UchatWidget room="my-room" launcherText={'현재 {user_count}명'} />
Vue 3(npm install @u-chat/vue). isCustomElement 설정 없이 써요.
<script setup>
import { UchatWidget } from '@u-chat/vue';
</script>
<template>
<UchatWidget room="my-room" launcher-text="현재 {user_count}명" />
</template>
위젯 속성(React·Svelte에서는 launcherText처럼 낙타 표기):
| 속성 | 설명 |
|---|---|
launcher-text |
단추 글자. {user_count}는 접속자 수, {unread_count}는 안 읽은 수로 바뀌어요 |
variant |
bubble(기본, 둥근 단추), bar(아래쪽 긴 막대, 열면 아래에서 올라와요) 또는 side(오른쪽 벽면 사이드바. 처음부터 화면 높이 전체로 열려 페이지 본문을 왼쪽으로 밀고, 접으면 오른쪽 벽에 세로 탭만 남아요. 768px보다 좁은 화면은 접힌 채 시작하고 열면 덮어요) |
launcher-color, launcher-image |
단추 배경색, 배경 그림 주소(그림이 우선) |
panel-title |
열린 창 위의 제목 |
preview-duration |
새 메시지 미리보기를 보여 주는 시간(ms, 기본 5000) |
lang |
위젯 단추·안내 문구 언어(ko, en, ja). 없으면 안쪽 <u-chat lang> → 페이지 <html lang> → 브라우저 언어, 그 밖의 언어는 영어 |
src |
uchat.js 주소. 독립 서버는 그 서버의 주소를 넣어요 |
- 회원 연동 방은 안쪽
<u-chat>에user_data를 넣어요(React·Svelte·Vue는userData). 설치 코드 화면에서 넣는 모양 → 떠 있는 위젯을 고르면 서명까지 들어간 위젯 코드가 나와요. - 페이지에 들어오면 바로 채팅방에 접속해요. 그래서 닫혀 있어도 미리보기와 접속자 수가 보여요. 지난 대화(
after.message의past)와 내가 보낸 메시지는 안 읽은 수에 넣지 않아요. - 창의
×로 숨기면 접속을 끊고, 이 채팅방에 한해 숨긴 상태를 기억해요. 오른쪽 아래에 작은 켜기 단추가 남아요. - 스크립트에서는
open(),close(),toggle(),maximize(),restore(),hide(),show(),destroy()를 불러요. React·Vue·Svelte는 컴포넌트에ref(Svelte는bind:this)를 걸고 같은 이름으로 불러요. uchat.js를 불러오지 못하면 위젯에uchat-widget-error이벤트가 와요(event.detail이 오류).
태그 대신 스크립트로 만들 때는 createUchatWidget(넣을 요소, 설정)을 써요. 한 줄 스크립트로 불러왔으면 window.UchatEmbed에, npm이면 @u-chat/embed/widget에 있어요.
const widget = UchatEmbed.createUchatWidget(document.body, {
chat: { room: 'my-room', user_data: signedUserData },
widget: { launcherText: '현재 {user_count}명', variant: 'bar' },
});
widget.addEventListener('uchat-widget-error', (event) => console.error(event.detail));
widget.open();
자리와 크기는 CSS 변수로 바꿔요.
u-chat-widget {
--uchat-widget-right: 20px;
--uchat-widget-bottom: 20px;
--uchat-widget-panel-width: 380px;
--uchat-widget-panel-height: 640px;
--uchat-widget-side-width: 360px;
--uchat-widget-accent: #ff7e25;
--uchat-widget-z-index: 2147483000;
}
봐도 잘 이해가 되지 않나요? 문의 게시판에 질문을 남겨 주세요.