mcpField
T
tamagui-v2

tamagui-v2

github.com/ramarivera设计与 UI0114 次安装40 周前更新
代码仓库

关于 tamagui-v2

tamagui-v2 是面向跨平台 React 应用开发的 Tamagui 指南技能,涵盖 Web 与 React Native 的样式、组件和主题。内容还涉及 Bento 组件与 Takeout 全栈起始套件的使用模式。

主要功能

  1. 01统一的样式与设计令牌
  2. 02Web 和 React Native 组件
  3. 03编译器静态提取样式
  4. 04支持明暗模式的主题系统
  5. 05Bento 表单、表格与列表指南
  6. 06Takeout 路由、认证与数据指南

适用场景

  1. 01构建 Web 与原生共用的 React 界面
  2. 02使用令牌与主题统一应用样式
  3. 03为表单和数据表参考组件模式
  4. 04查阅 Takeout 全栈功能用法

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ramarivera/tamagui-skills --skill tamagui-v2

安装后在 Claude Code 中输入/tamagui-v2确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

__internal/
__generation-guide.md
__llms.txt
references/
animations.md
bento-forms.md
bento-lists.md
bento-tables.md
breaking-changes-and-new-features.md
compiler.md
components.md
configuration.md
core-styling.md
README.md
takeout-auth.md
takeout-database.md
takeout-routing.md
takeout-zero.md
theming.md

详细介绍

Tamagui v2 技能

适用于 Web 和原生平台的通用 React UI 框架(v1.144.0+)

此技能提供全面指导,帮助你使用 Tamagui 的样式系统、组件库、Bento 高级组件和 Takeout 入门套件构建跨平台 React 应用。


概述

Tamagui 是一个通用 UI 框架,让你只需编写一次代码,即可在 Web 和 React Native 上部署,并获得出色的性能。它具备以下特性:

  • 统一的样式 API - styled() 函数,支持设计令牌、变体和响应式模式
  • 优化编译器 - 在构建时将样式提取为原子 CSS,以实现最高性能
  • 主题系统 - 12 阶色阶,自动支持浅色/深色模式
  • 跨平台组件 - Button、Dialog、Sheet、Input、Select、Tabs 等
  • 高级生态系统 - Bento(生产级表单、表格、列表)+ Takeout(全栈入门套件,包含路由、身份验证、数据库和实时同步)

版本: 1.144.0+
平台: Web(React)、iOS/Android(React Native)、Expo
许可证: 开源(MIT)+ Bento/Takeout(商业许可证)


何时使用此技能

遇到以下触发词时启用此技能:

核心框架
  • "tamagui"、"通用 UI"、"react native web"、"跨平台 UI"
  • "styled()"、"设计令牌"、"$token"、"主题令牌"
  • "XStack"、"YStack"、"ZStack"、"Stack 组件"
  • "useTheme"、"useMedia"、"响应式设计"
  • "@tamagui/*" 导入、"@tamagui/core"
  • "createStyledContext"、"withStaticProperties"
  • "variant"、"variants"、"defaultVariants"
Bento 组件
  • "bento"、"@tamagui/bento"、"bento 组件"
  • "React Hook Form"、"react-hook-form"、"表单验证"
  • "Zod"、"zod 验证"、"表单架构"
  • "TanStack Table"、"@tanstack/react-table"、"数据表格"
  • "虚拟列表"、"FlatList"、"瀑布流布局"
Takeout 入门套件
  • "takeout"、"tamagui takeout"、"one.js"
  • "基于文件的路由"、"one router"、"SSG"、"SSR"
  • "Better Auth"、"身份验证"、"OAuth"
  • "Drizzle ORM"、"数据库"、"PostgreSQL"
  • "Zero Sync"、"实时同步"、"离线优先"
特定平台
  • "动画驱动程序"、"CSS 动画"、"Reanimated"
  • "编译器优化"、"静态提取"、"包体积"
  • "仅限 Web"、"仅限原生平台"、"特定平台"

快速入门模式

1. 使用 styled() 设置基本样式

通过扩展现有组件来创建自定义组件:

import { View, Text, styled } from '@tamagui/core'

// Simple styled component
export const Card = styled(View, {
  padding: '$4',
  backgroundColor: '$background',
  borderRadius: '$4',
  borderWidth: 1,
  borderColor: '$borderColor',
})

// With variants
export const Button = styled(View, {
  padding: '$3',
  borderRadius: '$2',
  backgroundColor: '$blue10',
  cursor: 'pointer',
  
  variants: {
    variant: {
      primary: {
        backgroundColor: '$blue10',
      },
      secondary: {
        backgroundColor: '$gray5',
      },
      outlined: {
        backgroundColor: 'transparent',
        borderWidth: 1,
        borderColor: '$borderColor',
      },
    },
    size: {
      small: { padding: '$2' },
      medium: { padding: '$3' },
      large: { padding: '$4' },
    },
  } as const,
  
  defaultVariants: {
    variant: 'primary',
    size: 'medium',
  },
})

// Usage
<Button variant="outlined" size="large">Click Me</Button>
2. 核心布局组件

Stack 是 Tamagui 布局的基础:

import { XStack, YStack, ZStack, Text, Button } from 'tamagui'

export function LayoutExample() {
  return (
    <YStack gap="$4" padding="$4">
      {/* Horizontal stack */}
      <XStack gap="$2" justifyContent="space-between" alignItems="center">
        <Text>Label</Text>
        <Button>Action</Button>
      </XStack>
      
      {/* Vertical stack with responsive gap */}
      <YStack 
        gap="$4"
        $gtSm={{ gap: '$6' }}  // Larger gap on small+ screens
      >
        <Card>Item 1</Card>
        <Card>Item 2</Card>
      </YStack>
      
      {/* Overlay stack (position: relative) */}
      <ZStack width={300} height={200}>
        <View backgroundColor="$blue5" fullscreen />
        <Text position="absolute" top="$4" left="$4">
          Overlay Text
        </Text>
      </ZStack>
    </YStack>
  )
}
3. 使用设计令牌

设计令牌为应用中的值提供一致性:

import { View, Text, createTamagui } from '@tamagui/core'

// Tokens are defined in createTamagui config
const config = createTamagui({
  tokens: {
    color: {
      white: '#fff',
      black: '#000',
      blue: '#0066cc',
    },
    space: {
      1: 4,
      2: 8,
      3: 12,
      4: 16,
    },
    size: {
      sm: 100,
      md: 200,
      lg: 300,
    },
    radius: {
      1: 4,
      2: 8,
      3: 12,
      4: 16,
    },
  },
})

// Use tokens with $ prefix
<View 
  padding="$4"           // Uses space.4 = 16px
  backgroundColor="$blue" // Uses color.blue
  borderRadius="$3"      // Uses radius.3 = 12px
  width="$md"            // Uses size.md = 200px
/>

// Tokens work in styled()
const StyledCard = styled(View, {
  padding: '$4',
  margin: '$2',
  backgroundColor: '$background',
  borderRadius: '$4',
})
4. 使用 12 阶色阶设置主题

Tamagui 使用语义化色阶系统(1-12)来保持主题一致:

import { YStack, Text, Theme } from 'tamagui'

export function ThemeExample() {
  return (
    <YStack 
      backgroundColor="$color1"  // Subtle background
      borderColor="$color6"      // Regular border
      padding="$4"
    >
      <Text color="$color12">Heading (highest contrast)</Text>
      <Text color="$color11">Body text (high contrast)</Text>
      <Text color="$color10">Muted text (low contrast)</Text>
      
      {/* Apply sub-theme */}
      <Theme name="blue">
        <YStack 
          backgroundColor="$color9"  // Blue solid background
          padding="$3"
          borderRadius="$2"
        >
          <Text color="$color1">White text on blue</Text>
        </Theme>
      </YStack>
    </YStack>
  )
}

// Dynamic theme switching
import { useTheme } from '@tamagui/core'

export function ThemedComponent() {
  const theme = useTheme()
  
  // Access theme values directly
  console.log(theme.background.val) // e.g., "#ffffff"
  console.log(theme.color11.val)    // e.g., "#333333"
  
  return <Text color={theme.color11}>Themed Text</Text>
}
5. 使用媒体查询实现响应式设计

使用媒体查询属性构建响应式布局:

import { YStack, Text, useMedia } from 'tamagui'

export function ResponsiveExample() {
  const media = useMedia()
  
  return (
    <YStack
      padding="$4"
      $gtSm={{ padding: '$6' }}     // > sm breakpoint
      $gtMd={{ padding: '$8' }}     // > md breakpoint
      flexDirection="column"
      $gtLg={{ flexDirection: 'row' }}  // Row layout on large screens
    >
      <Text
        fontSize="$4"
        $gtSm={{ fontSize: '$5' }}
        $gtMd={{ fontSize: '$6' }}
      >
        Responsive Text
      </Text>
      
      {/* Conditional rendering based on media query */}
      {media.gtMd && <Text>Only on medium+ screens</Text>}
    </YStack>
  )
}

// Configure media queries in createTamagui
const config = createTamagui({
  media: {
    xs: { maxWidth: 660 },
    sm: { maxWidth: 800 },
    md: { maxWidth: 1020 },
    lg: { maxWidth: 1280 },
    xl: { maxWidth: 1420 },
    xxl: { maxWidth: 1600 },
    gtXs: { minWidth: 660 + 1 },
    gtSm: { minWidth: 800 + 1 },
    gtMd: { minWidth: 1020 + 1 },
    gtLg: { minWidth: 1280 + 1 },
  },
})
6. 使用 enterStyle/exitStyle 添加动画

为任何组件添加流畅动画:

import { YStack, Button, AnimatePresence } from 'tamagui'
import { useState } from 'react'

export function AnimationExample() {
  const [show, setShow] = useState(false)
  
  return (
    <YStack gap="$4">
      <Button onPress={() => setShow(!show)}>
        Toggle
      </Button>
      
      <AnimatePresence>
        {show && (
          <YStack
            key="animated-box"
            animation="quick"
            enterStyle={{ 
              opacity: 0, 
              y: -20,
              scale: 0.9,
            }}
            exitStyle={{ 
              opacity: 0,
              y: 20,
              scale: 0.9,
            }}
            opacity={1}
            y={0}
            scale={1}
            backgroundColor="$blue5"
            padding="$4"
            borderRadius="$4"
          >
            <Text>Animated Content</Text>
          </YStack>
        )}
      </AnimatePresence>
    </YStack>
  )
}

// Define animations in createTamagui
import { createAnimations } from '@tamagui/animations-react-native'

const animations = createAnimations({
  quick: {
    type: 'spring',
    damping: 20,
    mass: 1.2,
    stiffness: 250,
  },
  bouncy: {
    type: 'spring',
    damping: 10,
    mass: 0.9,
    stiffness: 100,
  },
})
7. 使用 createStyledContext 构建复合组件

构建协调一致的组件系列:

import { createStyledContext, styled, View, Text } from '@tamagui/core'
import { withStaticProperties } from '@tamagui/helpers'

// Define context
const CardContext = createStyledContext({
  size: '$4' as any,
})

// Create base component
const CardFrame = styled(View, {
  backgroundColor: '$background',
  borderRadius: '$4',
  borderWidth: 1,
  borderColor: '$borderColor',
  
  context: CardContext,
  
  variants: {
    size: {
      small: { padding: '$3' },
      medium: { padding: '$4' },
      large: { padding: '$6' },
    },
  } as const,
})

// Create child components that consume context
const CardTitle = styled(Text, {
  context: CardContext,
  fontWeight: 'bold',
  
  variants: {
    size: {
      small: { fontSize: '$4' },
      medium: { fontSize: '$5' },
      large: { fontSize: '$6' },
    },
  } as const,
})

const CardDescription = styled(Text, {
  context: CardContext,
  color: '$color11',
  
  variants: {
    size: {
      small: { fontSize: '$3' },
      medium: { fontSize: '$4' },
      large: { fontSize: '$5' },
    },
  } as const,
})

// Compose with static properties
export const Card = withStaticProperties(CardFrame, {
  Title: CardTitle,
  Description: CardDescription,
})

// Usage - size cascades to all children
<Card size="large">
  <Card.Title>Large Card</Card.Title>
  <Card.Description>
    All text automatically sized large
  </Card.Description>
</Card>
8. 表单组件示例
import { Button, Dialog, Input, Label, XStack, YStack } from 'tamagui'

export function LoginForm() {
  return (
    <YStack gap="$4" padding="$4">
      <YStack gap="$2">
        <Label htmlFor="email">Email</Label>
        <Input 
          id="email" 
          placeholder="[email protected]"
          autoCapitalize="none"
          keyboardType="email-address"
        />
      </YStack>
      
      <YStack gap="$2">
        <Label htmlFor="password">Password</Label>
        <Input 
          id="password" 
          secureTextEntry
          placeholder="••••••••"
        />
      </YStack>
      
      <XStack gap="$2" justifyContent="flex-end">
        <Button variant="outlined">Cancel</Button>
        <Button theme="blue">Sign In</Button>
      </XStack>
    </YStack>
  )
}
9. Dialog 模式
import { Button, Dialog, XStack, YStack, H2, Paragraph } from 'tamagui'

export function DialogExample() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button>Open Dialog</Button>
      </Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Overlay
          key="overlay"
          animation="quick"
          opacity={0.5}
          enterStyle={{ opacity: 0 }}
          exitStyle={{ opacity: 0 }}
        />

        <Dialog.Content
          bordered
          elevate
          key="content"
          animation={['quick', { opacity: { overshootClamping: true } }]}
          enterStyle={{ x: 0, y: -20, opacity: 0, scale: 0.9 }}
          exitStyle={{ x: 0, y: 10, opacity: 0, scale: 0.95 }}
          gap="$4"
        >
          <Dialog.Title>Confirm Action</Dialog.Title>
          <Dialog.Description>
            Are you sure you want to proceed?
          </Dialog.Description>

          <XStack alignSelf="flex-end" gap="$2">
            <Dialog.Close displayWhenAdapted asChild>
              <Button variant="outlined">Cancel</Button>
            </Dialog.Close>
            <Dialog.Close asChild>
              <Button theme="blue">Confirm</Button>
            </Dialog.Close>
          </XStack>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog>
  )
}
10. Adapt 模式(小屏幕上使用移动端 Sheet)
import { Button, Dialog, Sheet, Adapt } from 'tamagui'

export function AdaptiveDialog() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button>Open</Button>
      </Dialog.Trigger>

      <Adapt when="sm" platform="touch">
        <Sheet animation="medium" zIndex={200000} modal dismissOnSnapToBottom>
          <Sheet.Frame padding="$4" gap="$4">
            <Adapt.Contents />
          </Sheet.Frame>
          <Sheet.Overlay 
            animation="lazy"
            enterStyle={{ opacity: 0 }}
            exitStyle={{ opacity: 0 }}
          />
        </Sheet>
      </Adapt>

      <Dialog.Portal>
        <Dialog.Overlay />
        <Dialog.Content>
          {/* Content shows as Dialog on desktop, Sheet on mobile */}
          <Dialog.Title>Adaptive UI</Dialog.Title>
          <Dialog.Description>
            This is a dialog on desktop, sheet on mobile
          </Dialog.Description>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog>
  )
}

Bento 组件概览

来自 @tamagui/bento 的高级组件(生产环境使用需持有 Bento 许可证)。

表单系统

可组合的输入组件,集成 React Hook Form 和 Zod:

import { Input } from '@tamagui/bento/components/inputsParts'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
})

export function BentoForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
  })
  
  return (
    <YStack gap="$4">
      <Input size="$4">
        <Input.Label htmlFor="email">Email</Input.Label>
        <Input.Box>
          <Input.Section>
            <Mail size={16} />
          </Input.Section>
          <Input.Area 
            id="email" 
            placeholder="[email protected]"
            {...register('email')}
          />
        </Input.Box>
        {errors.email && (
          <Input.Info color="$red10">{errors.email.message}</Input.Info>
        )}
      </Input>
      
      <Input size="$4">
        <Input.Label htmlFor="password">Password</Input.Label>
        <Input.Box>
          <Input.Area 
            id="password" 
            secureTextEntry
            placeholder="••••••••"
            {...register('password')}
          />
          <Input.Section>
            <Input.Button>
              <Eye size={16} />
            </Input.Button>
          </Input.Section>
        </Input.Box>
        {errors.password && (
          <Input.Info color="$red10">{errors.password.message}</Input.Info>
        )}
      </Input>
    </YStack>
  )
}

主要特性:

  • Input.Label - 通过 htmlFor 绑定的无障碍标签
  • Input.Box - 感知焦点的容器,带有 FocusContext
  • Input.Section - 用于图标/按钮的布局区域
  • Input.Area - 实际的文本输入区域
  • Input.Icon - 带主题样式的图标容器
  • Input.Button - 输入框内的操作按钮
  • Input.Info - 辅助/错误文本
使用 TanStack Table 构建表格

可用于生产环境的数据表格,支持排序、筛选和分页:

import { DataTable } from '@tamagui/bento/components/Table'
import { createColumnHelper } from '@tanstack/react-table'

type User = {
  id: number
  name: string
  email: string
  role: string
}

const columnHelper = createColumnHelper<User>()

const columns = [
  columnHelper.accessor('name', {
    header: 'Name',
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('email', {
    header: 'Email',
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('role', {
    header: 'Role',
    cell: info => <Text textTransform="capitalize">{info.getValue()}</Text>,
  }),
]

export function UsersTable({ users }: { users: User[] }) {
  return (
    <DataTable
      data={users}
      columns={columns}
      enableSorting
      enableFiltering
      enablePagination
      pageSize={10}
    />
  )
}
虚拟化列表

使用 FlatList 模式高效渲染列表:

import { FlatList } from '@tamagui/bento/components/List'
import { YStack, Text } from 'tamagui'

export function VirtualizedList({ items }: { items: string[] }) {
  return (
    <FlatList
      data={items}
      keyExtractor={(item, index) => `${item}-${index}`}
      renderItem={({ item }) => (
        <YStack padding="$4" borderBottomWidth={1} borderColor="$borderColor">
          <Text>{item}</Text>
        </YStack>
      )}
      estimatedItemSize={60}
    />
  )
}

其他模式:

  • 瀑布流布局 - Pinterest 风格的网格
  • 水平轮播 - 可滑动列表
  • 动画列表 - 进入/退出动画
  • 下拉刷新 - 适合移动设备的刷新方式

Takeout 技术栈概览

来自 Tamagui Takeout 的全栈入门套件(需要许可证)。

One.js 路由

基于文件的路由,支持 SSG/SSR/SPA 模式:

app/
  _layout.tsx              # Root layout
  index.tsx                # / (home page)
  blog/
    [slug].tsx             # /blog/my-post (dynamic route)
  (auth)/                  # Route group (no URL nesting)
    login.tsx              # /login
    signup.tsx             # /signup
  +not-found.tsx           # Custom 404 page

类型安全的导航:

import { Link, useRouter, useParams } from 'one'

export function Navigation() {
  const router = useRouter()
  
  return (
    <YStack gap="$2">
      {/* Declarative navigation */}
      <Link href="/">Home</Link>
      <Link href="/blog/hello-world">Blog Post</Link>
      
      {/* Programmatic navigation */}
      <Button onPress={() => router.push('/about')}>
        Go to About
      </Button>
    </YStack>
  )
}

// Dynamic routes
export function BlogPost() {
  const { slug } = useParams<{ slug: string }>()
  return <Text>Post: {slug}</Text>
}

服务器端数据加载:

import { createLoader } from 'one'

export const loader = createLoader(async (context) => {
  const post = await db.query.posts.findFirst({
    where: eq(posts.slug, context.params.slug),
  })
  
  return { post }
})

export default function BlogPost() {
  const { post } = useLoader(loader)
  return <Text>{post.title}</Text>
}
Better Auth 集成

支持多种策略的完整身份验证系统:

import { signIn, signOut, useSession } from '~/lib/auth'

export function AuthExample() {
  const session = useSession()
  
  if (!session.user) {
    return (
      <YStack gap="$4">
        {/* Email/password */}
        <Button onPress={() => signIn.email({ email, password })}>
          Sign In
        </Button>
        
        {/* OAuth */}
        <Button onPress={() => signIn.social({ provider: 'github' })}>
          Sign in with GitHub
        </Button>
        
        {/* Magic link */}
        <Button onPress={() => signIn.magicLink({ email })}>
          Send Magic Link
        </Button>
      </YStack>
    )
  }
  
  return (
    <YStack gap="$2">
      <Text>Welcome, {session.user.name}</Text>
      <Button onPress={() => signOut()}>Sign Out</Button>
    </YStack>
  )
}

功能:

  • 电子邮件/密码身份验证
  • OAuth(GitHub、Google 等)
  • 魔法链接(电子邮件)
  • OTP(电子邮件/电话)
  • 会话管理
  • 适用于原生应用的 JWT 令牌
Drizzle ORM(数据库)

使用 PostgreSQL 执行类型安全的数据库查询:

import { db } from '~/db'
import { posts, users } from '~/db/schema'
import { eq, desc } from 'drizzle-orm'

// Define schema
export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  slug: text('slug').notNull().unique(),
  content: text('content'),
  authorId: integer('author_id').references(() => users.id),
  createdAt: timestamp('created_at').defaultNow(),
})

// Query
const allPosts = await db.query.posts.findMany({
  orderBy: [desc(posts.createdAt)],
  with: {
    author: true,  // Join with users
  },
})

// Insert
await db.insert(posts).values({
  title: 'My Post',
  slug: 'my-post',
  content: 'Post content...',
  authorId: 1,
})

// Update
await db.update(posts)
  .set({ title: 'Updated Title' })
  .where(eq(posts.id, 1))

// Delete
await db.delete(posts).where(eq(posts.id, 1))
Zero Sync(实时数据)

离线优先的实时同步:

import { useQuery, useMutation } from '@rocicorp/zero/react'
import { z } from '@rocicorp/zero'

// Define schema
const todoSchema = z.object({
  id: z.string(),
  text: z.string(),
  completed: z.boolean(),
  createdAt: z.number(),
})

export function TodoList() {
  // Real-time query (updates automatically)
  const [todos] = useQuery(q => q.todos.orderBy('createdAt', 'desc'))
  
  // Optimistic mutation
  const [addTodo] = useMutation(async (tx, text: string) => {
    await tx.todos.insert({
      id: crypto.randomUUID(),
      text,
      completed: false,
      createdAt: Date.now(),
    })
  })
  
  const [toggleTodo] = useMutation(async (tx, id: string) => {
    const todo = await tx.todos.get(id)
    if (todo) {
      await tx.todos.update({
        id,
        completed: !todo.completed,
      })
    }
  })
  
  return (
    <YStack gap="$2">
      {todos.map(todo => (
        <XStack key={todo.id} gap="$2">
          <Checkbox 
            checked={todo.completed}
            onCheckedChange={() => toggleTodo(todo.id)}
          />
          <Text>{todo.text}</Text>
        </XStack>
      ))}
    </YStack>
  )
}

功能:

  • 在设备之间实时同步
  • 离线优先架构
  • 乐观变更(即时更新 UI)
  • 自动解决冲突
  • 使用 Zod 执行类型安全的查询
  • 用于实现零延迟查询的客户端副本

参考指南

如需特定主题的详细文档,请参阅以下参考文件:

主题参考文件描述
核心样式core-styling.mdstyled() API、变体系统、createStyledContext、组合模式、TypeScript 集成
组件components.mdButton、Dialog、Sheet、Input、Select、Tabs、Switch、Popover、Stack、Adapt 模式
主题theming.md12 级色阶系统、主题创建、动态主题切换、useTheme 钩子
动画animations.md动画驱动程序(CSS、React Native、Reanimated、Motion)、enterStyle/exitStyle、AnimatePresence
配置configuration.mdcreateTamagui 函数、令牌、主题、字体、媒体查询、简写属性
编译器compiler.md静态提取、Babel 优化、原子 CSS 生成、平台特定设置
Bento 表单bento-forms.mdReact Hook Form 集成、Zod 验证、可组合输入系统、无障碍功能
Bento 表格bento-tables.mdTanStack Table v8 集成、排序、分页、筛选、自适应布局
Bento 列表bento-lists.mdFlatList 模式、虚拟化列表、瀑布流布局、性能优化
Takeout 路由takeout-routing.mdOne.js 基于文件的路由、SSG/SSR/SPA 模式、动态路由、服务器端数据加载
Takeout 身份验证takeout-auth.mdBetter Auth 集成、会话管理、OAuth、魔法链接、OTP
Takeout 数据库takeout-database.mdDrizzle ORM 设置、架构定义、类型安全的查询、迁移
Takeout Zerotakeout-zero.md用于实时数据的 Zero Sync、离线优先架构、乐观变更
破坏性变更breaking-changes-and-new-features.md从旧版本迁移的指南、配置 v4/v5 的差异

常见问题

1. 令牌用法

❌ 错误:

<View padding={16} />  // Hard-coded value
<View padding="16px" /> // String with unit

✅ 正确:

<View padding="$4" />  // Token reference
<View padding={16} />  // Number is OK if intentional (not extracted by compiler)
2. 变体定义

❌ 错误:

// Missing `as const`
variants: {
  size: {
    small: { padding: '$2' },
    large: { padding: '$4' },
  },
}

✅ 正确:

// Must use `as const` for proper TypeScript inference
variants: {
  size: {
    small: { padding: '$2' },
    large: { padding: '$4' },
  },
} as const
3. 平台特定样式

❌ 错误:

// Using platform detection in styled()
const Component = styled(View, {
  padding: Platform.OS === 'web' ? 10 : 20,  // Won't extract
})

✅ 正确:

// Use platform prop modifiers
const Component = styled(View, {
  padding: 20,
  $platform-web: {
    padding: 10,
  },
})
4. 媒体查询属性顺序

❌ 错误:

<View 
  $gtMd={{ padding: '$8' }}  // Applied first
  padding="$4"               // Overrides media query
/>

✅ 正确:

<View 
  padding="$4"               // Base value
  $gtMd={{ padding: '$8' }}  // Overrides on medium+
/>
5. 动画驱动不匹配

❌ 错误:

// Using spring physics with CSS driver
import { createAnimations } from '@tamagui/animations-css'

const animations = createAnimations({
  bouncy: {
    type: 'spring',  // CSS doesn't support springs!
    damping: 10,
  },
})

✅ 正确:

// CSS driver uses easing strings
import { createAnimations } from '@tamagui/animations-css'

const animations = createAnimations({
  bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55) 500ms',
})

// Or use React Native driver for springs
import { createAnimations } from '@tamagui/animations-react-native'

const animations = createAnimations({
  bouncy: {
    type: 'spring',
    damping: 10,
    stiffness: 100,
  },
})
6. 未设置静态属性的 Context

❌ 错误:

// Creating compound components without withStaticProperties
const Card = styled(View, { context: CardContext })
const CardTitle = styled(Text, { context: CardContext })

// Usage requires separate imports
import { Card } from './Card'
import { CardTitle } from './CardTitle'

<Card>
  <CardTitle>Title</CardTitle>
</Card>

✅ 正确:

import { withStaticProperties } from '@tamagui/helpers'

const CardFrame = styled(View, { context: CardContext })
const CardTitle = styled(Text, { context: CardContext })

export const Card = withStaticProperties(CardFrame, {
  Title: CardTitle,
})

// Single import
<Card>
  <Card.Title>Title</Card.Title>
</Card>
7. 缺少 AnimatePresence

❌ 错误:

// Using exitStyle without AnimatePresence
{show && (
  <View 
    animation="quick"
    enterStyle={{ opacity: 0 }}
    exitStyle={{ opacity: 0 }}  // Won't animate on exit!
  />
)}

✅ 正确:

import { AnimatePresence } from 'tamagui'

<AnimatePresence>
  {show && (
    <View 
      key="animated-view"  // Key is required!
      animation="quick"
      enterStyle={{ opacity: 0 }}
      exitStyle={{ opacity: 0 }}
    />
  )}
</AnimatePresence>
8. 内联样式与 styled()

❌ 错误(性能问题):

// Inline styles aren't extracted by compiler
{items.map(item => (
  <View 
    key={item.id}
    style={{ 
      padding: 16, 
      backgroundColor: '#fff',
      borderRadius: 8,
    }}
  />
))}

✅ 正确:

// Use styled() for extractable styles
const ItemView = styled(View, {
  padding: '$4',
  backgroundColor: '$background',
  borderRadius: '$2',
})

{items.map(item => (
  <ItemView key={item.id} />
))}
9. Web 与 Native 的差异

请留意平台特定的行为:

  • CSS 驱动器 - 仅限 Web,不支持弹簧物理效果
  • Sheet 组件 - 在移动设备上配合触摸手势可提供更好的用户体验
  • Adapt 模式 - 根据平台/屏幕尺寸自动切换组件
  • 表单属性(type、action、method)- 仅限 Web
  • 动画驱动器 - Reanimated 在 Web 上表现不佳,在 Native 上表现出色
10. 编译器优化

样式必须可静态分析,编译器才能提取:

❌ 不会提取:

const dynamicPadding = isPremium ? '$6' : '$4'
<View padding={dynamicPadding} />  // Runtime dynamic value

✅ 会提取:

<View padding={isPremium ? '$6' : '$4'} />  // Inline ternary is OK
// Or use variants
<View size={isPremium ? 'large' : 'small'} />

代码生成提示

生成 Tamagui 代码时,请遵循以下最佳实践:

1. 从配置开始

生成组件前,务必确保存在包含 tokens、主题和媒体查询的 createTamagui 配置。

2. 始终使用 Tokens

优先使用设计 tokens($4、$background、$color11),而非硬编码值,以便提取样式并支持主题化。

3. 利用变体提高灵活性

为组件创建尺寸、变体和状态变体,而不是分别定义多个组件:

// ✅ Good - One component with variants
const Button = styled(View, {
  variants: {
    variant: { primary: {...}, secondary: {...} },
    size: { small: {...}, large: {...} },
  } as const,
})

// ❌ Avoid - Multiple separate components
const PrimaryButton = styled(View, { ... })
const SecondaryButton = styled(View, { ... })
4. 使用 createStyledContext 进行组合

对于复合组件(Card、Input、Select),使用 createStyledContext 共享尺寸和样式:

const Context = createStyledContext({ size: '$4' })
const Parent = styled(View, { context: Context, variants: {...} })
const Child = styled(Text, { context: Context, variants: {...} })
export const Component = withStaticProperties(Parent, { Child })
5. 添加无障碍支持

务必添加无障碍属性:

<Input 
  id="email"
  aria-label="Email address"
  aria-describedby="email-error"
  aria-invalid={!!error}
/>
<Label htmlFor="email">Email</Label>
{error && <Text id="email-error">{error}</Text>}
6. 默认支持响应式设计

添加响应式属性,提供更好的用户体验:

<YStack
  padding="$4"
  $gtSm={{ padding: '$6' }}
  $gtMd={{ padding: '$8' }}
  flexDirection="column"
  $gtLg={{ flexDirection: 'row' }}
/>
7. 动画模式

使用 enterStyle/exitStyle 实现进入/退出动画,并用 AnimatePresence 包裹:

<AnimatePresence>
  {show && (
    <View
      key="unique-key"
      animation="quick"
      enterStyle={{ opacity: 0, y: -10 }}
      exitStyle={{ opacity: 0, y: 10 }}
      opacity={1}
      y={0}
    />
  )}
</AnimatePresence>
8. 平台适配

使用 Adapt 模式构建适合移动设备的界面:

<Dialog>
  <Adapt when="sm" platform="touch">
    <Sheet>
      <Adapt.Contents />
    </Sheet>
  </Adapt>
  <Dialog.Portal>
    <Dialog.Content>...</Dialog.Content>
  </Dialog.Portal>
</Dialog>
9. TypeScript 类型

务必导出可复用组件的属性类型:

import { GetProps } from '@tamagui/core'

export const MyComponent = styled(View, { ... })
export type MyComponentProps = GetProps<typeof MyComponent>
10. 测试注意事项

生成测试代码时:

  • 使用 testID 属性标识组件(跨平台)
  • 使用 TamaguiProvider 模拟主题 Context
  • 使用媒体查询模拟来测试响应式行为
  • 使用 AnimatePresence 的退出回调验证动画

版本与生态说明

当前版本: 1.144.0+

依赖项:

  • React 19+(或 React 18.2+)
  • React Native(用于 Native 目标平台)
  • Expo SDK(可选,用于托管工作流)

生态系统包:

包描述
tamagui包含核心功能和组件的元包
@tamagui/core核心样式引擎
@tamagui/animations-cssCSS 动画驱动器
@tamagui/animations-react-nativeReact Native 动画驱动器
@tamagui/animations-reanimatedReanimated v3 驱动器
@tamagui/animations-motiMoti/Motion 驱动器
@tamagui/themes主题创建辅助工具
@tamagui/colorsRadix UI 色阶
@tamagui/bento高级组件(需要许可证)
Tamagui Takeout全栈起步模板(需要许可证)

从 v1.x 迁移:


快速链接


其他资源

如需查看全面的示例和模式,请浏览:


最后更新: 2026 年 1 月
技能版本: 2.0
生成自: Tamagui 官方文档(v1.144.0+)

评论

支持 Markdown