Next.js入門①~初めでつまづく~

Next.jsを始めるとすると、用語がたくさん出てきます。

ここでは、用語を挙げるにとどめます(自分も説明できるほど理解できていない)

React経験者とそうでない場合に分けたいと思います。

[非React経験者編]

React、JSX、コンポーネント、ルーティング、状態(state)、React Hooks、ライフサイクル、副作用、Redux、redux tool kit、

[React経験者編]

App Router、Pages Router、SSR、CSR、SSG、Edge Runtime、TypeScript、ESLint、Tailwind

と挙げればキリがない。

 

ここでは、覚書きのような書き方になるので、もう少し構造的なHP見たい方は下記参照。

JavaScriptがわからなくても大丈夫!Next.jsのやさしい入門ガイド

 

ここでは、Next.js14で、Pages Routerでやっていきます。(上のホームページも、Next.jsのバージョンは不明だが多分そうやっている)

npx create-next app [プロジェクト名] のコマンドを使用します。

Pages Routerのときと、App Routerの時では、ディレクトリ構造が異なりますので留意が必要です。ページはpagesディレクトリ以下に作成し、その他のファイルはpagesと同レベルに、componentsディレクトリを作成し、その中に入れていきます。

調べていくと、React.jsはライブラリで、その延長上にNext.jsというフレームワークがあるというのは正しいようで誤りであるのかもしれません。

 

Next.jsで、React.jsっぽく書くことはできますが、Next.jsではそれ以外の選択肢も複数あります。まずは、React.jsっぽく書く、すなわち、前提条件を作ってReact.jsと同じかきかたで書いていくこととします。(この時点で自分でも半分意味が分かっていません)

 

SSRは使用せず(SSR用のメソッドを使用しない)、API Routeは使用せず(今は知らないからどうせ使えない)、React.js同様レンダリングでのデータ取得はCSRでとるということにすれば(また、fetchやaxiosはuseEffect内に記載)、React.jsと同じように扱えるし、ビルドの時も少し設定を変えるだけでビルドできる。

 

プログラミングしたプロジェクトを動かして見たいというのはだれしも思うものである。firebase hostingは割合なじみがあるのでそれでやってみることとする。

https://blog.hatena.ne.jp/yuuzaki2022/yuuzaki2022.hatenablog.com/edit?entry=4207112889967093862

 

 

 

expo-sqliteが新しくなっていて使いやすいので使ってみる

下記HPを参照にします。

React NativeからローカルのSQLiteデータベースを操作する方法を確認 | アールエフェクト

 

① 以前のものと比べてどうなっているかを、共通点と相違点を押えながら、簡単に書いてみます。

・openDatabaseメソッドではなく、openDatabaseAsyncメソッドに変更されてます

。引数の数と階層の深さが圧倒的に減ってます。

・executeSQLメソッドが、execAsync メソッドに変更されてます。

` `の中に、SQL分を記載することができるし、データベースに入れたい値を、プレースホルダーを使用することなく、JSXの特性を生かして、INTEGER(数値)なら単に、  ${ intValue }でよく、テキストは、'${valueText}'と記載することで使える。

 

② さて、これを踏まえて、用語とその説明を記入するテキストボックスがあって、

その下の『確定』ボタンを押すと、データベースに保存され、下記のリストへ表示されるようにする。

 

App.js

import { StatusBar } from "expo-status-bar";
import { useState } from "react";
import { useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Button,
  TextInput,
  FlatList,
  ScrollView,
} from "react-native";
import * as SQLite from "expo-sqlite";

export default function App() {
  const [data, setData] = useState();
  const [title, setTitle] = useState("");
  const [explanation, setExplanation] = useState("");
  const [itemNumber, setItemNumber] = useState(0);

  useEffect(() => {
    async function insertData() {
      try {
        const db = await SQLite.openDatabaseAsync("terms.db");
        await db.execAsync(`
          CREATE TABLE IF NOT EXISTS term ( id INTEGER PRIMARY KEY NOT NULL, title TEXT NOT NULL, explanation INTEGER);
          INSERT INTO term (title, explanation) VALUES ('FPGA','ユーザーが自由に設計して使うLSIの一つで、プログラムの記憶要素としてSRAMを使用しているもの。');
        `);
        const results = await db.getAllAsync(`
          SELECT * FROM term;
        `);
        console.log("results", results);
        setData(results);
      } catch (error) {
        console.log("Error", error);
      }
    }
    insertData();
  }, );

  const renderItem = ({ item }) => {
    return (
      <View>
        <Text>{item.title}</Text>
        <Text>{item.explanation}</Text>
      </View>
    );
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.upperContainer}>
        <Text>タイトル:</Text>
        <TextInput
          style={styles.titleBox}
          multiline={true}
          numberOfLines={4}
          placeholder="文字入力"
          value={title}
          onChangeText={(txt) => setTitle(txt)}
        />
        <Text>説明:</Text>
        <TextInput
          style={styles.titleBox}
          multiline={true}
          numberOfLines={4}
          placeholder="文字入力"
          value={explanation}
          onChangeText={(txt) => setExplanation(txt)}
        />
      </View>
      <Button
        title="確定"
        onPress={() => {
          async function insertData() {
            try {
              const db = await SQLite.openDatabaseAsync("terms.db");
              await db.execAsync(`
                      CREATE TABLE IF NOT EXISTS term ( id INTEGER PRIMARY KEY NOT NULL, title TEXT NOT NULL, explanation INTEGER);
                      INSERT INTO term (title, explanation) VALUES ('${title}','${explanation}');
                    `);
              const results = await db.getAllAsync(`
                      SELECT * FROM term;
                    `);
              console.log("results", results);
              setData(results);
            } catch (error) {
              console.log("Error", error);
            }
          }
          insertData();
        }}
      />
      <Button title="データベースに追加" />
      <Text>素数</Text>
      <FlatList
        style={styles.flatlist}
        data={data}
        renderItem={renderItem}
        keyExtractor={(item) => item.id.toString()}
      />
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
  upperContainer: {
    marginTop: 50,
  },
  flatlist: {
    marginTop: 50,
    width: "100%",
  },
});

 

ただし、これでは、下記のエラーが出ます。

 VirtualizedLists should never be nested inside plain ScrollViews with the same orientation because it can break windowing and other functionality - use another VirtualizedList-backed container instead.

どういったエラーかというと、ScrollViewにFlatListを入れたらダメというエラーです。

次はこれについて考えていきます。

ググるといろいろな対処法が出てきます。

上のコードで、ScrollViewをViewに変更するとどうなるかですが、上の部分は固定で、

リストの一覧表示のところだけスクロールできるようになります。

 

 

【React Native(Expo)】 知らないと意外とアプリ作成に手間取るアイコン利用の仕方

はじめに


 アイコンをつかうのは、アプリ作成では必須です。これはライブラリを使ってみます。

 

前提


"expo": "~51.0.28",
    "expo-status-bar": "~1.12.1",
    "react": "18.2.0",
    "react-native": "0.74.5",
    "react-native-vector-icons": "^10.2.0"

要約


これを、react-native-vector-iconsライブラリで実現します。といっても、

react-native-vector-iconsライブラリの使用方法は割合ネットにあります。

ここでは、アイコンを置く位置の決め方と、イベント処理の追加をやってみます。

 

本文


プロジェクト内に、Fontsディレクトリを作成し、そこに、node-modulesのreact-native-vector-icons内の、AntDesign.ttfをコピーしたものをペーストする。

そして、冒頭に下記を記載する。

import Icon from 'react-native-vector-icons/AntDesign';

これで、Iconコンポーネントが使用できるようになる。

App.js

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import React, { useState } from 'react';
import Icon from 'react-native-vector-icons/AntDesign';

export default function App() {
  const [showText, setShowText] = useState("");

  const pluscircleHandler = () => {
    setShowText("追加")

  }
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <Text>{showText}</Text>
      <Icon name='leftcircleo' size={50} style={{position: 'absolute',top:10, left:10}}/>
      <TouchableOpacity onPress={pluscircleHandler}  style={{position: 'absolute', bottom:10, right:10}}>
        <Icon name='pluscircleo' size={50}/>
      </TouchableOpacity>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    position: 'relative',
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

 

containerスタイルに、position: 'relative' を入れておいて、

その子コンポーネントに、postion: 'absolute' 、top:100、left: 100 

を追加すればよい。

 

後で知ったのですが、Iconコンポーネントに、onPress属性が使えるようです。

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import React, { useState } from 'react';
import Icon from 'react-native-vector-icons/AntDesign';

export default function App() {
  const [showText, setShowText] = useState("");

  const pluscircleHandler = () => {
    setShowText("追加")

  }
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <Text>{showText}</Text>
      <Icon name='leftcircleo' size={50} style={{position: 'absolute',top:10, left:10}}/>
        <Icon
        name='pluscircleo'
        size={50}
        onPress={pluscircleHandler}  
        style={{position: 'absolute', bottom:10, right:10}}/>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    position: 'relative',
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

総論


アイコンが使えるようになると、アプリ作成に便利ですね。

【React Native(Expo)】 端末の向きを変更した際に、自動回転させる

はじめに


 端末の向きを変更した際に、自動回転させたいと思い、React  Nativeでどのようにこれを実現するかということを調べてみました。

 

前提


主要なライブラリのバージョンは下記のようになってます。 

 "expo": "~51.0.28",
    "react": "18.2.0",
    "react-native": "0.74.5"

要約


端末(スマホなど)の向きを変更した際に、アプリ(今回はボタンが2つあるだけ)が自動回転するようにしてます。

ここではライブラリは使用せず、Dimensionsを使用して作成してます。

 

本文


やり方ですが、

①まず、app.json内の、"orientaion"のところを、unkown(あるいはundefined)にします。

 

② 下記のように、DimensionsのaddEventListenerメソッドを使用して実現します。

App.js

import { StatusBar } from "expo-status-bar";
import React, { useState, useEffect } from "react";
import { StyleSheet, Text, View, Button, Dimensions } from "react-native";

export default function App() {
  const [dimensions, setDimensions] = useState({
    window: Dimensions.get("window"),
    screen: Dimensions.get("screen"),
  });

  const [windowSize, setWindowSize] = useState({
    width: Dimensions.get("window").width,
    height: Dimensions.get("window").height,
  });

  useEffect(() => {
    const subscription = Dimensions.addEventListener(
      "change",
      ({ window, screen, height, width }) => {
        setDimensions({ window, screen });
        setWindowSize({ width, height });
      }
    );
    return () => subscription.remove();
  });

  return (
    <View style={styles.container}>
      <View>
        <Button title="ボタン1" />
        <Button title="ボタン2" />
      </View>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

総論


DimensionsのaddEventListenerメソッドを利用してますが、書き方が少しややこしい感じがしますが、端末の向きを検知して、縦横の表示を変えるなら、これが正解でしょう。

react-native-orientaion-lockerなどのライブラリを使用する方法もありますが、ここでは記載しておりません。

基本情報技術者試験の聞き流し動画のようなアプリを作る

『First Item』を押すと、

まあ、これは置いておいて、

『聞き流し』ボタンを押すと、用語と用語の説明を、聞き流しできる。

 

App.js

import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { StyleSheet } from "react-native";
import ListHomeScreen from "./screen/ListHomeScreen";
import DetailScreen from "./screen/DetailScreen";

const Stack = createStackNavigator();

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

 

ListHomeScreen.js

import React from "react";
import {
  View,
  Text,
  Button,
  FlatList,
  TouchableOpacity,
  Alert,
  StyleSheet,
} from "react-native";
import useStore from "../store";
import * as Speech from "expo-speech";
import { DATA } from "../data/dataSet";

export default function ListHomeScreen({ navigation }) {
  const { id, setId, title, setTitle, content, setContent } = useStore();

  const consoleText = () => {
    for (let i = 0; i < DATA.length; i++) {
      Speech.speak(DATA[i].title);
      Speech.speak(DATA[i].content);
    }
  };

  const renderItem = ({ item }) => {
    const itemHandler = () => {
      setId(item.id);
      setTitle(item.title);
      setContent(item.content);
      navigation.navigate("Detail", {
        id: id,
        title: title,
        content: content,
      });
      /*
      Alert.alert(item.title, "タップされました", [
        {
          text: "OK",
          onPress: () => {
            navigation.navigate("Detail", {
              id: id,
              title: title,
              content: content,
            });
          },
        },
        { text: "Cancel" },
      ]);
      */
    };

    return (
      <TouchableOpacity onPress={itemHandler}>
        <View>
          <Text style={styels.item_title}>{item.title}</Text>
        </View>
      </TouchableOpacity>
    );
  };

  return (
    <View>
      <FlatList data={DATA} renderItem={renderItem} />
      <Button title="聞き流し" onPress={consoleText} />
    </View>
  );
}

const styels = StyleSheet.create({
  container: {},
  list: {},
  item: {},
  item_title: {
    fontSize: 20,
  },
});

 

DetailScreen.js

import React from "react";
import { View, Text, StyleSheet } from "react-native";
import useStore from "../store";

export default function DetailScreen() {
  const { id, title, content } = useStore();

  return (
    <View>
      <Text style={styles.standardFontSize}>{id}</Text>
      <Text style={styles.standardFontSize}>{title}</Text>
      <Text style={styles.standardFontSize}>{content}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {},
  standardFontSize: {
    fontSize: 20,
  },
});

 

dataSet.js

import React from "react";

export const DATA = [
  {
    id: 1,
    title: "First Item",
    content: "First Itemでは、Javaプログラム",
  },
  {
    id: 2,
    title: "Second Item",
    content: "Second Itemでは、Pythonプログラム",
  },
  {
    id: 3,
    title: "Third Item",
    content: "Third Itemでは、JavaScriptプログラム",
  },
];

 

store.js

import { create } from "zustand";

const useStore = create*1,
  title: "",
  setTitle: (value) => set*2,
  content: "",
  setContent: (value) => set*3,
}));

export default useStore;

 

これを作るうえで、はまってしまったところがありました。

React Navigationで、ページ遷移の際に、遷移先ページに『値』をオブジェクトリテラルで送ろうとしたら、どうしてもうまくいきませんでした。

リストのアイテムを押したら、Alertモーダルでは、押したアイテムを正しく表示されるのに、いざ、ページ遷移すると、1つ前の『値』が送られ、遷移先ページでは、1つ前の値が表示される。

useStateでは、stateが次のレンダーで更新されるようです。

qiita.com

qiita.com

これらややこしいので、zustandを使用して、自分が使いやすいように、

set関数も作成し、値も、どのコンポーネントからもとってこれるようにしてます。

 

上の問題の対処法としては、どうやら、別の変数に一度代入しておくという方法があるようです。今度試してみます。

*1:set) => ({

  id: 0,
  setId: (value) => set((state) => ({ id: value }

*2:state) => ({ title: value }

*3:state) => ({ content: value }

Math.random()の留意点

0~10の整数をランダムに出力したい場合、

 

const random_Number = Math.floor(Math.random( ) * 11) ;

 

なぜ、11を掛けるかは下記HP参照

 

【JavaScript入門】乱数の作り方(範囲指定/重複なし/ランダム文字列) | 侍エンジニアブログ (sejuku.net)

AsyncStorageへの保存の復習+『配列と、配列のコードの文字列との変換』

react vavigationを使ってます。

 

この辺は、完全に忘れてましたが、復習しました。

改めて、Amazon Q And Codewispererは、こういうときにかなり便利。

 

 

 

App.js

import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
import { createStackNavigator } from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";
import HomeScreen from "./screen/HomeScreen";
import NextScreen from "./screen/NextScreen";

const Stack = createStackNavigator();

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

HomeScreen.js

import React, { useEffect } from "react";
import { View, Text, Button } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";

export default function HomeScreen({ navigation }) {
  const array = ["banana", "apple", "orange"];
  const arrayText = array.join('","');
  const arrayTextConnect = '["' + arrayText + '"]';
  const insertData = async (value) => {
    try {
      await AsyncStorage.setItem("@array_data", value);
    } catch (error) {
      console.log(error);
    }
  };

  useEffect*1;
      const start = value.indexOf("[") + 1;
      console.log(value.indexOf("]"));
      const end = value.indexOf("]");
      console.log(value.substring(start, end));
      const text = value.substring(start, end);
      setArray(text.split(","));
    }
  };

  useEffect(() => {
    setUpData();
  }, []);

  const renderItem = ({ item }) => {
    return <Text>{item}</Text>;
  };

  return (
    <View>
      <Text>{data}</Text>
      <FlatList data={array} renderItem={renderItem} />
    </View>
  );
}

 

*1:) => {

    insertData(arrayTextConnect);
  }, );

  return (
    <View>
      <Text>{arrayTextConnect}</Text>
      <Button title="PUSH" onPress={() => navigation.navigate("Next")} />
    </View>
  );
}

NextScreen.js

import React, { useState, useEffect } from "react";
import { View, Text, Button, FlatList } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";

export default function NextScreen() {
  const [data, setData] = useState("");
  const [array, setArray] = useState();

  const setUpData = async () => {
    const value = await AsyncStorage.getItem("@array_data");
    if (value != null) {
      setData(value);
      console.log(value);
      console.log(value.indexOf("["