mcpField
T
telegram-mini-apps-react

telegram-mini-apps-react

github.com/nailorsh开发045 次安装23 周前更新
代码仓库

关于 telegram-mini-apps-react

此 Skill 为使用 `@tma.js/sdk-react` 构建 Telegram Mini Apps 的 React 开发者提供指引,涵盖 Telegram UI、用户与启动数据、主题、视口,以及在 Telegram 外进行本地开发。

主要功能

  1. 01初始化并使用 React SDK
  2. 02集成 Telegram 原生 UI 组件
  3. 03读取初始化数据和启动参数
  4. 04绑定主题与视口 CSS 变量
  5. 05在本地模拟 Telegram 环境
  6. 06处理深度链接和平台功能可用性

适用场景

  1. 01构建 Telegram React Mini App
  2. 02根据 Telegram 主题和安全区域调整布局
  3. 03在 Telegram 外测试应用行为
  4. 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.tsx
page-component.tsx
user-data.tsx
references/
deeplink.md
init.md
mock-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。

其他资源

另请参阅

评论

支持 Markdown