AndroidからReact Nativeへの移行を検討中であれば、 Android開発者がReact Native開発を学ぶうえで必要となる発想の転換の糸口として、ぜひこのガイドをお役立てください。もちろん「新しい世界」の扉を開いたことはだれにも口外しませんので、どうぞご安心を 😉
AndroidからReact Nativeに移行する場合の習得プロセスは、それまでのAndroid開発経験に大きく左右されます。「従来」のView/XMLベースのAndroid開発を行う開発者と、Jetpack Composeの開発経験を持つ開発者とでは、習得の難易度が異なってきます。Composeの宣言型UIモデルに慣れている場合は、️React Nativeのコンポーネントベースのアプローチにも同様の概念が存在するため、取得しやすいはずです ☺️ 一方、XMLレイアウトと命令型のView操作を使用している場合は、より大きな発想の転換を迫られる可能性があります。
これまでの開発経験に関係なく、異なる点について概要を見ていきます。始める前に、React Nativeを学ぶにあたっての「前提条件」を確認しておきましょう。
Reactでユーザーインターフェイスを構築する場合、考え方はAndroid開発の場合とは異なります。Reactでは、まずUIをコンポーネントと呼ばれるパーツに分割します。次に、各コンポーネントにおけるさまざまな視覚的stateを記述します。最後に、複数のコンポーネントを接続して、コンポーネント間でデータが流れるようにします。
Androidの場合、選択したUIフレームワークによって、アーキテクチャのアプローチが異なります。 簡単に比べてみると、次のとおりです。
最も根本的な発想の転換が必要なのは、ReactにはAndroidと同様のコンポーネント分離がないことです。 Androidアプリは、以下の4つの主要コンポーネントとインテントを中心に構成されています。
Reactアプリでは、ほとんどの機能はUIコンポーネントとそのstate管理を中心に構築されており、バックグラウンド操作はさまざまなメカニズムを通じて処理されます。
考え方を関連付けるとすると、以下のようになります。
ここで、標準的なReactアプリの構成を確認し、アーキテクチャがアプリの一般的な構成要素にどう対応するかを見ていきましょう。
React Nativeにおいては、アプリはコンポーネントとそのインタラクションを中心に構築されるため、決まった構造はありません。その代わり、アプリのコンポーネントとデータフローをどう構築するのが最適かという判断は、開発者に委ねられています。つまり、コードをどう構成するかについても、開発者が意図的に決定する必要があるということです。
標準的なReact Nativeプロジェクトの構造は、以下のようになります。
my-app/
├── android/ # ネイティブのAndroidプロジェクトファイル
├── ios/ # ネイティブのiOSプロジェクトファイル
├── node_modules/ # NPMの依存関係(Gradleの依存関係に相当)
├── src/ # JavaScript/TypeScriptコード(Androidのapp/src/mainと同様)
│ ├── components/ # 再利用可能なUIコンポーネント
│ ├── screens/ # 画面コンポーネント
│ ├── navigation/ # ナビゲーション構成
│ ├── services/ # API呼び出し、ビジネスロジック
│ ├── hooks/ # カスタムフック
│ ├── context/ # ReactのContext定義
│ ├── utils/ # ヘルパー関数
│ └── assets/ # 画像、フォントなど(res/ディレクトリと同様)
├── App.js # ルートコンポーネント(Applicationクラスに相当)
├── index.js # エントリポイント(MainActivityと同様)
├── package.json # NPMの設定(build.gradleに相当)
├── metro.config.js # Metroバンドラーの設定(Gradleの設定と同様)
├── babel.config.js # Babelトランスパイラーの設定
└── tsconfig.json # TypeScriptの設定
AndroidとReact Nativeの基本的なアーキテクチャの概念をマッピングし、標準的なプロジェクト構造を確認できたので、次はReact Nativeアプリの中核となる構成要素を思考の枠組みでマッピングしてみましょう。
AndroidにおけるUIの構成要素は、従来型の開発の場合はView/ViewGroup、Jetpack Composeの場合はコンポーザブル関数となりますが、React Nativeにおいては、すべてがComponentとなります。
従来のAndroid開発は命令型で、UIを手動で操作しますが、React Nativeは宣言型であり、UIがどのように見えるかを記述します。
React Nativeでは、レイアウトにFlexbox(英語のみ)を使用します。Flexboxは以下の点で、Androidの従来のXMLレイアウトシステムとは異なります。
React NativeにはCSSに似た(英語のみ)スタイルシステムが採用されています。ウェブのCSSとネイティブモバイル開発の概念が組み合わせられているため、Android開発者にとっては異なりつつも親しみやすいものになっています。
React Nativeでは、XMLスタイルリソースやComposeの修飾子システムの代わりに、JavaScriptオブジェクトを使用してスタイルを設定します。その後、これらのスタイルをインラインで直接またはStyleSheet APIを使用してコンポーネントに適用します。
import { View, Text, StyleSheet } from "react-native";
function StyleExample() {
return (
// StyleSheetの例
<View style={styles.container}>
// インラインの例
<Text
style={{
fontSize: 16,
color: "blue",
}}
>
インラインスタイルの例
</Text>
</View>
);
}
// StyleSheetの定義
const styles = StyleSheet.create({
container: {
padding: 16,
},
});
// React Nativeでは以下は想定どおりに機能しません。
<View style={{ color: 'red' }}>
<Text>テキストは赤色で表示されません</Text>
</View>
// 各コンポーネントに明示的にスタイルを設定する必要があります。
<View style={{ backgroundColor: 'blue' }}>
<Text style={{ color: 'red' }}>テキストは赤色で表示されます</Text>
</View>
アーキテクチャと同様に、state管理に対するアプローチも、「従来」のAndroidの開発経験者とJetpack Composeの開発経験者とでは変わってきます。
props: props(プロパティ)は、親コンポーネントから子コンポーネントに渡される不変データです。propsはイミュータブルであるため、コンポーネントを再利用可能にすることができます。
Android開発では、propsは以下に相当します。
フック: Reactによって提供される関数で、コンポーネントからReactの機能を使用できるようにします。
Context: Contextを使用すると、各レベルでpropsを手動で渡すことなく、コンポーネントツリーを通じてデータを渡せるようになります。
stateは信頼できる唯一の情報源
UIをどう更新するかではなく、UIが特定のstateでどのように見えるかを考えましょう。 UIはstateを反映するものであり、その逆ではありません。
コンポーネントは純粋関数
同じpropsまたはstateが渡された場合、コンポーネントは常に同じようにレンダリングされる必要があります。副作用はuseEffectを使用して個別に処理されます。
更新はイミュータブルに
ビューをインプレースで変更するのではなく、更新された内容を表すstateを新しく作成します。
従来のAndroidでは、一連のライフサイクルコールバックを使用して、アクティビティのstateをそのライフサイクル全体にわたって管理していました。一方、React Nativeでは、コンポーネントレベルのuseEffectフックから「アクセス」できる、よりシンプルなマウント/アンマウントのライフサイクルモデルを使用します。 両者を比較すると、以下のようになります。
ナビゲーションは主に、「従来」のAndroidではインテントシステムとアクティビティスタック(英語のみ)、Jetpack ComposeではNavigationコンポーネントによって管理されます。React Nativeにおけるナビゲーションは、Androidとは以下の点で異なります。
AndroidとReact Nativeのナビゲーションは、基本的な概念は似ていますが、実装は異なります。思考の枠組みにおいて、両者は以下のように対応付けることができます。
// Androidのインテントに相当
function HomeScreen({ navigation }) {
return (
<Button
title="詳細を表示"
onPress={() => {
// インテントを指定したstartActivity()の代わり
navigation.navigate('Details', {
itemId: 86,
title: '商品の詳細',
});
}}
/>
);
}
// ルートパラメーターへのアクセス(インテントエクストラと同様)
function DetailsScreen({ route }) {
const { itemId, title } = route.params;
return (
<View>
<Text>{title}の詳細</Text>
<Text>Item ID:{itemId}</Text>
</View>
);
}
AndroidからReact Nativeに移行する際は、UIとビジネスロジックの関係において、大幅な発想の転換が必要となります。
Android開発の場合
React Native開発の場合
React Nativeでは、最初にバックエンドサービスを設計するのではなく、まずUIを作成して、その後に必要なロジックを追加していきます。この「UI主導」のアプローチは、コンポーネントを主要な構成要素とするReactのコンポーネントベースのアーキテクチャに由来しています。 プロジェクトが複雑化してくると、上記のプロジェクト構造の例のように、純粋なビジネスロジックをカスタムフック、コンテキスト、サービスに分離させる場合もあります。 React Nativeを使用していくうちに、これらの要素の適切なバランスが見つかり、独自のスタイルを確立できるはずです。
React Nativeでは、UIとビジネスロジックの関係の構築方法は変わりますが、バックエンドサービス(APIクライアント、データ解析、state同期)はAndroidと概念的に類似しているため、AndroidアプリとReact Nativeアプリのどちらにも対応可能です。React Nativeでは、通常以下のライブラリを使用します。(英語のみ)
決定的な違いは、必要なバックエンドサービスではなく、それらがコンポーネント構造とどう統合されるかにあります。多くの場合は、データ取得とstate管理をカプセル化するカスタムフックを作成し、このフックをコンポーネント内で直接使用します。
思考の枠組みの中でReact Nativeを馴染みのあるAndroid開発の概念と対応付けるにあたって、このガイドがお役に立てば幸いです。ただし、これは単なる第一歩に過ぎません。こうした比較は、ReactとAndroidの違いを理解するための出発点にはなりますが、より深い学びや実践の代わりにはなりません。 React Nativeの習得を目指して学習を続ける場合は、以下の重要なリソースを参照してください。(英語のみ)
それでは「新しい世界」でお会いできることを楽しみにしています 😉