Tamagui 跨端 UI 开发实战指南:styled()、Tokens、主题系统与编译优化全解析
发布时间:2026/9/15 2:53:28来源:尧图网络
Tamagui 跨端 UI 开发实战指南styled()、Tokens、主题系统与编译优化全解析【免费下载链接】tamaguiStyle React fast with 100% parity on React Native, an optional UI kit, and optimizing compiler.项目地址: https://gitcode.com/GitHub_Trending/ta/tamaguiTamagui 是一个面向 Web 与 React Native 的通用 React UI 框架核心价值在于让开发者用同一套styled()、设计 Token、主题与动画体系构建跨平台应用并通过优化编译器在构建期把静态样式抽取为 CSS同时保证双端 100% 的样式一致。本文基于仓库内的 plans/tamagui-skill/skills/tamagui/SKILL.md 编写系统讲解从获取项目专属配置、编写组件与主题到避免常见反模式、验证编译优化的完整实战路径。读完本文你将掌握 Tamagui 的核心 API 使用规范、12 级色阶约定、动画驱动选择、复合组件模式以及让编译器真正生效的代码写法。写代码前的第一步获取项目专属配置在动手写任何 Tamagui 代码之前务必先获取当前项目的真实配置而不是凭经验猜测 Token 名、主题名或断点名npx tamagui generate-prompt该命令会在项目根目录生成tamagui-prompt.md其中包含当前项目特有的设计 Tokenspace、size、radius、color、zIndex 等类别主题名称与层级结构可用的组件列表含复合组件如Dialog.Close媒体查询断点简写属性shorthand映射字体家族动画预设名称写作与编码时始终以该文件中的 Token/主题/断点名为准不要臆造或使用默认值。这条规则对 AI 协作场景尤其重要——它让生成代码与项目真实配置严格对齐。源码视角generate-prompt是怎么工作的该命令的实现在 code/core/cli/src/generate-prompt.ts其执行链路清晰可查先通过tamagui/static/loadTamagui重新加载配置设置TAMAGUI_KEEP_THEMES1按web平台解析读取.tamagui/tamagui.config.json若不存在会报错提示先执行tamagui generate调用generateMarkdown(config)生成 Markdown写入默认路径./tamagui-prompt.md也支持--output参数自定义输出位置。从生成逻辑可以反推文档内容的组织方式它按顺序输出Configuration Settings含 defaultFont、onlyAllowShorthands、themeClassNameOnRoot、platform 等→ Shorthand Properties → Themes三层级解析 组件主题→ Tokens → Media Queries → Fonts → Animations → Components。其中onlyAllowShorthands: true时文档会明确提示本项目必须使用简写属性否则会报错。命令注册在 code/core/cli/src/cli.ts 的generate-prompt子命令中同文件还提供generate、generate-css、generate-themes等配套命令。此外generate命令也会自动把 prompt 输出到.tamagui/prompt.md。核心概念styled()函数styled()是 Tamagui 的基础 API用于在既有组件之上扩展新组件import { View, Text, styled } from tamagui/core const Card styled(View, { padding: $4, // 使用 Token 时加 $ 前缀 backgroundColor: $background, borderRadius: $4, variants: { size: { small: { padding: $2 }, large: { padding: $6 }, }, elevated: { true: { boxShadow: 0 8px 24px $shadow4, }, }, } as const, // 必须加否则类型推断失效 defaultVariants: { size: small, }, }) // 使用 Card sizelarge elevated /关键规则variants 对象上必须使用as const保证 TS 能推断出变体类型Token 使用$前缀$4、$background、$color11Prop 顺序有影响——后面的 props 覆盖前面的对象内后定义的 variants 覆盖先定义的。从源码看styled()的核心实现在 code/core/web/src/styled.tsx它基于createComponent构建并通过mergeVariants见 code/core/web/src/helpers/mergeVariants.ts合并变体。同文件还导出了styledHtml()可直接为a、button、input等原生 HTML 标签生成带类型推断的样式组件例如a标签自动获得href类型并内置了color、display、width等与样式 props 冲突的原生属性的剔除处理。Stack 组件import { XStack, YStack, ZStack } from tamagui // XStack flexDirection: row // YStack flexDirection: column // ZStack position: relative 且子元素绝对定位 YStack gap$4 padding$4 XStack justifyContentspace-between alignItemscenter TextLabel/Text ButtonAction/Button /XStack /YStack主题系统主题按层级嵌套、组合import { Theme } from tamagui // 基础主题 Theme namedark {/* 子主题 */} Theme nameblue {/* 实际使用 dark_blue 主题 */} ButtonBlue button on dark/Button /Theme /Theme // 读取主题值 const theme useTheme() console.log(theme.background.val) // 实际颜色值 console.log(theme.color11.val) // 高对比度文本色useTheme()返回的每个主题值对象都带有.val属性取到的才是最终可用的颜色字符串。从源码结构看主题代理与响应式订阅逻辑位于 code/core/web/src/hooks/getThemeProxied.ts含schemeOptimized与DynamicColorIOS等平台优化分支说明useTheme()在原生端也会尽量做动态取色优化。12 级色阶约定$color1-4背景色由弱到强$color5-6边框、分隔线$color7-8hover/active 状态色$color9-10实心背景色$color11-12文本色低到高对比度响应式样式使用媒体查询 props具体断点名以你的tamagui-prompt.md为准YStack padding$4 $gtSm{{ padding: $6 }} // 以你的配置中的实际断点名为准 $gtMd{{ padding: $8 }} flexDirectioncolumn $gtLg{{ flexDirection: row }} / // 或使用 hook const media useMedia() if (media.gtMd) { // 在 medium 屏幕渲染 }useMedia()的实现在 code/core/web/src/hooks/useMedia.tsx它维护了断点状态的订阅机制与useThemeStateSubscribed采用同构的订阅模型确保断点变化时组件精准重渲染。动画import { AnimatePresence } from tamagui AnimatePresence {show ( YStack keymodal // 退出动画必须提供 key transitionquick enterStyle{{ opacity: 0, y: -20 }} exitStyle{{ opacity: 0, y: 20 }} opacity{1} y{0} / )} /AnimatePresence动画驱动driver一览tamagui/animations-css—— 仅 Web使用 CSS transitionstamagui/animations-react-native—— 原生AnimatedAPItamagui/animations-reanimated—— 原生性能最优tamagui/animations-motion—— 基于 spring 物理。CSS 驱动使用缓动字符串其余驱动支持 spring 物理参数。这一点在仓库配置中有直接佐证code/core/config/src/animations.css.ts 中的预设全部是字符串形式如bouncy: ease-in 200ms、quick: ease-in 100ms而 code/core/config/src/animations.tsreact-native 驱动则使用{ type: timing, duration: 100 }这样的对象配置。CSS 驱动底层解析code/core/animations-css/src/createAnimations.tsx 展示了驱动如何把transitionprop 规范化为统一格式normalizeTransition再按default/enter/exit三种动画状态取出对应预设getEffectiveAnimation最终拼出key duration delay形式的 CSS transition 字符串。值得注意的实现细节extractDuration会从ease-in 200ms、cubic-bezier(...) 400ms或slow 2s中解析时长未写时长时默认按 300ms 处理即便传入 spring 类型的配置CSS 驱动也会退化为使用默认 CSS transition 时长约 300ms这正是下文spring 动画配 CSS 驱动反模式的底层原因。复合组件Compound Components当多个子组件需要共享状态时使用createStyledContextimport { createStyledContext, styled, View, Text } from tamagui/core import { withStaticProperties } from tamagui/helpers const CardContext createStyledContext({ size: medium as small | medium | large }) const CardFrame styled(View, { context: CardContext, padding: $4, backgroundColor: $background, variants: { size: { small: { padding: $2 }, medium: { padding: $4 }, large: { padding: $6 }, }, } as const, }) const CardTitle styled(Text, { context: CardContext, // 继承父级 size fontWeight: bold, variants: { size: { small: { fontSize: $4 }, medium: { fontSize: $5 }, large: { fontSize: $6 }, }, } as const, }) export const Card withStaticProperties(CardFrame, { Title: CardTitle, }) // 用法 —— size 自动级联到子组件 Card sizelarge Card.TitleLarge Title/Card.Title /Card源码实现createStyledContext位于 code/core/web/src/helpers/createStyledContext.tsx。它基于 React Context 构建默认值通过mergeProps(defaultValues, values)合并同时支持namespace参数与scope机制——每个 scope 会惰性创建独立的 Context 实例getOrCreateScopedContext避免不同组件库之间的上下文命名冲突。注释中还特别说明了为何用use const而非函数声明防止 esbuild 在 SSR__esm惰性初始化阶段把函数声明提升到 React 初始化之前导致报错以及__disableMergeDefaultValues这一性能开关用于已合并过、需要保持顺序的场景。常见模式Dialog 配合 Adapt移动端自动降级为 Sheetimport { Dialog, Sheet, Adapt, Button } from tamagui Dialog Dialog.Trigger asChild ButtonOpen/Button /Dialog.Trigger Adapt whensm platformtouch Sheet modal dismissOnSnapToBottom Sheet.Frame padding$4 Adapt.Contents / /Sheet.Frame Sheet.Overlay / /Sheet /Adapt Dialog.Portal Dialog.Overlay keyoverlay transitionquick opacity{0.5} enterStyle{{ opacity: 0 }} exitStyle{{ opacity: 0 }} / Dialog.Content keycontent transitionquick enterStyle{{ opacity: 0, scale: 0.95 }} exitStyle{{ opacity: 0, scale: 0.95 }} Dialog.TitleTitle/Dialog.Title Dialog.DescriptionDescription/Dialog.Description Dialog.Close asChild ButtonClose/Button /Dialog.Close /Dialog.Content /Dialog.Portal /Dialog要点Adapt whensm platformtouch表示在sm断点以下且为触摸平台时Dialog 内容整体切换为底部 Sheet 呈现Adapt.Contents是内容占位Portal 内的 Overlay 与 Content 都要提供key以便退出动画生效。表单与 Input/Labelimport { Input, Label, YStack, XStack, Button } from tamagui YStack gap$4 padding$4 YStack gap$2 Label htmlForemailEmail/Label Input idemail placeholderemailexample.com autoCapitalizenone keyboardTypeemail-address / /YStack XStack gap$2 justifyContentflex-end Button variantoutlinedCancel/Button Button themeblueSubmit/Button /XStack /YStackthemeblue会为该按钮应用蓝色主题变体variantoutlined则切换按钮的描边样式变体。反模式清单Anti-Patterns❌animationprop不存在animationprop——这是最常被凭空发明的 prop。正确的 prop 是transition其值类型为TransitionProp一个已注册的动画名、一个对象或一个数组。CSS transition 字符串不属于TransitionProp。// bad - 不存在该 prop View animationquick / // bad - CSS 字符串不是 TransitionProp View transitionall 0.2s ease / // good - 使用 config 在 animations 下注册的名称 View transitionquick /只有当配置注册了多个动画驱动时才使用animatedBydriver指定驱动。❌ 认为现代样式 prop 仅限 WebbackdropFilter、mixBlendMode、boxShadow、filter、backgroundImage、transition、cursor、userSelect都是一等公民的强类型 propsReact Native 新架构New Architecture已原生实现。backdropFilter是真正的原生高斯背景模糊实现毛玻璃表面无需再引入额外的 blur view 包。把这类属性在 iOS 上视为无效是过时的假设。// bad - 旧版 RN 阴影分组把 web 与 native 割裂 View shadowColor$shadowColor shadowOffset{{ width: 0, height: 8 }} shadowRadius{10} / // good - 统一的 token 化写法双端一致 View boxShadow0 8px 24px $shadow4 /Tamagui 自身也在向这个方向演进通过一个配置项把border、outline、shadow的长属性从类型系统中移除统一收敛为合并后的border、outline、boxShadowprops因为简写与长属性混用会在 atomic CSS 的优先级上产生冲突。仓库中的简写映射如bxsh → boxShadow、bc → borderColor、br → borderRadius定义在 code/core/shorthands/src/index.ts可从侧面印证这套合并策略。❌ 硬编码数值而不是用 Token// bad View padding{16} backgroundColor#fff / // good - 使用设计 Token View padding$4 backgroundColor$background /❌ variants 缺少as const// bad - TypeScript 无法推断变体类型 variants: { size: { small: {...}, large: {...} } } // good variants: { size: { small: {...}, large: {...} } } as const❌ 在 styled() 里做平台检测// bad - 编译器无法抽取这类样式 const Box styled(View, { padding: Platform.OS web ? 10 : 20, }) // good - 使用平台修饰符 const Box styled(View, { padding: 20, $platform-web: { padding: 10 }, })❌ 没有 AnimatePresence 却使用 exitStyle// bad - 退出动画不会生效 {show View exitStyle{{ opacity: 0 }} /} // good AnimatePresence {show View keybox exitStyle{{ opacity: 0 }} /} /AnimatePresence❌ 动态值阻止编译器抽取// bad - 运行时变量阻止编译器抽取 const dynamicPadding isPremium ? $6 : $4 View padding{dynamicPadding} / // good - 内联三元表达式可被抽取 View padding{isPremium ? $6 : $4} /❌ 媒体查询顺序错误// bad - 基础值覆盖了响应式值 View $gtMd{{ padding: $8 }} padding$4 / // good - 先写基础值再写响应式覆盖 View padding$4 $gtMd{{ padding: $8 }} /❌ 用 CSS 驱动跑 spring 动画// bad - CSS 驱动不支持 spring 物理 import { createAnimations } from tamagui/animations-css const anims createAnimations({ bouncy: { type: spring, damping: 10 } // 不会生效 }) // good - CSS 驱动使用缓动字符串 const anims createAnimations({ bouncy: cubic-bezier(0.68, -0.55, 0.265, 1.55) 300ms })正如前文源码分析所示CSS 驱动会把 spring 配置退化为默认时长300ms的 CSS transition因此这类配置在视觉上达不到预期。编译器优化Tamagui 编译器会在构建期把静态样式抽取为 CSS。要让样式能被抽取使用 Token——$4可抽取16不一定能抽取使用内联三元表达式——padding{x ? $4 : $2}可抽取避免运行时变量——计算出来的值无法抽取优先使用 variants——比条件 props 更利于抽取。如何验证抽取是否生效开发模式下查找data-tamagui属性开启编译器后 bundle 体积应更小样式应以 CSS class 形式存在而非内联样式。如果抽取未生效通常意味着代码里存在上述反模式运行时变量、平台检测分支、硬编码数值等。这些规则与反模式清单互为印证编译优化的成败本质上取决于是否严格遵循Token 内联三元 variants 静态可分析的书写约定。TypeScript 类型import { GetProps, styled, View } from tamagui/core const MyComponent styled(View, { variants: { size: { small: {}, large: {} } } as const, }) // 提取组件 props 类型 type MyComponentProps GetPropstypeof MyComponent // 扩展自定义 props interface ExtendedProps extends MyComponentProps { onCustomEvent?: () void }仓库中styled的泛型设计code/core/web/src/styled.tsx保证当 variants 带as const时size等变体 props 能被精确推断为字面量联合类型GetProps才能提取出完整、可继承的 props 类型。此外还有styledHtml的 test-d 文件code/core/web/src/styledHtml.test-d.ts验证 HTML 元素 props 的类型行为。快速参考模式示例Tokenpadding$4主题值backgroundColor$background色阶color$color11高对比度文本响应式$gtSm{{ padding: $6 }}变体Button sizelarge variantoutlined /动画transitionquick enterStyle{{ opacity: 0 }}主题切换Theme namedarkTheme nameblue复合组件CardCard.Title配合createStyledContext更多资源仓库文档tamagui.dev站点源码位于 tamagui.dev其中 docs/plans 与 plans 目录包含动画缺陷排查如 plans/fix-motion.md、原生手势plans/native-gestures.md、Sheet 虚拟化列表适配plans/sheet-virtualized-list-adapter.md等深入话题若要在本地深入源码可参考 CONTRIBUTING.md 了解仓库结构code/core下按包划分web、animations-*、config、cli等并使用scripts/typecheck.sh、scripts/watch-ts.ts等辅助脚本本文涉及的实操命令npx tamagui generate-prompt的实现可查阅 code/core/cli/src/generate-prompt.ts 与其命令注册 code/core/cli/src/cli.ts。【免费下载链接】tamaguiStyle React fast with 100% parity on React Native, an optional UI kit, and optimizing compiler.项目地址: https://gitcode.com/GitHub_Trending/ta/tamagui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网