关于 telegram-mini-apps-react
此 Skill 为使用 `@tma.js/sdk-react` 构建 Telegram Mini Apps 的 React 开发者提供指引,涵盖 Telegram UI、用户与启动数据、主题、视口,以及在 Telegram 外进行本地开发。
主要功能
- 01初始化并使用 React SDK
- 02集成 Telegram 原生 UI 组件
- 03读取初始化数据和启动参数
- 04绑定主题与视口 CSS 变量
- 05在本地模拟 Telegram 环境
- 06处理深度链接和平台功能可用性
适用场景
- 01构建 Telegram React Mini App
- 02根据 Telegram 主题和安全区域调整布局
- 03在 Telegram 外测试应用行为
- 04使用启动参数实现应用导航
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nailorsh/ agents_utils --skill telegram-mini-apps-react安装后在 Claude Code 中输入/telegram-mini-apps-react确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/back-button-modal.tsxpage-component.tsxuser-data.tsxreferences/deeplink.mdinit.mdmock-env.md详细介绍
使用 React 构建 Telegram Mini Apps
本技能为使用 React 和 @tma.js/sdk-react 包构建 Telegram Mini Apps 提供全面指导。
概述
Telegram Mini Apps 是在 Telegram 的 WebView 内显示的 Web 应用。它们可与 Telegram 的原生 UI 组件(返回按钮、主按钮)集成,并可访问用户数据、主题参数和平台专属功能。
关键概念:
- Mini Apps 是 Telegram 机器人的附加功能
- 它们运行在 Telegram 的 WebView 内
- 它们通过事件和方法与 Telegram 客户端通信
- 它们可以访问启动参数、初始化数据和主题设置
快速入门
1. 安装
# For React projects, install the React-specific package
pnpm i @tma.js/sdk-react
# DO NOT install both @tma.js/sdk and @tma.js/sdk-react - this causes bugs!
重要:
@tma.js/sdk-react包会完整地重新导出@tma.js/sdk,因此无需单独安装这两个包。
2. 创建新项目(可选)
pnpm dlx @tma.js/create-mini-app@latest
# or
npx @tma.js/create-mini-app@latest
此 CLI 会搭建一个配置完善的完整项目。
项目结构
典型的 Telegram Mini App React 项目结构:
src/
├── main.tsx # Entry point - SDK initialization
├── init.ts # SDK configuration and component mounting
├── mockEnv.ts # Development environment mocking
├── App.tsx # Main React app with routing
├── components/
│ ├── Page.tsx # Page wrapper with back button handling
│ └── EnvUnsupported.tsx # Fallback for non-TG environments
├── hooks/
│ └── useDeeplink.ts # Deep linking handler
└── services/
└── analytics.ts # Analytics with user data
核心概念
SDK 初始化
使用任何功能之前都必须先初始化 SDK。详细实现请参阅 references/init.md。
import {
init as initSDK,
setDebug,
themeParams,
miniApp,
viewport,
backButton,
swipeBehavior,
initData
} from '@tma.js/sdk-react';
export async function init(options: {
debug: boolean;
eruda: boolean;
mockForMacOS: boolean;
}): Promise<void> {
// Enable debug mode for development
setDebug(options.debug);
// Initialize the SDK (REQUIRED before using any features)
initSDK();
// Mount components you'll use in the app
backButton.mount.ifAvailable();
initData.restore();
// Configure swipe behavior
if (swipeBehavior.isSupported()) {
swipeBehavior.mount();
swipeBehavior.disableVertical();
}
// Setup Mini App theming
if (miniApp.mount.isAvailable()) {
themeParams.mount();
miniApp.mount();
themeParams.bindCssVars(); // Binds theme to CSS variables
}
// Configure viewport
if (viewport.mount.isAvailable()) {
viewport.mount().then(() => {
viewport.bindCssVars();
viewport.requestFullscreen();
});
}
}
入口点(main.tsx)
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { retrieveLaunchParams } from '@tma.js/sdk-react';
import { init } from './init';
import App from "./App";
import { EnvUnsupported } from "./components/EnvUnsupported";
// Mock environment for local development
import './mockEnv';
const root = ReactDOM.createRoot(document.getElementById('root')!);
try {
const launchParams = retrieveLaunchParams();
const { tgWebAppPlatform: platform } = launchParams;
const debug = (launchParams.tgWebAppStartParam || '').includes('debug')
|| import.meta.env.DEV;
await init({
debug,
eruda: debug && ['ios', 'android'].includes(platform),
mockForMacOS: platform === 'macos',
}).then(() => {
root.render(
<StrictMode>
<App/>
</StrictMode>,
);
});
} catch (e) {
// Show fallback UI when not in Telegram
root.render(<EnvUnsupported/>);
}
使用返回按钮
返回按钮是显示在标题栏中的 Telegram 原生 UI 元素。
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { backButton, miniApp } from '@tma.js/sdk-react';
export function Page({ children, back = true }) {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
if (back) {
backButton.show();
// onClick returns a cleanup function
return backButton.onClick(() => {
const isDeeplink = location.state?.fromDeeplink;
const isFirstPage = !window.history.state || window.history.state.idx === 0;
if (isDeeplink || isFirstPage) {
miniApp.close(); // Close the Mini App
} else {
navigate(-1); // Go back in history
}
});
}
backButton.hide();
}, [back, navigate, location]);
return <>{children}</>;
}
使用信号(useSignal Hook)
信号是会自动更新的响应式值。在 React 中使用 useSignal 订阅信号:
import { useEffect } from 'react';
import { backButton, useSignal } from '@tma.js/sdk-react';
function BackButtonStatus() {
const isVisible = useSignal(backButton.isVisible);
useEffect(() => {
console.log('Back button is', isVisible ? 'visible' : 'hidden');
}, [isVisible]);
return null;
}
获取初始化数据(用户信息)
初始化数据包含用户信息,可用于身份验证:
import { initData } from '@tma.js/sdk-react';
function getUserId(): number | undefined {
try {
const user = initData.user();
return user?.id;
} catch (e) {
return undefined;
}
}
// Get start parameter (for deep linking)
const startParam = initData.startParam();
启动参数
启动参数包含平台信息、主题和应用数据:
import { retrieveLaunchParams, useLaunchParams } from '@tma.js/sdk-react';
// In component
function Component() {
const launchParams = useLaunchParams();
// launchParams.tgWebAppPlatform - 'ios', 'android', 'macos', 'tdesktop', 'web', 'weba'
// launchParams.tgWebAppVersion - SDK version supported by client
// launchParams.tgWebAppData - init data
// launchParams.tgWebAppThemeParams - theme colors
// launchParams.tgWebAppStartParam - custom start parameter
}
// Outside component
const launchParams = retrieveLaunchParams();
主题
Telegram 会自动提供主题参数。将它们绑定到 CSS 变量:
import { themeParams, miniApp } from '@tma.js/sdk-react';
// During initialization
if (miniApp.mount.isAvailable()) {
themeParams.mount();
miniApp.mount();
themeParams.bindCssVars(); // Creates CSS variables like --tg-theme-bg-color
}
绑定后可用的 CSS 变量:
--tg-theme-bg-color--tg-theme-text-color--tg-theme-hint-color--tg-theme-link-color--tg-theme-button-color--tg-theme-button-text-color--tg-theme-secondary-bg-color--tg-theme-header-bg-color--tg-theme-accent-text-color--tg-theme-section-bg-color--tg-theme-section-header-text-color--tg-theme-subtitle-text-color--tg-theme-destructive-text-color
视口和安全区域
正确处理视口和安全区域,确保布局正常:
import { viewport } from '@tma.js/sdk-react';
if (viewport.mount.isAvailable()) {
viewport.mount().then(() => {
viewport.bindCssVars(); // Binds viewport dimensions to CSS
viewport.requestFullscreen(); // Request fullscreen mode
});
}
可用的 CSS 变量:
/* Safe area insets */
padding-top: var(--tg-viewport-safe-area-inset-top, 0);
padding-bottom: var(--tg-viewport-safe-area-inset-bottom, 0);
/* Content safe area (for notch, etc.) */
padding-top: var(--tg-viewport-content-safe-area-inset-top, 0);
/* Viewport dimensions */
height: var(--tg-viewport-height);
width: var(--tg-viewport-width);
在 CSS 中的用法:
.header {
padding-top: max(2rem, calc(var(--tg-viewport-content-safe-area-inset-top, 0) + var(--tg-viewport-safe-area-inset-top, 0)));
}
.footer {
padding-bottom: calc(1rem + var(--tg-viewport-safe-area-inset-bottom, 0));
}
开发环境模拟
在 Telegram 之外进行本地开发时,请模拟环境。参阅 references/mock-env.md。
import { emitEvent, isTMA, mockTelegramEnv } from '@tma.js/sdk-react';
if (import.meta.env.DEV) {
if (!await isTMA('complete')) {
const themeParams = {
accent_text_color: '#6ab2f2',
bg_color: '#17212b',
button_color: '#5288c1',
button_text_color: '#ffffff',
destructive_text_color: '#ec3942',
header_bg_color: '#17212b',
hint_color: '#708499',
link_color: '#6ab3f3',
secondary_bg_color: '#232e3c',
section_bg_color: '#17212b',
section_header_text_color: '#6ab3f3',
subtitle_text_color: '#708499',
text_color: '#f5f5f5',
};
mockTelegramEnv({
onEvent(e) {
if (e.name === 'web_app_request_theme') {
return emitEvent('theme_changed', { theme_params: themeParams });
}
if (e.name === 'web_app_request_viewport') {
return emitEvent('viewport_changed', {
height: window.innerHeight,
width: window.innerWidth,
is_expanded: true,
is_state_stable: true,
});
}
if (e.name === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
}
},
launchParams: new URLSearchParams([
['tgWebAppThemeParams', JSON.stringify(themeParams)],
['tgWebAppData', new URLSearchParams([
['auth_date', (Date.now() / 1000 | 0).toString()],
['hash', 'mock-hash'],
['signature', 'mock-signature'],
['user', JSON.stringify({ id: 1, first_name: 'Developer' })],
]).toString()],
['tgWebAppVersion', '8.4'],
['tgWebAppPlatform', 'tdesktop'],
]),
});
console.info('⚠️ Running in mocked Telegram environment');
}
}
深度链接
处理深度链接的启动参数。参阅 references/deeplink.md。
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { initData } from "@tma.js/sdk-react";
export function useDeeplink() {
const navigate = useNavigate();
const processedRef = useRef(false);
useEffect(() => {
if (processedRef.current) return;
const startParam = initData.startParam();
if (!startParam) return;
processedRef.current = true;
try {
// startParam is base64url encoded
const base64 = startParam.replace(/-/g, '+').replace(/_/g, '/');
const decoded = atob(base64);
const params = new URLSearchParams(decoded);
const route = params.get('route');
if (route) {
navigate(route, { replace: true, state: { fromDeeplink: true } });
}
} catch (e) {
console.error("Failed to parse startParam:", e);
}
}, [navigate]);
}
最佳实践
1. 始终检查可用性
使用任何方法之前,先检查该方法是否可用:
import { backButton } from '@tma.js/sdk-react';
// Option 1: Check before calling
if (backButton.show.isAvailable()) {
backButton.show();
}
// Option 2: Call only if available (safer, no-op if unavailable)
backButton.show.ifAvailable();
// Option 3: Mount only if available
backButton.mount.ifAvailable();
2. 使用前挂载组件
必须先挂载组件,才能使用其方法:
// ❌ Wrong - will throw error
backButton.show();
// ✅ Correct
backButton.mount();
backButton.show();
3. 处理 macOS 错误
Telegram for macOS 存在已知问题:
if (platform === 'macos') {
mockTelegramEnv({
onEvent(event, next) {
if (event.name === 'web_app_request_theme') {
const tp = themeParams.state() || retrieveLaunchParams().tgWebAppThemeParams;
return emitEvent('theme_changed', { theme_params: tp });
}
if (event.name === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
}
next();
},
});
}
4. 不要重复安装 SDK
切勿同时安装 @tma.js/sdk 和 @tma.js/sdk-react:
// ❌ Wrong - causes bugs
{
"dependencies": {
"@tma.js/sdk": "^3.0.0",
"@tma.js/sdk-react": "^3.0.8"
}
}
// ✅ Correct - only the React package
{
"dependencies": {
"@tma.js/sdk-react": "^3.0.8"
}
}
5. 根据需要禁用滑动
防止意外导航:
if (swipeBehavior.isSupported()) {
swipeBehavior.mount();
swipeBehavior.disableVertical(); // Prevents swipe-to-close
}
向服务端发送初始化数据
进行身份验证时,请将初始化数据发送到你的服务端:
import { retrieveRawInitData } from '@tma.js/sdk-react';
const initDataRaw = retrieveRawInitData();
fetch('https://api.example.com/auth', {
method: 'POST',
headers: {
Authorization: `tma ${initDataRaw}`,
},
});
服务端验证:
- Node.js 使用
@tma.js/init-data-node - 使用你的机器人令牌验证哈希
- 未经验证,切勿信任初始化数据
支持的平台
Mini Apps 支持以下平台:
- android - Telegram for Android
- ios - Telegram for iOS
- macos - Telegram for macOS(存在一些问题)
- tdesktop - Telegram Desktop
- weba - Telegram Web A
- web - Telegram Web K
常见问题
SDK 未初始化错误
使用任何 SDK 功能之前,请确保先调用 init()。
组件未挂载错误
调用组件的方法之前,请先挂载该组件:
backButton.mount();
backButton.show();
方法不可用错误
调用方法之前,请检查其可用性:
if (backButton.show.isAvailable()) {
backButton.show();
}
在 Telegram 外部运行时应用崩溃
在开发期间模拟环境,并提供备用 UI。
其他资源
另请参阅
- references/init.md - 完整的初始化示例
- references/mock-env.md - 环境模拟
- references/deeplink.md - 深层链接实现
- examples/page-component.tsx - 带返回按钮的页面包装器