在 React 和 Vue 中使用
Web SDK 的核心包不依賴任何前端框架:會變化的資料都以“快照 + 訂閱”的形式提供,可以接入任何框架。React 和 Vue 另有官方的繫結包,負責在元件的生命週期中建立、訂閱和釋放列表,處理 React StrictMode 的重複掛載、熱更新和服務端渲染:
| 包 | 內容 | 支援的版本 |
|---|---|---|
@deeprespond/im-web-react | DRProvider 和 hooks | React 18、19 |
@deeprespond/im-web-vue | 外掛和組合式函式 | Vue 3.3 以上 |
繫結包不含介面元件,只負責資料。它們與核心包一起釋出、版本號相同,請安裝相同的版本。Svelte、Angular 等其他框架直接使用核心包,見不用繫結包。
倉庫提供基於這兩個繫結包的可執行 Demo(同一張功能表的 React / Vue 兩套實現),見React / Vue 工程版 Demo,也可以下載原始碼包直接執行。
需要現成的聊天介面時,使用 React UIKit 或 Vue UIKit:它們基於核心 SDK 和繫結包構建,提供會話、訊息、好友和群組元件,已釋出到公共 npm(版本 1.1.1),也提供原始碼和本地 tgz 包。Vue UIKit 執行時不依賴 React。
| 要做什麼 | 看哪裡 |
|---|---|
| 決定在哪裡建立客戶端(單頁應用、Next.js / Nuxt) | 客戶端放在哪裡 |
| React 中顯示登入狀態、會話列表、訊息列表 | React |
| Vue 中使用 | Vue |
| 服務端渲染時 hooks 返回什麼 | 客戶端還沒有時的值 |
| Svelte、Angular 等其他框架 | 不用繫結包 |
| 避開常見的坑(StrictMode、長列表、隱藏的面板) | 注意事項 |
客戶端放在哪裡
單頁應用
在一個模組中建立一次客戶端,整個應用共用,不要在元件中建立:
// src/im.ts
import { createDRClient } from '@deeprespond/im-web';
export const im = createDRClient({ appKey: '1575529652#demo', apiUrl: 'https://im.example.com' });
// 热更新会重新执行这个模块:先释放旧的客户端,新的才能创建(webpack 为 import.meta.webpackHot)
if (import.meta.hot) import.meta.hot.dispose(() => void im.destroy());然後把它交給繫結包:React 用 <DRProvider client={im}>,Vue 用 app.use(createDRPlugin({ client: im }))。
服務端渲染
Next.js、Nuxt 等框架會在服務端執行頁面程式碼,而客戶端只能在瀏覽器中建立(在服務端呼叫 createDRClient 丟擲 unsupported)。這時不要在模組頂層建立,改為把選項交給繫結包,由它在瀏覽器中建立、解除安裝時釋放:React 用 <DRProvider options={...}>,Vue 用 createDRPlugin({ options: ... })。服務端渲染時 hooks 和組合式函式返回空的值(見客戶端還沒有時的值),瀏覽器中建立客戶端後自動更新。
Next.js 的 App Router 中,DRProvider 要放在你自己的 'use client' 元件中:選項中的函式(ticketProvider、logger.sink)不能從服務端元件傳給客戶端元件。
在元件中建立
要在元件中建立的(例如登入之後才知道 AppKey),在 effect 中建立、在清理函式中 destroy(),或者用 DRProvider 的 options。React StrictMode 在開發模式下會建立、釋放、再建立一次,這是允許的:destroy() 之後可以立即再建立。不要在渲染函式或 useState、useMemo 的初始化函式中建立:StrictMode 會呼叫它們兩次,第二次建立時第一個還沒有釋放,會丟擲 invalid_state(client_exists)。
React
npm install @deeprespond/im-web @deeprespond/im-web-react提供客戶端
import { DRProvider } from '@deeprespond/im-web-react';
// im 为上文 src/im.ts 中创建的客户端(import { im } from './im')
// 在入口中渲染它:createRoot(document.getElementById('root')!).render(<Root />)
export function Root() {
return (
<DRProvider client={im}>
<App />
</DRProvider>
);
}服務端渲染的應用改用 options:
'use client';
import type { ReactNode } from 'react';
import { DRProvider } from '@deeprespond/im-web-react';
const options = { appKey: '1575529652#demo', apiUrl: 'https://im.example.com' };
export function IMProvider({ children }: { children: ReactNode }) {
return <DRProvider options={options}>{children}</DRProvider>;
}options 只在掛載時讀取一次,之後修改不起作用;要換成另一個應用時,改變 DRProvider 的 key 讓它重新掛載。
登入狀態
useAuthState() 返回登入狀態,據此決定顯示登入頁還是主介面。客戶端還沒有建立時(服務端渲染、第一次渲染)為 null,這時還不知道是否已登入,應顯示載入中:
import { useAuthState, useConnectionState } from '@deeprespond/im-web-react';
export function Root() {
const auth = useAuthState();
const connection = useConnectionState();
if (auth === null) return <p>加载中…</p>;
if (auth.kind === 'signed_out' || auth.kind === 'ended') return <LoginPage />;
return (
<>
{connection.kind !== 'ready' && <p className="banner">{connection.kind === 'offline' ? '网络不可用' : '连接中…'}</p>}
<MainPage />
</>
);
}登入頁中用 useDRClient() 取得客戶端呼叫登入:
import { useDRClient } from '@deeprespond/im-web-react';
export function LoginPage() {
const im = useDRClient();
const login = async () => {
const res = await fetch('/api/im-ticket', { method: 'POST', credentials: 'include' });
const { ticket } = (await res.json()) as { ticket: string };
await im?.auth.loginWithTicket({ ticket });
};
return <button disabled={!im} onClick={() => void login()}>登录</button>;
}會話列表
useLiveList(create, deps) 在 effect 中呼叫 create(im) 取得列表,解除安裝時釋放。登入前不呼叫 create,退出、切換使用者後自動按新的登入重新建立:
import { memo } from 'react';
import type { ConversationView } from '@deeprespond/im-web';
import { useLiveList, useConversationTitle, useUnreadTotal } from '@deeprespond/im-web-react';
export function ConversationList({ onOpen }: { onOpen: (key: string) => void }) {
const { snapshot, list } = useLiveList<ConversationView>((im) => im.conversations.list(), []);
const unread = useUnreadTotal();
return (
<div>
<h3>消息{unread > 0 && `(${unread})`}</h3>
{snapshot.loading && snapshot.items.length === 0 && <p>加载中…</p>}
{snapshot.items.map((c) => (
<ConversationRow key={c.conversation_key} view={c} onOpen={onOpen} />
))}
{snapshot.hasMore && <button onClick={() => void list?.loadMore()}>加载更多</button>}
</div>
);
}
// 没有变化的会话仍是原来的对象,memo 的行组件不会重新渲染
const ConversationRow = memo(function ConversationRow({ view, onOpen }: { view: ConversationView; onOpen: (key: string) => void }) {
const title = useConversationTitle(view);
return (
<div onClick={() => onOpen(view.conversation_key)}>
{title} {view.unread_count > 0 && <b>{view.unread_count}</b>}
</div>
);
});請為 useLiveList 寫明條目的型別(如 useLiveList<ConversationView>),否則快照中條目的型別推斷不出來。其他列表同樣適用:好友為 useLiveList<Friend>((im) => im.friends.list(), []),群成員為 useLiveList<GroupMember>((im) => im.groups.members(groupId), [groupId]),deps 變化時釋放舊的列表、建立新的。
會話標題不在會話物件中:它來自好友備註、使用者資料、群名,這些變化時會話物件不變。所以 memo 的行元件要用 useConversationTitle、useDisplayName 顯示名稱,它們會在名稱變化時重新渲染。
訊息列表與傳送
useMessageList(conversation_key, opts?) 開啟一個會話的訊息列表,conversation_key 變化時釋放舊的、開啟新的,解除安裝時釋放:
import { useState } from 'react';
import { DRError } from '@deeprespond/im-web';
import { describeError } from '@deeprespond/im-web/locale/zh-CN';
import { useDRClient, useDREvent, useDisplayName, useMessageList, useTyping } from '@deeprespond/im-web-react';
export function Chat({ conversationKey }: { conversationKey: string }) {
const im = useDRClient();
const { snapshot, list } = useMessageList(conversationKey);
const typing = useTyping(conversationKey);
const [text, setText] = useState('');
useDREvent('message.sendFailed', (p) => {
if (p.conversation_key === conversationKey) alert(describeError(p.error));
});
const send = async () => {
if (!im || !text) return;
setText('');
try {
await im.messages.send({ conversation_key: conversationKey }, { type: 'text', body: { text } });
} catch (err) {
if (err instanceof DRError) alert(describeError(err));
}
};
return (
<div>
{snapshot.hasOlder && <button onClick={() => void list?.loadOlder()}>查看更早的消息</button>}
{snapshot.items.map((m) => (
<MessageRow key={m.client_msg_id ?? String(m.seq)} sender={m.sender} text={m.type === 'text' ? String(m.body?.text ?? '') : `[${m.type}]`} status={m._local.status} />
))}
{typing.length > 0 && <p>对方正在输入…</p>}
<input value={text} onChange={(e) => { setText(e.target.value); im?.conversations.typing(conversationKey); }} />
<button onClick={() => void send()}>发送</button>
</div>
);
}
function MessageRow({ sender, text, status }: { sender: string | null; text: string; status: string }) {
const name = useDisplayName(sender);
return <p>{name}:{text}{status !== 'sent' && `(${status})`}</p>;
}- 草稿會話(還沒有發過訊息的單聊)發出第一條訊息後變為正式會話,列表會就地切換,
conversation_key隨後改為新的會話 ID 不會引起重新開啟。 - 只改變
opts.around_seq時,列表跳轉到這條訊息附近(list.jumpTo()),不重新開啟。 - 列表在介面上被隱藏但沒有解除安裝時(如標籤式介面中切走的面板),呼叫
list.setViewing({ visible: false }),否則開啟了autoMarkRead時會把使用者沒看到的訊息標為已讀。
事件、線上狀態與聊天室
import { useDREvent, usePresence, useChatroom, useHandle, useDRClient, useDRState } from '@deeprespond/im-web-react';
export function Notifier() {
// 总是调用最新一次渲染的函数,不会因为它变化而重新订阅
useDREvent('message.received', ({ message, alert }) => {
if (alert && document.hidden) new Notification('新消息', { body: message.sender ?? '' });
});
return null;
}
export function OnlineDots({ usernames }: { usernames: string[] }) {
const presence = usePresence(usernames); // 名单按内容比较,每次渲染传入新数组不会重新订阅
return <>{usernames.map((u) => <span key={u}>{u}{presence.get(u)?.online ? '(在线)' : ''}</span>)}</>;
}
export function LiveRoom({ roomId }: { roomId: string }) {
const { snapshot, error } = useChatroom(roomId); // 挂载时进入,卸载或 roomId 变化时离开
if (error) return <p>进入失败</p>;
if (!snapshot) return <p>正在进入…</p>;
return <p>{snapshot.info.name}:{snapshot.messages.length} 条消息</p>;
}
export function CurrentCall() {
const current = useDRState((im) => im.calls.current, ['call.incoming', 'call.changed'], null);
const snapshot = useHandle(current); // 通话的快照;没有通话时为 null
return snapshot ? <p>通话状态:{snapshot.call.status}</p> : null;
}useDRState(read, events, fallback?) 用於讀取任何同步的值:events 中的事件發出時重新呼叫 read,結果沒有變化(Object.is)時不重新渲染。各個值對應的事件見用事件讀取同步狀態的值。
React 繫結的全部匯出
| 匯出 | 說明 |
|---|---|
DRProvider | 提供客戶端。屬性 client(已建立的客戶端)與 options(DRClientOptions,由它在瀏覽器中建立,解除安裝時 destroy())二選一 |
useDRClient() | 當前的客戶端 DRClient | null;用 options 時,建立之前為 null |
useLiveList(create, deps) | 返回 { snapshot, list }。在 effect 中呼叫 create(im) 取得列表,deps 變化、登入會話變化或解除安裝時 dispose();未登入時不呼叫 create |
useMessageList(conversation_key, opts?) | 返回 { snapshot, list },開啟 im.messages.open(conversation_key, opts);conversation_key 為 null 或 undefined 時不開啟 |
useHandle(handle) | 聊天室、通話物件的快照;handle 為 null 時返回 null |
useChatroom(room_id, opts?) | 掛載時進入聊天室,解除安裝或 room_id 變化時離開(StrictMode 的重複掛載不會離開);返回 { handle, snapshot, error } |
useDRState(read, events, fallback?) | 讀取同步的值,events 中的事件發出時重新讀取;另外總是在 auth.stateChanged、auth.userSwitched、storage.reset 時重新讀取。客戶端還沒有時返回 fallback |
useDREvent(event, listener) | 訂閱事件,解除安裝時取消 |
usePresence(usernames) | 訂閱這些人的線上狀態,解除安裝或名單變化時取消;返回 Map<string, PresenceState | undefined>。應用沒有開啟線上狀態時不訂閱,值全為 undefined |
useAuthState() | 登入狀態 AuthState | null |
useConnectionState() | 連線狀態 ConnectionState |
useConfig() | 執行配置 ClientConfig | null |
useMe() | 本人資料 SelfProfile | null |
useUnreadTotal(opts?) | 未讀總數 number,opts.excludeMuted 為 true 時不計免打擾的會話 |
useUser(username) | 其他使用者的資料 UserProfile | undefined,本地沒有時在後臺獲取 |
useFriendRequestUnread() | 未讀的好友申請數 number |
useTyping(conversation_key) | 會話中正在輸入的人 readonly string[] |
useDisplayName(username, opts?) | 使用者的顯示名 string | undefined(好友備註、群暱稱、暱稱、使用者名稱中第一個非空的;opts.group_id 給出時考慮群暱稱),名稱相關的資料變化時更新 |
useConversationTitle(view) | 會話的標題 string | undefined,名稱相關的資料變化時更新 |
useChannel(handle?) | 獨立頻道的快照,見頻道狀態 |
useGameRoom(room?)、useGameEvent(room, event, listener) | 遊戲房間的快照和事件,見遊戲房間狀態 |
hooks 內部用 useSyncExternalStore,併發渲染時同一次渲染中讀到的資料前後一致。
Vue
npm install @deeprespond/im-web @deeprespond/im-web-vue安裝外掛
// main.ts。App 为根组件(import App from './App.vue'),im 为上文 src/im.ts 中创建的客户端
import { createApp } from 'vue';
import { createDRPlugin } from '@deeprespond/im-web-vue';
createApp(App).use(createDRPlugin({ client: im })).mount('#app');Nuxt 等服務端渲染的應用改用 options,由外掛在瀏覽器中建立,應用解除安裝時 destroy():
import { createDRPlugin } from '@deeprespond/im-web-vue';
app.use(createDRPlugin({ options: { appKey: '1575529652#demo', apiUrl: 'https://im.example.com' } }));在元件中使用
組合式函式與 React 的 hooks 一一對應、名字相同。返回的快照和狀態是隻讀的 ShallowRef,在模板中直接使用:
<script setup lang="ts">
import { ref } from 'vue';
import type { ConversationView } from '@deeprespond/im-web';
import { useAuthState, useConnectionState, useDRClient, useDREvent, useLiveList, useMessageList, useUnreadTotal } from '@deeprespond/im-web-vue';
import { describeError } from '@deeprespond/im-web/locale/zh-CN';
const im = useDRClient();
const auth = useAuthState();
const connection = useConnectionState();
const unread = useUnreadTotal();
const { snapshot: conversations, list } = useLiveList<ConversationView>((c) => c.conversations.list());
const current = ref<string | null>(null);
const { snapshot: messages } = useMessageList(current); // current 变化时释放旧的列表、打开新的
const text = ref('');
async function send() {
if (!im.value || !current.value || !text.value) return;
const body = { text: text.value };
text.value = '';
await im.value.messages.send({ conversation_key: current.value }, { type: 'text', body });
}
useDREvent('message.sendFailed', (p) => alert(describeError(p.error)));
</script>
<template>
<p v-if="auth === null">加载中…</p>
<p v-else-if="auth.kind !== 'signed_in'">请登录</p>
<div v-else>
<p v-if="connection.kind !== 'ready'">连接中…</p>
<h3>消息({{ unread }})</h3>
<div v-for="c in conversations.items" :key="c.conversation_key" @click="current = c.conversation_key">
{{ c.peer ?? c.group_id }} {{ c.unread_count || '' }}
</div>
<button v-if="conversations.hasMore" @click="list?.loadMore()">加载更多</button>
<div v-if="current">
<p v-for="m in messages.items" :key="m.client_msg_id ?? m.seq ?? ''">{{ m.sender }}:{{ m.body?.text }}</p>
<input v-model="text" @keyup.enter="send"> <button @click="send">发送</button>
</div>
</div>
</template>- 引數可以是值、
ref或 getter(如useMessageList(() => props.conversationKey)),變化時釋放舊的、重新建立。 useLiveList(create)沒有deps引數:create在追蹤依賴的作用域中執行,其中讀取的響應式值(如props.groupId)變化時自動重新建立,如useLiveList<GroupMember>((c) => c.groups.members(props.groupId))。同樣請寫明條目的型別。- 列表在
onMounted中建立、在作用域銷燬時釋放,所以在服務端渲染中不會建立;list在掛載之後才有值。 - 被
<KeepAlive>快取的元件不會銷燬,列表仍然開啟:useMessageList在停用時自動報告列表不在顯示,重新啟用時恢復,這個會話不會因為頁面被快取而一直算作“正在被看”。 - 會話標題、顯示名用
useConversationTitle(view)、useDisplayName(username),返回computed,名稱相關的資料變化時自動更新。
Vue 繫結的全部匯出
| 匯出 | 說明 |
|---|---|
createDRPlugin({ client?, options? }) | 外掛。client(已建立的客戶端)與 options(在瀏覽器中建立,應用解除安裝時 destroy())二選一 |
useDRClient() | 當前的客戶端,ShallowRef<DRClient | null> |
useLiveList(create) | 返回 { snapshot, list },都是隻讀的 ShallowRef;create 中讀取的響應式值變化、登入會話變化或作用域銷燬時釋放 |
useMessageList(conversation_key, opts?) | 返回 { snapshot, list };引數可以是值、ref 或 getter |
useHandle(handle) | 聊天室、通話物件的快照;handle 可以是值、ref 或 getter |
useChatroom(room_id, opts?) | 掛載時進入聊天室,作用域銷燬或 room_id 變化時離開;返回 { handle, snapshot, error } |
useDRState(read, events, fallback?) | 讀取同步的值,返回只讀的 ShallowRef,規則同 React |
useDREvent(event, listener) | 訂閱事件,作用域銷燬時取消 |
usePresence(usernames) | 訂閱線上狀態,返回 ShallowRef<Map<string, PresenceState | undefined>>;usernames 可以是值、ref 或 getter |
useAuthState()、useConnectionState()、useConfig()、useMe()、useUnreadTotal(opts?)、useUser(username)、useFriendRequestUnread()、useTyping(conversation_key) | 同 React,返回只讀的 ShallowRef |
useDisplayName(username, opts?)、useConversationTitle(view) | 同 React,返回 computed;引數可以是值、ref 或 getter |
useChannel(handle?) | 獨立頻道的快照,返回只讀的 Ref,見頻道狀態 |
useGameRoom(room?)、useGameEvent(room, event, listener) | 遊戲房間的快照(只讀的 ShallowRef)和事件;room 可以是值、ref 或 getter,見遊戲房間狀態 |
組合式函式也可以在元件之外的 effectScope 中使用,這時立即開始,在作用域停止時釋放。
客戶端還沒有時的值
服務端渲染時、用 options 建立客戶端之前,hooks 和組合式函式返回以下的值,客戶端建立後自動更新:
| 函式 | 值 |
|---|---|
useLiveList | 快照為 { items: [], hasMore: false, loading: true },list 為 null |
useMessageList | 快照為 { items: [], gaps: [], hasOlder: false, hasNewer: false, loading: 'initial', pinned: [] },list 為 null |
useHandle、useChatroom | 快照為 null |
useAuthState() | null:還不知道是否已登入,應顯示載入中,而不是登入頁 |
useConnectionState() | { kind: 'idle' } |
useConfig()、useMe() | null |
useUnreadTotal()、useFriendRequestUnread() | 0 |
useTyping() | 空陣列 |
useUser()、useDisplayName()、useConversationTitle() | undefined |
usePresence() | 值全為 undefined |
有客戶端但未登入時,狀態類的函式讀取客戶端的實際值(useAuthState() 為 { kind: 'signed_out' },據此顯示登入頁);列表類的函式不建立列表,快照為不在載入中的空快照:useLiveList 為 { items: [], hasMore: false, loading: false },useMessageList 的 loading 為 'none'。這些空快照每次都是同一個物件。
不用繫結包
可訂閱的物件(LiveList、MessageList、聊天室和通話物件)都提供 getSnapshot() 和 subscribe(listener),規則見可訂閱的列表,可以接入任何框架。要點是:在副作用中建立列表、在清理時 dispose();按登入會話重新建立(退出、切換使用者後列表已被釋放)。
React:
import { useEffect, useState, useSyncExternalStore } from 'react';
import type { ConversationView, LiveList, LiveListSnapshot } from '@deeprespond/im-web';
const EMPTY: LiveListSnapshot<ConversationView> = Object.freeze({ items: Object.freeze([]), hasMore: false, loading: true });
const noSubscribe = () => () => {};
// 订阅函数定义在组件之外,否则每次渲染都会重新订阅
const subscribeAuth = (cb: () => void) => im.on('auth.stateChanged', cb);
export function ConversationList() {
// 同步读取的值:订阅对应的事件、读取属性;服务端渲染时为 null
const sessionId = useSyncExternalStore(subscribeAuth, () => im.auth.currentUser?.session_id ?? null, () => null);
const [list, setList] = useState<LiveList<ConversationView> | null>(null);
useEffect(() => {
if (!sessionId) return; // 未登录时 list() 抛出 not_signed_in
const l = im.conversations.list(); // 在 effect 中创建,不在渲染中创建
setList(l);
return () => {
l.dispose();
setList(null);
};
}, [sessionId]);
const snapshot = useSyncExternalStore(list?.subscribe ?? noSubscribe, list?.getSnapshot ?? (() => EMPTY), () => EMPTY);
return <ul>{snapshot.items.map((c) => <li key={c.conversation_key}>{c.unread_count}</li>)}</ul>;
}Vue(單頁應用;服務端渲染時改在 onMounted 中建立):
import { onScopeDispose, shallowRef } from 'vue';
const list = im.conversations.list();
const snapshot = shallowRef(list.getSnapshot());
const off = list.subscribe(() => {
snapshot.value = list.getSnapshot();
});
onScopeDispose(() => {
off();
list.dispose();
});Svelte:
import { readable } from 'svelte/store';
const list = im.conversations.list();
export const conversations = readable(list.getSnapshot(), (set) => {
set(list.getSnapshot());
return list.subscribe(() => set(list.getSnapshot()));
});
// 组件销毁时另外调用 list.dispose()Angular:用 signal() 儲存快照,在 subscribe 的回撥中更新它;在 DestroyRef.onDestroy 中取消訂閱並呼叫 dispose()。
注意事項
- 不在渲染中建立列表:
im.conversations.list()、im.messages.open()每次呼叫都建立新的列表,要在副作用中建立(或用繫結包),清理時dispose()。沒有釋放的訊息列表會讓 SDK 繼續為它補齊訊息,還會讓這個會話一直算作“正在被看”。 - StrictMode:開發模式下元件會掛載、解除安裝、再掛載一次。繫結包和“副作用中建立、清理中釋放”的寫法都能正確處理;
useChatroom不會因此離開聊天室。 - Vue 的響應式:SDK 的物件(客戶端、列表、聊天室和通話物件)不可擴充套件,快照和資料物件已凍結,放進
ref、reactive不會被代理,也不會出錯;但它們也不會因此變成響應式的,資料仍要經subscribe或組合式函式取得。 - 長列表:會話列表最多有
conversationWindow(預設 1000)項,訊息列表隨翻頁增長,請使用虛擬滾動。沒有變化的條目仍是原來的物件,行元件可以按引用快取(React.memo、Vue 的v-memo),但行中的名稱要用useDisplayName、useConversationTitle顯示,否則名稱變化時快取的行不會更新。 - 隱藏的列表:介面上看不到、但沒有釋放的訊息列表(標籤式介面中切走的面板),呼叫
list.setViewing({ visible: false }),顯示時再設為true。Vue 的<KeepAlive>由useMessageList自動處理;React 19.2 起的<Activity>隱藏時會執行 effect 的清理,列表已經釋放,不需要另外處理。 - 多個標籤頁:每個標籤頁有自己的客戶端和列表,資料由 SDK 在標籤頁之間同步,繫結包不需要另外處理。
頻道狀態
useChannel() 訂閱獨立頻道的狀態:
| 繫結 | 簽名 | 返回 |
|---|---|---|
| React | useChannel(handle?: ChannelHandle | null) | ChannelSnapshot | null |
| Vue | useChannel(handle?),引數可以是值、ref 或 getter | 只讀的 Ref |
省略 handle 時訂閱當前客戶端的頻道。
import { useChannel } from '@deeprespond/im-web-react';
function ChannelStatus() {
const snapshot = useChannel();
return <span>{snapshot?.session.status ?? '未加入频道'}</span>;
}import { useChannel } from '@deeprespond/im-web-vue';
const snapshot = useChannel();
// 模板读取 snapshot;脚本读取 snapshot.value。- 與其他 hooks 一樣,元件要放在
DRProvider中,或放在安裝了createDRPlugin的 Vue 應用中。 useChannel只讀取狀態,不會加入頻道,也不會開啟攝像頭和麥克風。請在使用者操作中呼叫join();頁面解除安裝前呼叫leave(),並清理播放音影片的元素,見獨立頻道。
遊戲房間狀態
useGameRoom() 訂閱遊戲房間的狀態,useGameEvent() 訂閱房間的事件:
| 繫結 | 簽名 | 返回 |
|---|---|---|
| React | useGameRoom(room?: GameRoom | null) | GameRoomSnapshot | null |
| React | useGameEvent(room, event, listener) | 無。房間變化或解除安裝時取消訂閱,總是呼叫最新一次渲染的 listener |
| Vue | useGameRoom(room?),引數可以是值、ref 或 getter | 只讀的 ShallowRef |
| Vue | useGameEvent(room, event, listener) | 無。房間變化或作用域銷燬時取消訂閱 |
省略 room(或 useGameEvent 的 room 傳 undefined)時跟隨 im.game.current:加入房間、房間結束時自動切換;傳 null 時返回 null、不訂閱。event 為 frame、custom、member、roomState、snapshot、state、close。
import { useGameEvent, useGameRoom } from '@deeprespond/im-web-react';
function MatchStatus() {
const room = useGameRoom();
useGameEvent(undefined, 'member', (e) => console.log(e.username, e.type));
if (!room) return <span>未加入对局</span>;
return <span>座位 {room.seat} · {room.state} · 延迟 {room.rtt ?? '-'} ms</span>;
}import { useGameEvent, useGameRoom } from '@deeprespond/im-web-vue';
const room = useGameRoom();
useGameEvent(undefined, 'close', (e) => console.log('对局结束', e.reason));
// 模板读取 room;脚本读取 room.value。- 快照包含連線狀態、房間狀態、座位、成員和延遲,變化時元件重新渲染;幀號不在快照裡(每幀都變),每幀的資料用
useGameEvent(room, 'frame', …)接收,避免每幀重新渲染。 useGameRoom只讀取狀態,不會加入房間。請在使用者操作中呼叫im.game.join(),離開對局時呼叫room.leave(),見遊戲房間 · Web 客戶端接入。
