はじめに
「ReactとCSSの知識があれば、iOS・Android・Webアプリが同時に作れる」という話を聞くと、React Nativeを思い浮かべる人が多いと思う。個人的にもそうだった。でも最近、Lynxというフレームワークを触ってみて、これがなかなか面白い。
LynxはGitHubで13,800以上のスターを獲得している、オープンソースのクロスプラットフォーム開発フレームワークです。「Write Once, Render Anywhere」というコンセプトで、一度書いたコードをiOS、Android、Webでネイティブレンダリングできる。
正直なところ、クロスプラットフォームフレームワークは山ほどある中で、何が違うのか気になって調べてみた。結論から言うと、Webの知識をそのまま活かせる設計と、マルチスレッドエンジンによるパフォーマンスの良さが特徴です。
特徴・メリット
Web開発者にとっての学習コストの低さ
これが一番のポイントだと思う。LynxはCSSとReactの知識をそのまま活用できる設計になっている。つまり、普段Webアプリを作っている人なら、新しいスタイリング言語や独自のコンポーネント設計を覚える必要がない。
React Nativeの場合、StyleSheetオブジェクトを使った独自のスタイリングが必要だったりする。Lynxではより標準的なCSSに近い書き方ができるので、Web開発者にとってはストレスが少ない。
マルチスレッドエンジンによる高パフォーマンス
公式サイトでは「instant launch and silky UI responsiveness」と謳っている。マルチスレッドエンジンを採用していて、UIのレンダリングとJavaScriptの処理が分離されている。
これ、意外と重要な話で、JavaScriptが重い処理をしている間もUIがカクつかないということ。モバイルアプリでスクロールが引っかかるとユーザー体験が一気に悪くなるので、この点は評価できる。
ネイティブレンダリング
WebViewでHTMLを表示するのではなく、各プラットフォームのネイティブUIコンポーネントを使ってレンダリングする。だからパフォーマンスが良いし、プラットフォームのルック&フィールに合ったUIが実現できる。
クロスプラットフォーム対応
対応プラットフォームは以下の通り。
- iOS(iOS 10以上)
- Android(API 21以上、Android 5.0以上)
- HarmonyOS
- Web
HarmonyOSに対応しているのは珍しい。中国市場を視野に入れているプロジェクトには嬉しいポイントかもしれない。
Apache 2.0ライセンス
オープンソースで、Apache 2.0ライセンス。商用利用も問題ない。2,549以上のコミットがあり、活発に開発が続けられている。
インストール方法
前提条件
- Node.js 18以上(TypeScript設定を使う場合は18.19以上)
- macOSが推奨(Windows、Linuxは完全には検証されていない)
- iOS開発にはXcode
- Android開発にはAndroid Studio
プロジェクトの作成
新規プロジェクトはcreate-rspeedyを使って作成する。
npm create rspeedy@latest
プロンプトに答えていくと、必要なファイルが自動的に生成される。
# プロジェクト名を入力
? Project name: my-lynx-app
# テンプレートを選択
? Select a template: react
開発環境のセットアップ
cd my-lynx-app
npm install
Lynx Explorerのインストール
開発中のアプリを実機やシミュレーターで確認するために、Lynx Explorerというアプリが必要になる。公式からiOS、Android、HarmonyOS向けのビルド済みバイナリが提供されている。
iOSシミュレーターの場合は、Xcodeをインストールした上で、プリビルト版をダウンロードしてシミュレーターにドラッグするだけで導入できる。
開発サーバーの起動
npm run dev
開発サーバーが起動すると、QRコードが表示される。Lynx ExplorerでこのQRコードをスキャンすると、アプリが読み込まれる。ホットリロードにも対応しているので、コードを変更すると自動的に反映される。
基本的な使い方
コンポーネントの作成
Lynxでは、Reactの知識をそのまま活かしてコンポーネントを作成できる。
import { View, Text } from '@lynx-js/react';
const HelloWorld = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello, Lynx!</Text>
<Text style={styles.description}>
ReactとCSSでネイティブアプリを作ろう
</Text>
</View>
);
};
const styles = {
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
description: {
fontSize: 16,
color: '#666',
marginTop: 8,
},
};
export default HelloWorld;
イベントハンドリング
タップイベントなども普通に書ける。
import { View, Text } from '@lynx-js/react';
import { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const handlePress = () => {
setCount(prev => prev + 1);
};
return (
<View style={styles.container}>
<Text style={styles.count}>{count}</Text>
<View style={styles.button} onTap={handlePress}>
<Text style={styles.buttonText}>カウントアップ</Text>
</View>
</View>
);
};
const styles = {
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
count: {
fontSize: 48,
fontWeight: 'bold',
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
marginTop: 16,
},
buttonText: {
color: '#fff',
fontSize: 16,
},
};
export default Counter;
レイアウト
FlexboxベースのレイアウトがCSSライクに書ける。
const GridLayout = () => {
return (
<View style={styles.grid}>
<View style={styles.item}>
<Text>アイテム1</Text>
</View>
<View style={styles.item}>
<Text>アイテム2</Text>
</View>
<View style={styles.item}>
<Text>アイテム3</Text>
</View>
<View style={styles.item}>
<Text>アイテム4</Text>
</View>
</View>
);
};
const styles = {
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
padding: 16,
},
item: {
width: '50%',
padding: 8,
boxSizing: 'border-box',
},
};
実践的なユースケース
プロダクトギャラリー
公式チュートリアルでも紹介されているが、ECサイトの商品一覧のようなギャラリーUIを作るケース。
import { View, Text, Image, ScrollView } from '@lynx-js/react';
interface Product {
id: string;
name: string;
price: number;
imageUrl: string;
}
const ProductGallery = ({ products }: { products: Product[] }) => {
return (
<ScrollView style={styles.container}>
<View style={styles.grid}>
{products.map(product => (
<View key={product.id} style={styles.card}>
<Image
source={{ uri: product.imageUrl }}
style={styles.image}
/>
<Text style={styles.name}>{product.name}</Text>
<Text style={styles.price}>
¥{product.price.toLocaleString()}
</Text>
</View>
))}
</View>
</ScrollView>
);
};
const styles = {
container: {
flex: 1,
},
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
padding: 8,
},
card: {
width: '50%',
padding: 8,
},
image: {
width: '100%',
aspectRatio: 1,
borderRadius: 8,
},
name: {
fontSize: 14,
marginTop: 8,
},
price: {
fontSize: 16,
fontWeight: 'bold',
color: '#e74c3c',
},
};
カルーセル付き商品詳細ページ
商品詳細で画像のカルーセルを実装するパターン。
import { View, Text, Image, ScrollView } from '@lynx-js/react';
import { useState } from 'react';
interface ProductDetail {
id: string;
name: string;
price: number;
images: string[];
description: string;
}
const ProductDetailPage = ({ product }: { product: ProductDetail }) => {
const [currentIndex, setCurrentIndex] = useState(0);
return (
<ScrollView style={styles.container}>
{/* 画像カルーセル */}
<ScrollView
horizontal
pagingEnabled
onScroll={(e) => {
const index = Math.round(
e.nativeEvent.contentOffset.x / e.nativeEvent.layoutMeasurement.width
);
setCurrentIndex(index);
}}
>
{product.images.map((image, index) => (
<Image
key={index}
source={{ uri: image }}
style={styles.carouselImage}
/>
))}
</ScrollView>
{/* インジケーター */}
<View style={styles.indicators}>
{product.images.map((_, index) => (
<View
key={index}
style={[
styles.dot,
index === currentIndex && styles.activeDot
]}
/>
))}
</View>
{/* 商品情報 */}
<View style={styles.info}>
<Text style={styles.name}>{product.name}</Text>
<Text style={styles.price}>
¥{product.price.toLocaleString()}
</Text>
<Text style={styles.description}>{product.description}</Text>
</View>
</ScrollView>
);
};
const styles = {
container: {
flex: 1,
backgroundColor: '#fff',
},
carouselImage: {
width: '100vw',
height: 300,
},
indicators: {
flexDirection: 'row',
justifyContent: 'center',
paddingVertical: 12,
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#ccc',
marginHorizontal: 4,
},
activeDot: {
backgroundColor: '#333',
},
info: {
padding: 16,
},
name: {
fontSize: 20,
fontWeight: 'bold',
},
price: {
fontSize: 24,
color: '#e74c3c',
marginTop: 8,
},
description: {
fontSize: 14,
color: '#666',
marginTop: 16,
lineHeight: 24,
},
};
社内ツールのクロスプラットフォーム化
既存のWebアプリをモバイル対応させたいケースにも向いている。Webの知識がそのまま使えるので、Webエンジニアのチームでもモバイルアプリ開発に参入しやすい。
まとめ
Lynxは、Web開発者がその知識を活かしてネイティブアプリを開発できるクロスプラットフォームフレームワークです。
- ReactとCSSの知識をそのまま活用できる
- マルチスレッドエンジンで高いパフォーマンスを実現
- iOS、Android、HarmonyOS、Webに対応
- Apache 2.0ライセンスで商用利用可能
- GitHubで13,800以上のスター、活発な開発
30代になって思うのは、新しいフレームワークを覚えるコストって馬鹿にならないということ。その点、Lynxは既存のWeb知識を活かせるので、学習コスト的にはかなり優れていると思う。
まだmacOSでの開発が推奨されている段階ではあるが、React Nativeに不満を感じている人や、FlutterのDart学習に二の足を踏んでいる人は、一度試してみる価値があるんじゃないかと思います。
