はじめに
クロスプラットフォームのモバイルアプリ開発では、「開発は1つのコードベースで済ませたいけれど、動きはネイティブと遜色ないものにしたい」という2つの要求が常に綱引きになります。React NativeはJavaScriptブリッジ越しにネイティブビューを操作し、Flutterは自前の描画エンジンで画面を丸ごと塗ります。どちらも優れた選択肢ですが、「本物のネイティブビューそのもの」を扱っているわけではありません。
Snapchatを開発するSnap社が公開したValdiは、この構図に別の答えを出したフレームワークです。TypeScriptで書いたUIコードを、iOS・Android・macOSそれぞれのネイティブビューへと直接コンパイルします。WebViewもブリッジも介さないため、ネイティブアプリと同じ土俵でパフォーマンスを出せるのが特徴です。しかもこれは新しい実験的技術ではなく、Snap社内の本番アプリで8年間使われてきた実績のある基盤がベータ公開されたものです。
この記事では、Valdiがどんな仕組みでネイティブ性能を実現しているのか、インストールから実際のコンポーネントの書き方、既存アプリへの組み込み方まで紹介します。
Valdiとは
Valdiは、TypeScriptとJSXライクな構文で書いたUIを、iOS・Android・macOSのネイティブビューに直接コンパイルするクロスプラットフォームUIフレームワークです。Snap社が自社の生産アプリ向けに開発し、社内で8年間運用してきたものをオープンソースとして公開しています(2026年8月時点でベータステータス)。ライセンスはMITです。
主な特徴
- 真のネイティブパフォーマンス - WebViewやJSブリッジを経由せず、TypeScriptのコンポーネントをネイティブビューに直接マッピングする。ビューの自動リサイクリングと、C++で実装された最適化済みレイアウトエンジンによりマーシャリングコストを抑えている
- 段階的な導入がしやすい - Valdi単体で新規アプリを作ることも、既存のUIKit/Androidビュー階層の一部にValdiコンポーネントを埋め込むことも、逆にネイティブビューを
<custom-view>としてValdi側に埋め込むこともできる - ポリグロットな高速化 - パフォーマンスが重要な処理はC++・Swift・Kotlin・Objective-Cで実装し、型安全なバインディングでTypeScript側から呼び出せる
- 開発体験への配慮 - ミリ秒単位のホットリロード、VSCode/Cursor拡張機能によるシンタックスハイライトとデバッグ、Flexboxレイアウト(自動RTL対応込み)を備える
- マルチスレッド実行 - ワーカースレッドでJavaScriptを並列実行でき、重い処理をメインスレッドから切り離せる
インストール
Valdiの開発にはCLIツールが必要です。macOSでネイティブアプリをビルドする場合はXcodeが前提条件になり、それ以外の依存関係はdev_setupコマンドが自動でインストールします。
# CLIをグローバルインストール
npm install -g @snap/valdi
# 必要な依存関係を一括セットアップ
valdi dev_setup
# 新規プロジェクトの作成
mkdir my_project && cd my_project
valdi bootstrap
# 対象プラットフォームのビルド環境を追加
valdi install ios
# または
valdi install android
Valdiはブラウザ上で完結するライブラリではなく、Xcode・Android StudioなどのネイティブSDKと連携して動作する開発ツールチェーンです。そのため本記事ではブラウザ上で試せる実行サンプルではなく、実際のプロジェクトで使うコードをそのまま掲載します。
基本的な使い方
ValdiのコンポーネントはComponentクラスを継承し、onRender()の中でJSXライクな構文を使ってビュー階層を宣言します。<view>や<label>といったタグはネイティブビューに対応しており、backgroundColorやpaddingといったプロパティがそのままネイティブの見た目に反映されます。
import { Component } from 'valdi_core/src/Component';
class HelloWorld extends Component {
onRender() {
const message = 'Hello Valdi! 👻';
<view backgroundColor="#FFFC00" padding={30}>
<label color="black" value={message} />
</view>;
}
}
このコードをビルドすると、iOSではUIView、AndroidではViewとして、それぞれのプラットフォーム標準のネイティブビューが生成されます。レイアウトはFlexboxベースなので、Web開発の経験があればプロパティの感覚はそのまま流用できます。
実践的なユースケース
既存アプリへのValdiの段階的な組み込み
すでに稼働しているネイティブアプリを丸ごとValdiに置き換えるのは現実的ではありません。Valdiは、既存のUIKit(iOS)やAndroidビュー階層の一部分だけをValdiコンポーネントに差し替える統合パターンを想定して設計されています。これにより、新規画面や新規機能から段階的にValdiを導入し、既存コードのリスクを抑えたまま移行できます。逆に、Valdi管理下の画面の中でどうしてもプラットフォーム固有のネイティブビュー(地図コンポーネントなど)を使いたい場合は、<custom-view>タグでネイティブビューをValdi側に埋め込むこともできます。
import { Component } from 'valdi_core/src/Component';
class ProfileScreen extends Component {
onRender() {
<view flexDirection="column" padding={16}>
<label value="プロフィール" fontSize={20} />
{/* 既存のネイティブビューをValdi配下に埋め込む例 */}
<custom-view nativeViewId="legacy-map-view" height={200} />
</view>;
}
}
nativeViewIdで指定したネイティブ側のビューをValdiのレイアウトツリーにそのまま組み込めるため、地図SDKやカメラプレビューのようにネイティブ実装が前提のコンポーネントも共存させられます。
Managed Contextによる状態共有
複数のコンポーネントをまたいで状態を共有したい場面は、モバイルアプリでも頻繁に起こります。ValdiではManaged Contextという仕組みを使い、ReactのContextに近い感覚で状態をコンポーネントツリーに配信できます。ローカルなUI状態はコンポーネント内で完結させ、画面をまたぐ状態だけをManaged Contextに載せる、という使い分けが基本です。
import { Component } from 'valdi_core/src/Component';
import { ManagedContext } from 'valdi_core/src/ManagedContext';
interface AppState {
userName: string;
}
const AppContext = new ManagedContext<AppState>({ userName: 'Guest' });
class WelcomeLabel extends Component {
onRender() {
const { userName } = AppContext.use(this);
<label value={`ようこそ、${userName}さん`} />;
}
}
AppContext.use(this)でコンテキストの値を購読し、値が更新されると購読しているコンポーネントだけが再描画されます。画面遷移をまたぐログイン状態やテーマ設定などは、この仕組みでアプリ全体に配信するのが定石です。
ワーカースレッドでの重い処理の切り離し
画像加工や大きなJSONのパースなど、時間のかかる処理をメインスレッドで実行すると、せっかくのネイティブ描画性能を活かせずスクロールがカクついてしまいます。Valdiはマルチスレッドでのスクリプト実行に対応しており、ワーカースレッドに処理を逃がすことでメインスレッドの応答性を保てます。
import { Component } from 'valdi_core/src/Component';
import { Worker } from 'valdi_core/src/Worker';
class ImageProcessor extends Component {
private worker = new Worker('workers/image-filter.ts');
async applyFilter(imageData: ArrayBuffer) {
// 重いフィルタ処理をワーカースレッドに委譲する
const result = await this.worker.run('applySepia', imageData);
this.setState({ processedImage: result });
}
}
メインスレッドはUIの描画と入力の受け付けに専念し、フィルタ処理のような重い計算はワーカー側で完結させます。ネイティブビューへの直接コンパイルと組み合わせることで、体感速度を大きく損なわずに重い処理を扱えるのがValdiの強みです。
まとめ
Valdiは、TypeScriptで書いたUIをiOS・Android・macOSのネイティブビューへ直接コンパイルするという、React NativeともFlutterとも異なるアプローチを取るクロスプラットフォームフレームワークです。Snap社内での8年間の運用実績を背景に持ちながらベータ公開されたばかりで、これから仕様やAPIが磨かれていく段階にあります。既存アプリへの段階的な統合、Managed Contextによる状態共有、ワーカースレッドでの処理分離など、実アプリで求められる要件に応えられる作りになっている点は注目に値します。
ネイティブアプリのパフォーマンスとTypeScriptによる開発効率の両立に課題を感じているなら、valdi dev_setupから気軽に触ってみる価値があるフレームワークです。