🏄‍♂️

React Native & ChatGPT モバイルアプリ開発(1)

2024/02/10に公開

はじめに

こんな記事を書いているのですが、
https://zenn.dev/nori/articles/5444433481f549
後からChatGPTやgitのログを見て思い出しながら書くのはしんどいなということで、こちらは作業しながら更新していこうかと思います。

実はFlutterで始める前に一度React Nativeで軽くトライしてみたのですが、カテゴリ一覧と板一覧まで表示させたところで、このまま突き進むとぐちゃぐちゃな構造になってしまうとお蔵入りにしていました。

Flutterでの開発を行ったことで、ChatGPTでのそれなりの構造のプログラムの書かせ方がわかってきたのでReact Nativeで再挑戦ということです。同じ機能のアプリを作ることで2つのフレームワークそれぞれの利点などが見えてくるのかもしれません。

ちなみに筆者のレベルは、Reactは少し触ったことがあるもののReact Nativeはまったく初めて、JavaScriptは石器時代から触れているものの好きになれず、TypeScriptはなにそれ美味しいの状態です。

業務ではPHPでWEBアプリを開発したりPythonでLambda関数を書いたり、本職というか心の友はRuby。

開発環境構築(Mac)

  • Homebrewインストール
  • brew install nodenv
  • nodenv install 20.11.0
    21.x.xだとアプリのビルド中に[DEP0040] DeprecationWarning: The punycode module is deprecated. Please use a userland alternative instead.という警告が出るので20.x.x系統を選択。
  • npm install -g react-native-cli
  • npm install -g react-native
  • npx react-native init ApplicationName

開発開始

一度失敗しているので最初の指示は悩みましたが結局のところFlutter版とほぼ変わらず。

  • React Nativeで5ちゃんねるというBBSを閲覧するためのスマホアプリを作成します。 https://menu.5ch.net/bbsmenu.json からJSONを読み込んで表示するApp.tsxを作成してください。JSONのエンコーディングはUTF-8です。
  • JSONにはmenu_listというプロパティがあり、オブジェクトの配列を持っています。そのオブジェクトは以下のような内容で、カテゴリ一覧となっているので、まずはカテゴリ名の一覧の表示を目指します。
{
  "category_number": "1",
  "category_content": [
    {
      "url": "https://headline.5ch.net/bbynamazu/",
      "category": 1,
      "category_name": "地震",
      "directory_name": "bbynamazu",
      "category_order": 1,
      "board_name": "地震headline"
    },
    {
      "category_name": "地震",
      "category": 1,
      "url": "https://egg.5ch.net/namazuplus/",
      "board_name": "地震速報",
      "category_order": 2,
      "directory_name": "namazuplus"
    },
(中略)
  ],
  "category_total": 5,
  "category_name": "地震"
}
  • menu_listが未解決の変数であると警告が出ます。
  • BBSに関するinterface群を別のファイルに分けて管理することはできますか?
  • カテゴリ一覧を2列で表示したいです。左->右->次の行の左->右という順番で。カテゴリ名の上下左右にグリッド線を引きたいです。ただし画面の左端と右端には不要です。このグリッド表示は今後作成する画面でも利用するのでコンポーネントとして再利用可能にしてください。
  • 現在のApp.tsxです。ここにGridItemを使用するコードをマージしてください。

たまにコピペし辛い書き方であったり何ヶ所か省略しながらコードが記述されるので、面倒なときはこうやってマージさせます。3時間あたり50回?の制限があるのであまり無駄遣いはしたくないのですが。

  • 左右のセルの間に1本だけグリッド線をひいてください。また上下のセルの間も1本のグリッド線としてください。セルの高さを現在の1/2としてください。

ここが上手く伝わりませんでした。左右のセル間、上下のセル間は1本にして欲しかったのですが、それぞれのセルにグリッド線が引かれて二重になってしまいました。なので詳細に指定。

  • セルの上のグリッド線は最初の行だけとしてください。セルの下のグリッド線はすべてのセルに引いてください。セルの左側のグリッド線は不要です。セルの右側のグリッド線は各行の左側のセルのみに引いてください。
  • 以下の2つの警告が出ています。修正してください。
ESLint: Inline style: { flex: 1 } (react-native/no-inline-styles)
ESLint: Inline style: { justifyContent: 'space-between' } (react-native/no-inline-styles)

そしてできたメインのコードがこちら。

// App.tsx

import React, { useState, useEffect } from 'react';
import { SafeAreaView, FlatList, StyleSheet } from 'react-native';
import GridItem from './GridItem'; // GridItemコンポーネントをインポート
import { CategoryItem, Category, BbsMenu } from './types';

const App = () => {
  const [categories, setCategories] = useState<Category[]>([]);

  useEffect(() => {
    fetch('https://menu.5ch.net/bbsmenu.json')
      .then(response => response.json())
      .then((data: BbsMenu) => {
        const categoryNames: Category[] = data.menu_list.map((item: CategoryItem) => ({
          id: item.category_number,
          name: item.category_name,
        }));
        setCategories(categoryNames);
      })
      .catch(error => console.error(error));
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={categories}
        renderItem={({ item, index }) => (
          <GridItem
            name={item.name}
            isFirstRow={index < 2} // 2列表示のため、最初の2アイテムが最初の行になる
            isLeftCell={index % 2 === 0} // 0と偶数インデックスが左側のセル
          />
        )}
        keyExtractor={item => item.id}
        // 2列で表示
        numColumns={2}
        // グリッドのアイテム間のスペースを調整するために必要
        columnWrapperStyle={styles.columnWrapper}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  columnWrapper: {
    justifyContent: 'space-between',
  },
});

export default App;

カテゴリ一覧が2列で表示できるところまで完了です。カテゴリ名の前にフォルダアイコンを表示しようとしたのですが、2種類の方法を試してエラーが解決できなかったので中断しました。

ここまでのコードです。
https://github.com/yasuda0320/SurfPlank/tree/day0001

続きです。
https://zenn.dev/nori/articles/80b47243021d04

Flutter版はこちら。
https://zenn.dev/nori/articles/5444433481f549

Discussion