📱

React Native ( TypeScript ) × microCMS でつくるニュースアプリ

2023/06/26に公開

作ったもの

Gif画像: 完成したニュースアプリ(iOS)

フロントエンドはReact Native(TypeScript)。

https://reactnative.dev/

記事の入稿とデータ提供のためのバックエンドとしてmicroCMSを使いました。

https://microcms.io/

上記の構成で作るニュースアプリの事例は見当たらなかったため、自分で作ってみました。

設定

package.json
package.json
{
  "name": "my-first-react-native-app",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-native-community/masked-view": "^0.1.11",
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/stack": "^6.3.16",
    "axios": "^1.4.0",
    "expo": "~48.0.18",
    "expo-status-bar": "~1.4.4",
    "react": "18.2.0",
    "react-native": "0.71.8",
    "react-native-elements": "^3.4.3",
    "react-native-gesture-handler": "~2.9.0",
    "react-native-reanimated": "~2.14.4",
    "react-native-safe-area-context": "4.5.0",
    "react-native-screens": "~3.20.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@types/react": "~18.0.14",
    "react-native-dotenv": "^3.4.9",
    "typescript": "^4.9.4"
  },
  "private": true
}

ExpoでReact Nativeの環境を構築する

Expoとは?

Web開発がメインのエンジニアにとってのReact Native開発の面倒な部分は、JavaScript以外のモバイルアプリ開発の知識も必要なところです。例えば、Android SDKやiOS SDKの機能を利用するためには、GradleやXcodeに関連するツールやビルドに関する知識が必要になります。

そこで、Expoというツールを用いることにしました。

https://expo.dev/

なぜExpo?

ExpoとはReact Nativeベースのモバイルアプリケーション開発フレームワークです。これがReact Nativeの面倒な部分を抽象化して、JavaScriptを用いたUI開発に注力できるようサポートしてくれます。

Expoは、JavaScriptライブラリの追加は簡単ですが、ネイティブで作られたライブラリはExpo内に存在するものしか使用できません。そういった制約がAndroid SDKやiOS SDKを使える開発者には煩わしいかもしれません。

React Native 公式チュートリアルでも採用されてます。

https://reactnative.dev/docs/environment-setup

ExpoでReact Nativeの環境構築

Expoのコマンドラインツールをインストール

npm install -g expo-cli

https://www.npmjs.com/package/expo-cli

プロジェクトを作成

expo init my-second-react-native-app

すでにグローバルにExpo CLIがインストールされていることを前提としています。

これでもいける
npx create-expo-app my-second-react-native-app

npxはnpmパッケージランナーで、ローカルにExpo CLIがインストールされていなくても直接実行することができます。このコマンドは一時的にExpo CLIをダウンロードして、my-second-react-native-appという名前の新しいExpoプロジェクトを作成します。

デバッグ

公式Expoアプリでデバッグする

https://apps.apple.com/jp/app/expo-go/id982107779

https://docs.expo.dev/get-started/expo-go/

例えば、iPhoneアプリ開発であれば、Xcodeでヒルドして。手元のiPhoneにデータを送って実機確認するが、Expoの場合、公式アプリでデバッグできます。

Expoアプリがブラウザのような役を果たし、開発PCにあるビルドサーバーからダウンロードしデバッグができます。

ビルドサーバーを立ち上げる。

yarn start
  • ターミナルからQRコードでる
  • iPhoneのカメラアプリでQR認識させてタップ
  • 公式Expoアプリ立ち上がる
  • ビルド始まる
  • 実機確認できた

こんな流れて立ち上がると思います。

ちなみに、実機をシェイクするとデバッグメニューが立ち上がります。

補足

開発環境を作る過程のメモとしてスクラップも残しています。

https://zenn.dev/ryosuketter/scraps/08eac2a806cc7e

アプリの階層構造

主なファイルとその階層です。

アプリの階層構造
- ルートディレクトリ (Root Directory)
  - App.tsx                  # アプリのエントリーポイント
  - node_modules/
  - src/                     
    - components/            
      - Home/
        - index.tsx          # ホーム画面 (一覧画面)
        - renderItem.tsx     # リストアイテム
      - Detail/
        - index.tsx          # 記事 (詳細画面)
    - lib/                   # ライブラリやAPI関連のファイルを格納
      - api.ts               # API関連のユーティリティ
    - Types/                 # アプリで使用される型定義が格納されるファイル

microCMSとの連携

microCMSの詳細な使い方は、調べてればいろいろと出てきますのでここでは最低限に書いてます。

microCMSを用意

リスト形式を選択

APIスキーマ定義

APIキーの取得

microCMSではリクエストにAPIキーを含める事で特定のデータを取得できます。

具体的なやり方は

https://document.microcms.io/manual/api-request

APIキーをenvファイルで保護しますが、React Nativeの場合どうやるか?

envファイルの読み込み

自分は、.envファイルとreact-native-dotenvパッケージを使用して環境変数を設定しました。

https://www.npmjs.com/package/react-native-dotenv

まずはインストール

yarn add -D react-native-dotenv

プロジェクトのルートにあるbabel.config.jsにreact-native-dotenvプラグインを追加する。

babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "module:react-native-dotenv",
        {
          moduleName: "@env", // @envという名前のモジュールを作成し、環境変数をそのモジュールから参照できるようにした
          path: ".env", // 環境変数が定義された.envファイルのパスを指定
        },
      ],
    ],
  };
};

これでReact Nativeのコード内で、dotenvを使用して環境変数を読み込むことができます。

例えば

import { MICROCMS_API_KEY, MICROCMS_API_BASE_URL } from "@env";

console.log(MICROCMS_API_KEY, MICROCMS_API_BASE_URL);

React NativeからmicroCMSにAPIリクエストしてデータ取得

Next.jsアプリ開発などで用いるmicrocms-js-sdkはFetch API等のネットワーク通信に関する処理を隠蔽できて、冗長になりがちな処理をスッキリ簡単に書くことができます。

便利なので使いたいのですが、今回は使えない(と思ってる)。

https://github.com/microcmsio/microcms-js-sdk

なぜなら、microcms-js-sdkはNode.jsやブラウザベースのJavaScriptアプリケーションでの使用を想定している(と思っているから)です。

React NativeはJavaScriptを使用してモバイルアプリケーションを開発するフレームワークであり、その実行環境はNode.jsではなく、モバイルデバイス(AndroidまたはiOS)です。なのでNode.js環境で提供されるAPIを使用することはできない(と思ってます)。

HTTPリクエストはReact Nativeでも利用できます。通常はfetch関数やaxiosライブラリなどを使って行います。今回はaxiosを使って、React NativeからmicroCMSにAPIリクエストしてデータを取得する処理を書きます。

https://axios-http.com/

microCMSからデータを取得するための関数
src/lib/api.ts
import axios, { AxiosInstance } from "axios";
import { MICROCMS_API_KEY, MICROCMS_SERVICE_DOMAIN } from "@env";

// axios.createを使用してAxiosのインスタンスを作成
const client: AxiosInstance = axios.create({
  baseURL: `https://${MICROCMS_SERVICE_DOMAIN}.microcms.io/api/v1`,
  headers: { "X-API-KEY": MICROCMS_API_KEY },
});

// 記事の一覧を取得
export const fetchPosts = async () => {
  try {
    const response = await client.get("/news");
    return response.data.contents;
  } catch (error: unknown) {
    console.error(error);
    throw error;
  }
};

// IDを使って1つの記事を取得
export const fetchPostById = async (id: string) => {
  try {
    const response = await client.get(`/news/${id}`);
    return response.data;
  } catch (error: unknown) {
    console.error(error);
    throw error;
  }
};

以下のようなことを行っています。

axios.createを使用してAxiosのインスタンスを作成しています。作成する際に、基本的なURLとヘッダー情報(APIキー)を設定しています。これらの情報は@envからインポートした環境変数を用いて設定しています。

fetchPosts関数はmicroCMSの/newsエンドポイントからデータを取得します。client.get("/news")で/newsエンドポイントにGETリクエストを送り、そのレスポンスからデータを取り出して返しています。

fetchPostById関数は指定したIDを持つポストを取得します。client.get(/news/${id})で/news/{id}エンドポイントにGETリクエストを送り、そのレスポンスからデータを取り出して返しています。

これらの関数は非同期(async)で定義されており、HTTPリクエストのレスポンスを待つためにPromiseを返します。これにより、呼び出し元で非同期処理を簡単に扱うことができます。また、最低限のエラーハンドリングも行っており、エラーが発生した場合にはコンソールにエラーを出力し、そのエラーを呼び出し元に投げています。

https://github.com/ryosuketter/my-first-react-native-app/commit/817c381c21520a7fabb0afd489eabf710808d912

画面遷移の実装

React Navigationで画面遷移を実装しました。

https://reactnavigation.org/

画面遷移に関わる記述や型定義をピックアップすると以下の通りです。

準備

React Navigationのコアライブラリをインストール

yarn add @react-navigation/native

React Navigationの依存ライブラリをインストール

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

1つの画面遷移の種類であるstackを提供するReact Navigationのライブラリをインストール

yarn add @react-navigation/stack

Expoを使用してReact Nativeアプリケーションを開発している場合、Expoは独自のビルドおよびパッケージングシステムを提供しています。Expoのビルドシステムと連携してパッケージを適切にインストールおよびリンクするため、依存関係の管理が簡単になります。また、Expoが提供するSDKやライブラリとの互換性も考慮されています。

自分は上記のようにインストールしたが一貫性を保つために全部expo installでもよかったかもしれない。

App.tsx

React Native アプリケーションのエントリーポイントのファイルです。

  • NavigationContainerとStackNavigatorのインポートと実装
    • React Navigationライブラリを使用して、アプリのナビゲーションスタックを定義します
  • RootStackParamList型を用いたcreateStackNavigatorの呼び出し
    • ここで定義されたスタックはアプリケーションの全ての画面遷移を制御します
  • NavigationContainerコンポーネントで画面遷移したいUIを囲む
コード
App.tsx
import "react-native-gesture-handler";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { RootStackParamList } from "./src/Types";

const Stack = createStackNavigator<RootStackParamList>();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="Detail" component={Detail} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

ちなみに1行目は React Native Gesture Handlerと言って、React Nativeでジェスチャーを実装できるライブラリで、React Navigation を使用する際に必須となります。

このライブラリはアプリ全体で一度だけインポートする必要があります。なので今回はApp.tsx といったルートレベルのファイルでインポートしました。これにより、全てのコンポーネントでこのライブラリが適用され、正しいジェスチャーハンドリングとナビゲーション動作が保証されるでしょう。

https://github.com/ryosuketter/my-first-react-native-app/commit/d56b7e0d53d4481b8726923bdec7e9ca9f7dccba

src/components/Home (一覧画面)

  • useNavigationフックを使用してnavigationオブジェクトを取得
    • これを通じて、遷移関数にアクセスできます
  • HomeScreenNavigationProp型を定義
    • これにより、Home画面で利用可能なナビゲーション機能をTypeScriptに明示します。
コード
src/components/Home
import { useNavigation } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../../Types";

type HomeScreenNavigationProp = StackNavigationProp<RootStackParamList, "Home">;

export const Home = () => {
  const navigation = useNavigation<HomeScreenNavigationProp>();
  ...
  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => (
        <RenderItem item={item} navigation={navigation} />
      )}
      keyExtractor={(item) => item.id}
      refreshing={refreshing}
      onRefresh={onRefresh}
    />
  );
};

// src/components/Home/renderItem.tsx
export const RenderItem = ({ item, navigation }: RenderItemProps) => (
  <ListItem
    key={item.id}
    bottomDivider
    onPress={() => navigation.navigate("Detail", { id: item.id })}
  >
    <ListItem.Content>
      <View style={styles.itemContainer}>
        <View>
          <Avatar source={{ uri: item.thumbnail.url }} size="large" />
        </View>
        <View style={styles.textContainer}>
          <ListItem.Title>{item.title}</ListItem.Title>
        </View>
      </View>
    </ListItem.Content>
  </ListItem>
);

RenderItemコンポーネントにnavigationオブジェクトをpropsとして渡しています。

ListItemコンポーネントにonPressプロップを通じて、リストアイテムがタップされるとnavigation.navigate("Detail", { id: item.id })が実行され、Detailという名前の画面に遷移します。

遷移する際、item.idをパラメータとして遷移先の画面に渡します。これにより、遷移先の画面で特定のアイテムの詳細情報を表示するためにこのIDを使用することができます。

src/components/Detail (詳細画面)

RoutePropを使ってrouteオブジェクトの型を定義。これにより、ナビゲーションから渡されるパラメータ(route.params)の型が明示されます。

コード
src/components/Detail
import { RouteProp } from "@react-navigation/native";
import { RootStackParamList } from "../../Types";

type PostScreenRouteProp = RouteProp<RootStackParamList, "Detail">;

interface PostScreenProps {
  route: PostScreenRouteProp;
}

export const Detail = ({ route }: PostScreenProps) => {
  ...
};

src/Types

アプリケーション全体で使用される画面遷移の型を定義。

コード
src/Types
export type RootStackParamList = {
  Home: undefined;
  Detail: { id: string };
};

React Navigationでは、各画面に遷移する際にパラメータを指定することが可能です。RootStackParamListは、各画面に遷移する際にどのようなパラメータが必要なのかを定義するためのものです。

Home: undefinedという記述は、Home画面に遷移する際に特別なパラメータを必要としないことを意味しています。

これで画面遷移を確認できると思います。

装飾のためのスタイル

コンポーネント同士の位置関係を調整して配置し、コンポーネント自身を加工できます。コンポーネントのstyle属性に所定のパラメータを持ったオブジェクトを渡すことで、見た目を加工できます。

React Nativeでは、主に2つの方法でスタイルを適用します。

  • インラインスタイル
  • StyleSheetオブジェクト

インラインスタイル

<Image
  source={{ uri: post.thumbnail.url }}
  style={{ width: "100%", height: 200 }}
/>

StyleSheetオブジェクト

const styles = StyleSheet.create({
  titleWrapper: {
    marginBottom: 20,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
});

// 使用例
<View style={styles.loadingContainer}>

ここでは、StyleSheet.createメソッドを使用して、スタイルシートオブジェクトを作成しています。このオブジェクトには複数のスタイルを定義でき、それらのスタイルは全体で再利用可能です。Viewコンポーネントでは、作成したスタイルシートオブジェクトからloadingContainerスタイルを適用しています。

いろいろ書きましたが、CSSが経験がある方であれば、すぐ慣れます。

僕はまだ慣れてません。

レイアウトのためのスタイル

React Nativeのレイアウト機能は、Facebookの「Yoga」エンジンを用い、コンポーネントの座標とサイズを決定します。YogaはFlexboxモデルを基にしたプロパティをサポートし、複数のコンポーネントが混在する画面内で、それぞれの座標とサイズを調整します。

https://yogalayout.com/

レイアウトが期待通りにならないときは、スタイルで必要な情報を記述し忘れていないか確認するといいかもしれません。

Webとの違いも少なからずあるので、React Native のスタイリングで困ったら、以下の記事を見ようと思います。

https://zenn.dev/cureapp/articles/d182e76ca1ea1c

上記の記事を見ると、React Native では必ず 1 番親のコンポーネント(今回ならApp.tsx)で flex: 1 が書いてあり、こうすることで、画面の幅と高さ目一杯に要素が引き伸ばされます(下に続く)。

App.tsx
App.tsx
import "react-native-gesture-handler";
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { Home } from "./src/components/Home";
import { Detail } from "./src/components/Detail";
import { RootStackParamList } from "./src/Types";

const Stack = createStackNavigator<RootStackParamList>();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="Detail" component={Detail} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

ですが、今回はその設定は書かれていません。

今回は<NavigationContainer><Stack.Navigator>が自動的に画面全体を埋めるスタイルを持っているので、明示的にflex: 1を設定していません。

https://github.com/react-navigation/react-navigation

これは<NavigationContainer><Stack.Navigator>などの特定のコンポーネントに依存した挙動ですので、他のコンポーネントを親コンポーネントとする場合にはflex: 1の設定が必要となるケースもあります。

UIライブラリ

特にこだわりがなかったので、この記事を見てReact Native Elementsにしました。

https://qiita.com/tossi_sz/items/f01d673c2b63de1bc911#結論

https://reactnativeelements.com/

一覧画面のUI作成

src/components/Home (一覧画面)

コード
src/components/Home/index.tsx
import React, { useEffect, useState, useCallback } from "react";
import { useNavigation } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
import { FlatList, Alert } from "react-native";
import { RenderItem } from "./renderItem";
import { Post, RootStackParamList } from "../../Types";
import { fetchPosts } from "../../lib/api";

type HomeScreenNavigationProp = StackNavigationProp<RootStackParamList, "Home">;

export const Home = () => {
  // Post型の配列であるpostsと、その更新関数setPostsを定義
  const [posts, setPosts] = useState<Post[]>([]);
  // リフレッシュ中かどうかを示すrefreshingという状態と、その更新関数を定義
  const [refreshing, setRefreshing] = useState(false);
  const navigation = useNavigation<HomeScreenNavigationProp>();

  // 非同期関数で、APIからデータを取得し、取得したデータをpostsステートにセットする関数を定義
  const fetchAndSetPosts = async () => {
    try {
      const fetchedPosts = await fetchPosts();
      setPosts(fetchedPosts);
    } catch (error) {
      Alert.alert(
        "エラー",
        (error as Error)?.message || "記事の取得に失敗しました"
      );
    }
  };

  // コンポーネントの初回レンダリング時に、fetchAndSetPosts関数を呼び出す
  useEffect(() => {
    fetchAndSetPosts();
  }, []);

  // コンポーネントのマウント時(初回レンダリング時)に、fetchAndSetPosts関数を呼び出す
  // リフレッシュ開始時にはrefreshingステートをtrueに、完了したらfalseにする
  const onRefresh = useCallback(() => {
    setRefreshing(true);
    fetchAndSetPosts().then(() => setRefreshing(false));
  }, []);

  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => (
        <RenderItem item={item} navigation={navigation} />
      )}
      keyExtractor={(item) => item.id}
      refreshing={refreshing}
      onRefresh={onRefresh}
    />
  );
};

returnの中は、FlatListコンポーネントを使って、記事データをリスト表示しています。各リスト項目のレンダリングはRenderItemコンポーネントが担当

src/components/Home/renderItem

コード
src/components/Home/renderItem.tsx
import React from "react";
import { ListItem, Avatar } from "react-native-elements";
import { View, StyleSheet } from "react-native";
import { StackNavigationProp } from "@react-navigation/stack";
import { Post, RootStackParamList } from "../../Types";

type HomeScreenNavigationProp = StackNavigationProp<RootStackParamList, "Home">;

type RenderItemProps = {
  item: Post;
  navigation: HomeScreenNavigationProp;
};

// ListItemコンポーネントを使用して、一つ一つのリストアイテムを表示
// 各リストアイテムは一意のkeyを持ち、ユーザーがアイテムをタップしたときにDetail画面に遷移

export const RenderItem = ({ item, navigation }: RenderItemProps) => (
  <ListItem
    key={item.id}
    bottomDivider
    onPress={() => navigation.navigate("Detail", { id: item.id })}
  >
    <ListItem.Content>
      <View style={styles.itemContainer}>
        <View>
          <Avatar source={{ uri: item.thumbnail.url }} size="large" />
        </View>
        <View style={styles.textContainer}>
          <ListItem.Title>{item.title}</ListItem.Title>
        </View>
      </View>
    </ListItem.Content>
  </ListItem>
);

const styles = StyleSheet.create({
  itemContainer: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "flex-start",
    paddingEnd: 50,
  },
  textContainer: {
    marginLeft: 10,
  },
});
  • Imageコンポーネントを使用して、各記事のサムネイルを表示
  • ListItem.Titleコンポーネントを使用して、各記事のタイトルを表示

https://github.com/ryosuketter/my-first-react-native-app/commit/c7e42ed0562cd6bf72430d9d7234e3eb3d7ab32a

詳細画面のUI作成

src/components/Detail (詳細画面)

コード
src/components/Detail/index.tsx
import React, { useEffect, useState } from "react";
import {
  View,
  ScrollView,
  StyleSheet,
  Alert,
  ActivityIndicator,
  Image,
} from "react-native";
import { RouteProp } from "@react-navigation/native";
import { Text } from "react-native-elements";
import { Post, RootStackParamList } from "../../Types";
import { fetchPostById } from "../../lib/api";

type PostScreenRouteProp = RouteProp<RootStackParamList, "Detail">;

interface PostScreenProps {
  route: PostScreenRouteProp;
}

export const Detail = ({ route }: PostScreenProps) => {
  const [post, setPost] = useState<Post | null>(null);

  // コンポーネントがマウントされた時にAPIから投稿の詳細を取得
  // 成功した場合はstateに設定
  // エラーが発生した場合はアラートを表示
  useEffect(() => {
    const fetchAndSetPost = async () => {
      try {
        const fetchedPost = await fetchPostById(route.params.id);
        setPost(fetchedPost);
      } catch (error) {
        Alert.alert(
          "エラー",
          (error as Error)?.message || "記事の詳細の取得に失敗しました"
        );
      }
    };
    fetchAndSetPost();
  // 第二引数として空の配列[]ではなく[route.params.id]を渡していますので
  // route.params.idが変更された時(つまり新しい投稿の詳細画面に遷移した時)にのみ
  // このAPIリクエストが再度行われます
  }, [route.params.id]);

  // APIからデータを取得中の間、スピナー(ActivityIndicator)を表示
  if (!post) {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  // 投稿の詳細を表示
  return (
    <ScrollView>
      <Image
        source={{ uri: post.thumbnail.url }}
        style={{ width: "100%", height: 200 }}
      />
      <View style={styles.titleWrapper}>
        <Text h2>{post.title}</Text>
      </View>
      <Text>{post.body}</Text>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  titleWrapper: {
    marginBottom: 20,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
});

https://github.com/ryosuketter/my-first-react-native-app/commit/7344d6848b7b0aba0ef0335e546c57d6a8f5a910

ここまでの内容でmicroCMSから得た内容がReact Native上のアプリで表示できたと思います。

React Nativeの感想

<div>の代わりに<View>を使うなど、いくつかの特有のルールがありますが、慣れればWeb開発の感覚で開発できそうかな、と思えました。

ところでなぜ React Native?

"React Nativeの採用理由とFlutterの比較"

chot Inc.では以前からReactを用いたWebフロントエンド開発を行っていたため、自分以外に開発を任せる際にも、メンバーの経験を活かせると考えてReact Nativeを導入しました。

Flutterも考えましたが、私含め経験のないDart言語を学ぶ必要があるため、その学習コストを考慮するとReact Nativeの方が向いていると判断しました。

他には、Shopifyが自社の大規模なモバイルアプリをReact Nativeに移行した記事(2022/12)を見かけたり

https://shopify.engineering/migrating-our-largest-mobile-app-to-react-native

npm trendsを見てもダウロード数はされていそうなので使ってみることにしました。

https://npmtrends.com/react-native

React Native 製のアプリ一覧なども参考にしました。

https://reactnative.dev/showcase

さいごに

記事内に誤った内容が含まれているかもしれません。訂正すべき点などございましたら、遠慮なくお知らせいただけますと幸いです。ご意見やご指摘は、貴重なフィードバックとなります。

microCMSのTwitterにてご紹介いただきました

https://twitter.com/micro_cms/status/1673205174830268416?s=20

ありがとうございます。

Github

https://github.com/ryosuketter/my-first-react-native-app

chot Inc. tech blog

Discussion