はじめに
ReactとReact Nativeでコードを共有したい。この要望、開発者なら一度は持ったことがあるんじゃないでしょうか。
GitHubで13,500スター以上、月間ダウンロード約50万回。Tamaguiは「ユニバーサルUIキット&スタイルシステム」として、着実に存在感を増しています。
正直なところ、「WebとNativeでコード共有」って聞くと、どこかで妥協が必要なんだろうなと思っていたんですよ。でも実際に触ってみると、最適化コンパイラの存在がかなりデカい。パフォーマンスを犠牲にせずにクロスプラットフォーム開発ができる。これ、意外と他にないんですよね。
Tamaguiとは
Tamaguiは、ReactとReact Native間でコードを共有するためのUIライブラリスイートです。
「Universal UI kit and style system for React Native + Web, with an optimizing compiler」がキャッチコピー。要するに、WebとNative両方で動くUIキットで、しかも最適化コンパイラ付きという話。
3つのコアパッケージで構成されています:
- @tamagui/core: ユニバーサルスタイルライブラリ
- @tamagui/static: 最適化コンパイラ
- tamagui: UIキット本体
現在の最新バージョンはv1.142.0で、MITライセンスのオープンソース。231人以上のコントリビューターが開発に参加しており、TypeScriptで88%以上が書かれています。
特徴・メリット
1. 最適化コンパイラの威力
これ、Tamaguiの最大の特徴なんですよ。
ビルド時にスタイル付きコンポーネントを分析して、Webではシンプルなdiv + atomic CSS、NativeではView + hoisted style objectsに変換してくれる。
公式サイトによると、約89%のコンポーネントがシンプルなdivにフラット化され、Lighthouseスコアが約15%改善したとのこと。コンパイラを有効にするだけでこれだけ変わるのは大きい。
2. 真のクロスプラットフォーム対応
ViewとTextというプリミティブコンポーネントが、WebでもNativeでも一貫して動作します。
「Write once, deploy everywhere」を本気でやろうとしているライブラリは多いですけど、パフォーマンスを維持しながらそれを実現しているのがTamaguiの強み。
3. レスポンシブデザインが楽
メディアクエリのサポートが充実しています。インラインプロップス($gtSmなど)とuseMedia()フックの両方で対応可能。
CSSではなくJavaScriptでブレークポイントを管理する必要がないので、開発効率が上がります。
4. 段階的に導入できる
これ、個人的には重要なポイント。
設定なしでも動作するし、「100%のフィーチャーがランタイムとコンパイル時の両方で動作」するので、後からコンパイラをセットアップして最適化することもできる。既存プロジェクトへの導入ハードルが低い。
5. テーマシステムが優秀
useTheme()フックでフルに型付けされたデザイントークンにアクセスできます。WebでもNativeでも同じように動作するので、テーマ切り替えの実装がシンプル。
インストール方法
クイックスタート
一番簡単なのは公式テンプレートを使う方法。
npm create tamagui@latest
これでプロジェクトのセットアップが完了します。
既存プロジェクトへの導入
コアパッケージだけ使う場合:
yarn add @tamagui/core
UIキット全体を使う場合:
yarn add tamagui
推奨設定パッケージもインストール:
yarn add @tamagui/config
基本的な使い方
プロバイダーの設定
まずアプリのルートでTamaguiProviderをセットアップします。
import { TamaguiProvider, createTamagui } from '@tamagui/core'
import { defaultConfig } from '@tamagui/config/v4'
const config = createTamagui(defaultConfig)
export default function App() {
return (
<TamaguiProvider config={config}>
{/* アプリケーション */}
</TamaguiProvider>
)
}
デフォルト設定を使えばすぐに動く。カスタマイズは後からでもOK。
基本的なスタイリング
import { View, Text } from '@tamagui/core'
function MyComponent() {
return (
<View
padding="$4"
backgroundColor="$background"
borderRadius="$2"
>
<Text color="$color" fontSize="$5">
Hello Tamagui
</Text>
</View>
)
}
デザイントークン($4とか$backgroundとか)を使ってスタイリングする感覚は、Tailwindに近いかもしれない。
レスポンシブ対応
import { View } from '@tamagui/core'
function ResponsiveComponent() {
return (
<View
padding="$2"
$gtSm={{ padding: "$4" }}
$gtMd={{ padding: "$6" }}
>
{/* コンテンツ */}
</View>
)
}
$gtSmは「small以上」という意味。ブレークポイントごとにスタイルを上書きできます。
useMediaフックの活用
import { useMedia } from '@tamagui/core'
function AdaptiveComponent() {
const media = useMedia()
return (
<View>
{media.gtMd ? (
<DesktopLayout />
) : (
<MobileLayout />
)}
</View>
)
}
純粋にスタイリング目的でhooksを使っている場合、コンパイラがビルド時にhook呼び出しを削除してくれる。ランタイムオーバーヘッドがなくなるのは嬉しい。
テーマの利用
import { useTheme, View, Text } from '@tamagui/core'
function ThemedComponent() {
const theme = useTheme()
return (
<View backgroundColor={theme.background}>
<Text color={theme.color}>
テーマに対応したテキスト
</Text>
</View>
)
}
型が効くので、存在しないトークンを参照しようとするとエラーになる。TypeScriptとの相性がいい。
実践的なユースケース
カードコンポーネント
import { View, Text, XStack, YStack } from 'tamagui'
interface CardProps {
title: string
description: string
imageUrl?: string
}
function Card({ title, description }: CardProps) {
return (
<YStack
padding="$4"
backgroundColor="$background"
borderRadius="$4"
shadowColor="$shadowColor"
shadowRadius={10}
$gtSm={{ flexDirection: 'row' }}
>
<YStack flex={1} gap="$2">
<Text fontSize="$6" fontWeight="bold" color="$color">
{title}
</Text>
<Text fontSize="$4" color="$colorSubtle">
{description}
</Text>
</YStack>
</YStack>
)
}
YStackは縦並び、XStackは横並びのレイアウトコンポーネント。直感的で分かりやすい。
ボタンコンポーネント
import { Button } from 'tamagui'
function MyButtons() {
return (
<XStack gap="$2">
<Button size="$4" theme="active">
Primary
</Button>
<Button size="$4" theme="alt1">
Secondary
</Button>
<Button size="$4" disabled>
Disabled
</Button>
</XStack>
)
}
UIキット(tamaguiパッケージ)を使えば、Buttonなどのコンポーネントがすぐに使える。
フォーム入力
import { Input, Label, YStack } from 'tamagui'
import { useState } from 'react'
function ContactForm() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
return (
<YStack gap="$4" padding="$4">
<YStack>
<Label htmlFor="name">お名前</Label>
<Input
id="name"
value={name}
onChangeText={setName}
placeholder="山田太郎"
/>
</YStack>
<YStack>
<Label htmlFor="email">メールアドレス</Label>
<Input
id="email"
value={email}
onChangeText={setEmail}
placeholder="example@example.com"
inputMode="email"
/>
</YStack>
</YStack>
)
}
WebでもNativeでも同じ書き方でフォームが作れる。これがクロスプラットフォームの醍醐味。
他のライブラリとの比較
vs React Native for Web
React Native for Webは、React NativeのコードをそのままWebで動かすアプローチ。Tamaguiは最初からWeb/Native両対応で設計されていて、Webでは純粋なHTMLとCSSにコンパイルされる。パフォーマンス面ではTamaguiに分がある印象。
vs styled-components / Emotion
CSS-in-JSとしては同じカテゴリですが、Tamaguiはコンパイル時の最適化があるのが大きな違い。ランタイムでのスタイル生成を減らせるので、パフォーマンスが良い。
vs NativeWind
TailwindをReact Nativeで使うアプローチ。好みの問題もあるけど、TamaguiはUIキットまで含めた統合的なソリューション。どちらを選ぶかはプロジェクト次第。
まとめ
Tamaguiを試してみて感じたこと:
- クロスプラットフォーム: WebとNativeで本当にコード共有できる
- パフォーマンス: 最適化コンパイラの効果は実感できるレベル
- 開発体験: デザイントークン、レスポンシブ対応がスムーズ
- 段階的導入: 既存プロジェクトにも入れやすい設計
- TypeScript: 型サポートがしっかりしている
正直なところ、ReactとReact Nativeの両方を使うプロジェクトなら、一度試してみる価値はあると思います。特にスタイリングの重複に悩んでいる人には刺さるんじゃないでしょうか。
「WebとNative、どっちもやりたいけどコード二重管理は嫌だ」という人にとっては、かなり有力な選択肢。13,500スター以上の支持を集めているのも納得です。