비동기 로딩과 여러 채팅방

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;
}

봐도 잘 이해가 되지 않나요? 문의 게시판에 질문을 남겨 주세요.

상호 UCHAT · 대표 김경욱 · 사업자등록번호 141-05-32832
사업장소재지 경기도 파주시 와석순환로 61 · 통신판매업신고번호 2013-경기파주-4485호
대표번호 010-8677-8633 · 이메일 support@uchat.io

ⓒ 2013–2026 UCHAT. All rights reserved.