tencent cloud

即时通信 IM

React

下载
聚焦模式
字号
最后更新时间: 2026-05-27 14:17:08
TUIKit 是基于 Chat SDK 的一款 UI 组件库,可通过 UI 组件快速实现聊天、会话、搜索、关系链、群组等功能。本文介绍如何快速集成 TUIKit 并实现核心功能。
您也可以使用 AI 集成,通过自然语言描述需求即可自动生成 TUIKit 集成代码。

前提条件

Node.js v18 版本及以上,建议使用当前的 LTS v22 版本
React^18.2 || React^19.0.0 版本
TypeScript@^5.0.0

创建项目

创建一个新的名为 chat-integration-react 的 React 项目,并按照脚手架的步骤提示完成项目的初始化。
npm create vite@latest
可以参考以下选择:
◇ Project name:
│ chat-integration-react
◇ Select a framework:
│ React
◇ Select a variant:
│ TypeScript + React Compiler
◆ Install with npm and start now?
│ ● Yes / ○ No
说明:
建议使用 npm 进行安装,npm 会主动下载 peerDependencies 依赖。

安装并引入组件

步骤1:安装依赖

通过 npm 方式下载 chat-uikit-react 并在项目中使用。
npm i @tencentcloud/chat-uikit-react

步骤2:引入组件

注意:
以下代码中未填入 SDKAppIDuserIDuserSig,需在 步骤3 中获取相关信息后进行替换。
复制以下 App.tsx 代码并替换原有的 App.tsx 中的内容。
App.tsx
import { useEffect, useLayoutEffect, useState, useMemo } from "react";
import {
UIKitProvider,
useLoginState,
LoginStatus,
ConversationList,
Chat,
ChatHeader,
MessageList,
MessageInput,
ContactList,
ContactInfo,
ChatSetting,
Search,
VariantType,
Avatar,
useUIKit,
useConversationListState,
} from "@tencentcloud/chat-uikit-react";
import { IconChat, IconUsergroup, IconBulletpoint, IconSearch } from "@tencentcloud/uikit-base-component-react";

function App() {
// 语言支持 en-US(default) / zh-CN / ja-JP / ko-KR / zh-TW
// 主题支持 light(default) / dark
return (
<UIKitProvider theme={'dark'} language={'en-US'}>
<ChatApp />
</UIKitProvider>
);
}

function ChatApp() {
const [activeTab, setActiveTab] = useState<'conversations' | 'contacts'>('conversations');
const [isChatSettingShow, setIsChatSettingShow] = useState(false);
const [isSearchInChatShow, setIsSearchInChatShow] = useState(false);

const { language, theme } = useUIKit();

const isDark = theme === 'dark';

const texts = useMemo(() =>
language === 'zh-CN'
? { emptyTitle: '暂无会话', emptySub: '选择一个会话开始聊天', error: '请检查 SDKAppID, userID, userSig, 通过开发人员工具(F12)查看具体的错误信息', loading: '登录中...' }
: { emptyTitle: 'No conversation', emptySub: 'Select a conversation to start chatting', error: 'Please check the SDKAppID, userID, and userSig. View the specific error information through the developer tools (F12).', loading: 'Logging in...' },
[language]
);

const { status } = useLoginState({
SDKAppID: 0, // number 类型
userID: '', // string 类型
userSig: '', // string 类型
});

const { setActiveConversation, activeConversation } = useConversationListState();

// 初始化默认会话 可删除
useEffect(() => {
if (status === LoginStatus.SUCCESS) {
const userID = 'administrator';
const conversationID = `C2C${userID}`;
setActiveConversation(conversationID);
}
}, [status, setActiveConversation]);

// 切换会话时自动关闭侧边栏
useLayoutEffect(() => {
setIsChatSettingShow(false);
setIsSearchInChatShow(false);
}, [activeConversation?.conversationID]);

if (status === LoginStatus.ERROR) {
return (
<div className="loading-container is-error">
<div className="loading-brand">
<div className="loading-brand-icon">
<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
</div>
<span className="loading-brand-name">Chat</span>
</div>
<div className="error-icon">!</div>
<div className="loading-text">{texts.error}</div>
</div>
);
}

if (status !== LoginStatus.SUCCESS) {
return (
<div className="loading-container">
<div className="loading-brand">
<div className="loading-brand-icon">
<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
</div>
<span className="loading-brand-name">Chat</span>
</div>
<div className="loading-spinner"></div>
<div className="loading-text">{texts.loading}</div>
<div className="loading-progress-bar">
<div className="loading-progress-bar-inner"></div>
</div>
</div>
);
}

return (
<div className={`chat-layout ${isDark ? 'dark' : ''}`}>
{/* 左侧导航 */}
<SideTab activeTab={activeTab} onTabChange={setActiveTab} />

{/* 中间列表 会话列表-联系人列表 */}
<div className="conversation-list-panel">
{activeTab === 'conversations' ? <ConversationList /> : <ContactList className="contact-list" />}
</div>

{/* 右侧聊天 */}
{activeTab === 'conversations' && (
<Chat
className="chat-content-panel"
PlaceholderEmpty={
<div className="empty-placeholder">
<div className="empty-icon">💬</div>
<div className="empty-title">{texts.emptyTitle}</div>
<div className="empty-subtitle">{texts.emptySub}</div>
</div>
}
>
<div className="chat-main">
<ChatHeader
ChatHeaderRight={
<div className="header-actions">
<button
className="icon-button"
onClick={() => setIsSearchInChatShow(!isSearchInChatShow)}
>
<IconSearch size="20px" />
</button>
<button
className="icon-button"
onClick={() => setIsChatSettingShow(!isChatSettingShow)}
>
<IconBulletpoint size="20px" />
</button>
</div>
}
/>
<MessageList />
<MessageInput />
</div>

{/* 会话内搜索侧边栏 */}
{isSearchInChatShow && (
<div className="chat-sidebar-search">
<div className="chat-sidebar-header">
<span className="chat-sidebar-title">群搜索</span>
<button
className="icon-button"
onClick={() => setIsSearchInChatShow(false)}
>
</button>
</div>
<Search variant={VariantType.EMBEDDED} />
</div>
)}
{/* 聊天设置侧边栏 */}
{isChatSettingShow && (
<div className="chat-float-sidebar">
<ChatSetting onClose={() => setIsChatSettingShow(false)} />
</div>
)}
</Chat>
)}

{/* 联系人详情 */}
{activeTab === 'contacts' && (
<ContactInfo
className="contact-detail-panel"
onSendMessage={() => setActiveTab('conversations')}
onEnterGroup={() => setActiveTab('conversations')}
/>
)}
</div>
);
}

// SideTab 组件:左侧导航栏
interface SideTabProps {
activeTab: 'conversations' | 'contacts';
onTabChange: (tab: 'conversations' | 'contacts') => void;
}

function SideTab({ activeTab, onTabChange }: SideTabProps) {
const { theme } = useUIKit();
const { loginUserInfo } = useLoginState();
const isDark = theme === 'dark';

return (
<div className={`side-tab ${isDark ? 'dark' : ''}`}>
{/* 用户头像 */}
<div className="avatar-wrapper">
<Avatar src={loginUserInfo?.avatarUrl} />
<div className="tooltip">
<div className="tooltip-name">{loginUserInfo?.userName || loginUserInfo?.userId || '未命名'}</div>
<div className="tooltip-id">ID: {loginUserInfo?.userId}</div>
</div>
</div>

{/* Tab 切换 */}
<div className="tabs">
<div
className={`tab-item ${activeTab === 'conversations' ? 'active' : ''}`}
onClick={() => onTabChange('conversations')}
title="会话"
>
<IconChat size="24px" />
</div>

<div
className={`tab-item ${activeTab === 'contacts' ? 'active' : ''}`}
onClick={() => onTabChange('contacts')}
title="联系人"
>
<IconUsergroup size="24px" />
</div>
</div>
</div>
);
}

export default App;
复制以下 index.css 代码并替换同级目录的 src/index.css 样式文件。
src/index.css
body { margin: 0; padding: 0; font-family: Inter, Avenir, Helvetica, Arial, sans-serif; min-height: 100vh; box-sizing: border-box; } #root { height: 100vh; display: flex; justify-content: center; align-items: center; } .chat-layout { height: 60vh; aspect-ratio: 16 / 9; margin: 0 auto; display: flex; border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1); overflow: hidden; backdrop-filter: blur(10px); } @media (max-width:1920px) { .chat-layout { height: 80vh; } } .conversation-list-panel { width: 300px; display: flex; flex-direction: column; overflow: hidden; } .contact-list { border-radius: 0; } .chat-content-panel { flex-direction: row !important; flex: 1; border-left: 1px solid var(--uikit-stroke-color-primary); } .contact-detail-panel { flex: 1; } .icon-button { padding: 4px 6px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; border-radius: 4px; font-size: 16px; cursor: pointer; transition: background-color 0.2s; color: var(--uikit-text-color-primary); } .icon-button:hover { background-color: var(--uikit-button-color-secondary-hover); } .icon-button:active { background-color: var(--uikit-button-color-secondary-active); } .header-actions { display: flex; gap: 8px; margin-left: 8px; color: var(--uikit-text-color-link); } .message-toolbar { display: flex; justify-content: space-between; align-items: center; } .message-toolbar-actions { display: flex; align-items: center; gap: 12px; } .chat-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .chat-sidebar-search { border-left: 1px solid var(--uikit-stroke-color-primary); display: flex; flex-direction: column; flex: 0 0 300px; } .chat-float-sidebar { position: absolute; right: 0; top: 0; bottom: 0; min-width: 300px; max-width: 400px; display: flex; flex-direction: column; background-color: var(--uikit-bg-color-operate); box-shadow: -2px 0 8px rgba(0, 0, 0, 0.04), -4px 0 16px rgba(0, 0, 0, 0.06), -8px 0 32px rgba(0, 0, 0, 0.08); overflow: auto; z-index: 1000; } .chat-sidebar-header { position: sticky; top: 0; display: flex; align-items: center; justify-content: space-between; padding: 20px; background-color: var(--uikit-bg-color-operate); border-bottom: 1px solid var(--uikit-stroke-color-primary); z-index: 10; } .chat-sidebar-title { font-size: 16px; font-weight: 500; color: var(--uikit-text-color-primary); } .empty-placeholder { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background-color: var(--uikit-bg-color-operate); border-left: 1px solid var(--uikit-stroke-color-primary); color: #adb5bd; } .empty-icon { font-size: 64px; opacity: 0.3; } .empty-title { font-size: 16px; font-weight: 600; color: #6c757d; } .empty-subtitle { font-size: 14px; color: #868e96; } .loading-container { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; gap: 20px; } .loading-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; } .loading-brand-icon { width: 36px; height: 36px; border-radius: 8px; background: linear-gradient(135deg, #147aff, #6db3f2); display: flex; align-items: center; justify-content: center; } .loading-brand-icon svg { width: 20px; height: 20px; fill: #fff; } .loading-brand-name { font-size: 18px; font-weight: 600; color: #1a1a1a; letter-spacing: 0.2px; } .loading-spinner { width: 36px; height: 36px; border: 3px solid #e8ecf1; border-top-color: #147aff; border-radius: 50%; animation: spin 0.8s linear infinite; } .loading-text { color: #5f6368; font-size: 14px; font-weight: 400; line-height: 1.5; text-align: center; max-width: 360px; } .loading-progress-bar { width: 200px; height: 3px; background-color: #e8ecf1; border-radius: 3px; overflow: hidden; margin-top: 4px; } .loading-progress-bar-inner { width: 40%; height: 100%; background: linear-gradient(90deg, #147aff, #6db3f2); border-radius: 3px; animation: progress-slide 1.6s ease-in-out infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes progress-slide { 0% { transform: translateX(-100%); } 50% { transform: translateX(250%); } 100% { transform: translateX(600%); } } /* ===== Error ===== */ .loading-container.is-error { background-color: #f7f8fa; } .loading-container.is-error .loading-spinner { display: none; } .error-icon { width: 48px; height: 48px; border-radius: 50%; background-color: #fff2f0; border: 1px solid #ffccc7; display: flex; align-items: center; justify-content: center; font-size: 22px; color: #ff4d4f; flex-shrink: 0; } .loading-container.is-error .loading-text { color: #5f6368; font-size: 13px; line-height: 1.6; } .icon-image-effort { display: none; } .side-tab { width: 72px; height: 100%; background: var(--uikit-bg-color-function); display: flex; flex-direction: column; align-items: center; padding: 20px 0; transition: background 0.3s; } .avatar-wrapper { position: relative; margin-bottom: 24px; cursor: pointer; } .avatar-wrapper:hover .tui-avatar { transform: scale(1.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .tooltip { position: absolute; left: 60px; top: 50%; transform: translateY(-50%); padding: 8px 12px; background: rgba(0, 0, 0, 0.85); color: #fff; border-radius: 6px; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none; transition: all 0.3s; z-index: 1000; } .tooltip::before { content: ''; position: absolute; left: -6px; top: 50%; transform: translateY(-50%); border: 6px solid transparent; border-right-color: rgba(0, 0, 0, 0.85); } .avatar-wrapper:hover .tooltip { opacity: 1; visibility: visible; } .tooltip-name { font-size: 14px; font-weight: 500; margin-bottom: 4px; } .tooltip-id { font-size: 12px; opacity: 0.8; } .tabs { display: flex; flex-direction: column; gap: 16px; } .tab-item { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; border-radius: 12px; cursor: pointer; transition: all 0.3s; color: var(--uikit-text-color-primary); } .tab-item:hover { background: rgba(0, 0, 0, 0.05); } .tab-item.active { background: var(--uikit-button-color-primary-default); color: var(--uikit-text-color-button); } .call-kit { position: fixed !important; top: 50%; left: 50%; z-index: 999; transform: translate(-50%, -50%); max-width: 800px; max-height: 600px; }

步骤3:获取 SDKAppID、userID 和 userSig

在之前复制的 App.tsx 代码中,登录鉴权信息是空缺的,您还需要在 useLoginState hook 中填写您的腾讯云应用的鉴权信息,如下所示:
参数
类型
说明
SDKAppID
Number
在创建的音频和视频应用程序中,作为唯一标识符为。可以在 Tencent RTC 控制台 中创建一个应用来获取 SDKAppID。
userID
String
用户唯一标识符,可以在控制台中创建 2~3 个预设账号,用于互相发送消息,仅允许包含大小写字母(a-z, A-Z)、数字(0-9)、下划线和连字符。
secretKey
String
在创建的音频和视频应用程序中,secretKey 作为密钥请妥善保存。可以在 Tencent RTC 控制台 中获取。
userSig
String
用于用户登录认证的安全保护签名,用于确认用户的身份并防止恶意攻击者窃取您的云服务使用权限。
可单击 即时通信 IM 控制台 > 开发工具 > UserSig 工具,填写创建的 userID,即可生成 userSig。
1. 登录 即时通信 IM 控制台,在应用管理页面,单击创建新应用。如果您已有应用,可省略创建应用过程。

2. 应用管理页面的 SDKAppID 列获取 SDKAppID 和密钥信息。

注意:
查看密钥信息需要验证身份。
密钥信息为敏感信息,为防止他人盗用,请妥善保管,谨防泄露。
3. 进入用户管理页面,创建 2~3 个测试账号,用于体验单聊能力和群聊能力。

4. userSig 信息,可单击 即时通信 IM 控制台 > 开发工具 > UserSig 工具,填写创建的 userID,即可生成 userSig。
用户签名说明:
开发环境:如果您在本地运行演示并进行调试开发,可以使用调试文件中的 genTestUserSig 函数(参见步骤 3.2)来生成 'userSig'。在此方法中,SDKSecretKey 易受反编译和逆向工程攻击。一旦您的密钥泄露,攻击者即可窃取您的腾讯云流量。
生产环境:如果您的项目即将上线,请使用 服务端生成用户签名 的方法。

运行和测试

运行命令如下:
npm run dev
注意:
1. 请确保 步骤3 代码中 SDKAppIDuserIDuserSig 均已成功输入,如未替换将会导致项目表现异常。
2. userIDuserSig 为一一对应关系,具体参见 生成 UserSig
3. 如遇到项目启动失败,请检查 开发环境要求 是否满足。

集成更多高级特性

音视频通话

说明:
TUICallKit 是腾讯云推出的一款音视频通话 UI 组件,通过集成该组件,您只需要编写几行代码就可以在聊天应用中体验音视频通话功能。
更多详细信息可参考:音视频通话 > 开通服务
1. 安装 @trtc/calls-uikit-react 依赖
npm install @trtc/calls-uikit-react
2. @trtc/calls-uikit-react 导出 TUICallKit,并挂载到 DOM 节点上。
src/App.tsx 文件中继续补充下面的代码:
// src/App.tsx
import { TUICallKit } from '@trtc/calls-uikit-react';

function App() {
return (
<UIKitProvider>
// 导入 TUICallKit 并添加到代码中
<TUICallKit className="call-kit" />
<ChatApp />
</UIKitProvider>
);
}
3. <ChatHeader /> 上启用 enableCall 属性
<ChatHeader enableCall={true} />
4. 拨打语音通话


发送您的第一条消息

体验单聊功能:搜索好友并发送您的第一条消息



体验群聊功能:创建群聊并发送一条消息



常见问题

什么是 UserSig?

UserSig 是用户登录 Chat 的密码,其本质是对 UserID 等信息加密后得到的密文。

如何生成 UserSig?

UserSig 签发方式是将 UserSig 的计算代码集成到您的服务端,并提供面向项目的接口,在需要 UserSig 时由您的项目向业务服务器发起请求获取动态 UserSig。更多详情请参见 服务端生成 UserSig
注意:
本文示例代码采用的获取 UserSig 的方案是在客户端代码中配置 SECRETKEY,该方法中 SECRETKEY 很容易被反编译逆向破解,一旦您的密钥泄露,攻击者就可以盗用您的腾讯云流量,因此该方法仅适合本地跑通功能调试。 正确的 UserSig 签发方式请参见上文。

是否支持 react 17 版本?

目前不支持 React v17.x,仅支持 React v18.2+ 以上的版本。

我能不能使用第三方组件库,例如 Ant-Design?

核心组件之间的粘连代码可以使用其他组件库,这一点在示例代码中也可以看到,例如您可以将 <ChatSetting /> 封装成全屏抽屉组件。但是核心组件内部已经存在的组件暂时不支持修改。
import { Drawer } from 'antd';

const [isChatSettingShow, setIsChatSettingShow] = useState(false);

function onChatSettingClose() {
setIsChatSettingShow(false);
}

<Drawer
title="设置"
onClose={onChatSettingClose}
open={isChatSettingShow}
>
<ChatSetting />
</Drawer>

表情包的使用

为尊重表情设计版权,Chat Demo/TUIKit 工程中不包含大表情元素切图,正式上线商用前请您替换为自己设计或拥有版权的其他表情包。下图所示默认的小黄脸表情包版权归腾讯云所有,您可以通过升级至 Chat 专业版 Plus 和企业版 免费使用该表情包。




交流与反馈

加入 Telegram 技术交流群组WhatsApp 交流群,享有专业工程师的支持,解决您的难题。

相关文档

UIKit 相关:



帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈