카테고리 없음

react native - Expo 공부 4 새마음 새뜻으로 처음부터 시작

LAB 관리자 2024. 7. 9. 13:22
반응형

https://blog.expo.dev/the-new-expo-cli-f4250d8e3421

 

The New Expo CLI

In Expo SDK 46 we introduced a new “Local Expo CLI”, to replace the “Global Expo CLI” (npm i -g expo-cli). Unlike the global CLI, the local…

blog.expo.dev

계속 무시하다가, 이걸보고 무려 2년전에 expo가 꽤 대대적으로 바뀌었다는 사실을 알았다. 클로드는 이런 중요한걸 왜 안알려주는겨

 

처음부터 시작한다.

 

Expo로 앱만들기

 

1.  22년 8월 변경사항에 따라 npx 로 create expo app을 해준다

npx create-expo-app (내 앱의 이름)
cd (내 앱의 이름)
//근데 타입스크립트 형태로 돼서 다시 찾아봤다.
npx create-expo-app MyApp --template blank
//이걸로 바꿨다.

이건 마치 cra 같은 거다. 기본적으로 있어야하는 파일들을 자동생성해준다. create-expo-app@3.0.0 이걸 깔아야한다길래 난 조용히 y를 쳤다.

npx create-expo-app 내 앱 이름을 치면

폴더는 : app, assets, components, constants, hooks, node_modules, scripts 가 생기고

루트 디렉토리에 파일은 : app.json, babel.config.js, package-lock.json, package.json, tsconfig.json, README.md 가 생긴다.

 

그래서 --template blank를 추가해서 다시 시작하니 나랑 친한 js 프로젝트가 생성됐다 하이용~~

 

폴더 : assets, node_modules

루트 디렉토리 파일 : App.js, app.json, babel.config.js, package-lock.json, package.json 이렇다.

 

근데 expo start 를 치면 계속 에러가 뜬다.


ERROR

Error: EMFILE: too many open files, watch
//가 계속 뜬다.

https://josh-b.tistory.com/12

 

[react-native] expo Error: EMFILE: too many open files, watch 에러 해결 방법

expo를 React Native를 사용해서 react-native를 공부하고 있는데, expo start를 입력했을 때, 아래와 같은 오류가 발생했습니다. Error: EMFILE: too many open files, watch 위와 같은 에러가 발생 할 경우, 다음과 같

josh-b.tistory.com

 

brew update 를 하면된다는데, 이거 homebrew다. 홈브류는 맥OS용 패키지 관리자다. 

// 1. 홈브류 홈페이지에 갔더니 이걸 복사하래
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
// 2. 그래도 안되길래 claude한테 물었어
(echo; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"') >> /Users/jongwoo/.zprofile
//이걸로 Homebrew를 PATH에 추가한다
eval "$(/opt/homebrew/bin/brew shellenv)"
//현재 셀 세션에 Homebrew를 적용하기 위해 위의 명령어를 실행한다
source /Users/jongwoo/.zprofile
//위의 명령어를 실행해 변경사항을 적용한다

이렇게 했더니 귀신같이 brew update 명령어를 먹는다. claude 뭐냐?

티스토리에서 알려준대로 아래 명령어를 실행했다.

brew install watchman

됐다.


2.  npx expo start

하니까 드디어 된다. 이제 뭘해야되지? 파이어베이스 깔아야겠지?

근데 npx expo install firebase 하니까 또 legacy expo-cli가 node +17과 호환되지 않는단다. 

npm uninstall -g expo-cli //전역 expo-cli 제거
npm install expo // 로컬 expo 패키지가 설치된대.
npx expo install firebase //이렇게 하니까 되네 ㅁㅊ 왜되는거야 도대체

 

 

yarn add firebase 후에 

npx expo install firebase 하니까 됐다.

npx firebase-tools login

cd your-expo-project-directory

npx firebase-tools init

여기에 -tools 는 무슨의미일까?

 

여기서 그 으레 하는 RDS랑 HOSTING 스페이스바로 선택하고 싹싹 연결 설정하고 지나갔다.

 

database.rules.json

firebase.json

public 폴더랑 안에 index.html이 생겼다.

 

app.js에 아래 파일을 넣었다.

import React, { useState, useEffect } from "react";
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  FlatList,
  StyleSheet,
  SafeAreaView,
  KeyboardAvoidingView,
  Platform,
} from "react-native";
import { initializeApp } from "firebase/app";
import { getDatabase, ref, onValue, push, set, serverTimestamp } from "firebase/database";

// Firebase 설정
const firebaseConfig = {
  // Firebase 콘솔에서 가져온 설정을 여기에 넣으세요
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  databaseURL: "YOUR_DATABASE_URL",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
};

// Firebase 초기화
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);

const Post = ({ item }) => (
  <View style={styles.post}>
    <View style={styles.postHeader}>
      <Text style={styles.postNumber}>#{item.id}</Text>
    </View>
    <View style={styles.postContent}>
      <Text style={styles.storeName}>{item.storeName}</Text>
      <Text>{item.content}</Text>
    </View>
    <View style={styles.postFooter}>
      <Text style={styles.timestamp}>
        {new Date(item.timestamp).toLocaleString()}
      </Text>
    </View>
  </View>
);

const App = () => {
  const [posts, setPosts] = useState([]);
  const [storeName, setStoreName] = useState("");
  const [content, setContent] = useState("");

  useEffect(() => {
    const postsRef = ref(database, 'posts');
    const unsubscribe = onValue(postsRef, (snapshot) => {
      const data = snapshot.val();
      const postList = data
        ? Object.keys(data).map((key) => ({
            id: key,
            ...data[key],
          }))
        : [];
      setPosts(postList.reverse());
    });

    return () => unsubscribe();
  }, []);

  const addPost = async () => {
    if (storeName && content) {
      try {
        const newPostRef = push(ref(database, 'posts'));
        await set(newPostRef, {
          storeName,
          content,
          timestamp: serverTimestamp(),
        });
        setStoreName("");
        setContent("");
      } catch (error) {
        console.error("Error adding post: ", error);
      }
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerText}>식당 대신 전해드립니다</Text>
      </View>
      <FlatList
        data={posts}
        renderItem={({ item }) => <Post item={item} />}
        keyExtractor={(item) => item.id}
        style={styles.postList}
      />
      <KeyboardAvoidingView
        behavior={Platform.OS === "ios" ? "padding" : undefined}
        keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 0}
        style={styles.form}
      >
        <TextInput
          style={styles.input}
          placeholder="가게 이름"
          value={storeName}
          onChangeText={setStoreName}
        />
        <TextInput
          style={[styles.input, styles.contentInput]}
          placeholder="제보내용을 적어주세요"
          value={content}
          onChangeText={setContent}
          multiline
        />
        <TouchableOpacity style={styles.button} onPress={addPost}>
          <Text style={styles.buttonText}>글 작성</Text>
        </TouchableOpacity>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#f0f0f0",
  },
  header: {
    backgroundColor: "#4a4a4a",
    padding: 20,
    alignItems: "center",
  },
  headerText: {
    color: "white",
    fontSize: 20,
    fontWeight: "bold",
  },
  postList: {
    padding: 20,
  },
  post: {
    backgroundColor: "white",
    borderRadius: 8,
    padding: 15,
    marginBottom: 15,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 1,
    elevation: 2,
  },
  postHeader: {
    flexDirection: "row",
    justifyContent: "flex-end",
    marginBottom: 10,
  },
  postNumber: {
    fontSize: 14,
    color: "#777",
  },
  postContent: {
    marginBottom: 10,
  },
  storeName: {
    fontWeight: "bold",
    marginBottom: 5,
  },
  postFooter: {
    alignItems: "flex-end",
  },
  timestamp: {
    fontSize: 12,
    color: "#999",
  },
  form: {
    padding: 20,
    backgroundColor: "#f9f9f9",
    borderTopWidth: 1,
    borderTopColor: "#eee",
  },
  input: {
    backgroundColor: "white",
    borderRadius: 4,
    borderWidth: 1,
    borderColor: "#ddd",
    padding: 10,
    marginBottom: 10,
  },
  contentInput: {
    height: 100,
    textAlignVertical: "top",
  },
  button: {
    backgroundColor: "#4a4a4a",
    borderRadius: 4,
    padding: 15,
    alignItems: "center",
  },
  buttonText: {
    color: "white",
    fontWeight: "bold",
  },
});

export default App;

다시시작 - 이젠 된다!!!!!!!!!

 

아는 형님이 아주 중요한걸 알려줬다. 내가 expo-create-app 명령어를 쓰면서 루트 디렉토리를 만들었으면서, 파베랑 딴거 설치할 때는 루트 디렉토리 한꺼풀 바깥에서 작업하고있었다. 대체 왜그랬지? 그래서 형님이

 

보일러 플레이트 깃헙에서 별 많이 받은 거 보면서 디렉토리의 구조들과 정리방식과 호환방식을 먼저 보는 것이 코딩을 공부할 때 매우 중요하며 클로드 믿지말고 인터넷 강의 먼저 보라고 하면서 노마드 코더의 리액트 네이티브 강의를 추천해줬다.

 

그래서 난 code 폴더에 들어가서

1. npx create-expo-app (내앱이름) --template blank 로 js 기반 빈 프로젝트를 만들어준 후

2. npx expo install firebase -> npx firebase-tools login -> npx firebase-tools init 후 설정으로 파베 연동까지 했다.

3. 위의 코드를 붙여넣고, firebase config만 뭘로바꿨냐면, 파이어베이스 콘솔의 좌측 톱니바퀴(프로젝트 설정)-> 내앱->웹앱->sdk설정 및 구성에서 싹 복사해서 붙여넣기 했다. 끗!

 

okay 이제 해결해야 할 것은, 게시물 번호가 이상하게 나온다는 사실.

 

그리고 예상되는 문제점은, db에 누구나 접근할 수 있다는 점 - 사용량 폭발, 

import React, { useState, useEffect } from "react";
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  FlatList,
  StyleSheet,
  SafeAreaView,
  KeyboardAvoidingView,
  Platform,
} from "react-native";
import { initializeApp } from "firebase/app";
import { getDatabase, ref, onValue, push, set, serverTimestamp } from "firebase/database";

// Firebase 설정
const firebaseConfig = {
  apiKey:"-------------",
  authDomain: "-------------",
  databaseURL: "-------------",
  projectId: "-------------",
  storageBucket: "-------------",
  messagingSenderId: "-------------",
  appId: "-------------",
  measurementId: "-------------",
};

// Firebase 초기화
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);

const Post = ({ item }) => (
  <View style={styles.post}>
    <View style={styles.postHeader}>
      <Text style={styles.postNumber}>#{item.id}</Text>
    </View>
    <View style={styles.postContent}>
      <Text style={styles.storeName}>{item.storeName}</Text>
      <Text>{item.content}</Text>
    </View>
    <View style={styles.postFooter}>
      <Text style={styles.timestamp}>
        {new Date(item.timestamp).toLocaleString()}
      </Text>
    </View>
  </View>
);

const App = () => {
  const [posts, setPosts] = useState([]);
  const [id, setId] = useState("");
  const [storeName, setStoreName] = useState("");
  const [content, setContent] = useState("");

  useEffect(() => {
    const postsRef = ref(database, 'posts');
    const unsubscribe = onValue(postsRef, (snapshot) => {
      const data = snapshot.val();
      const postList = data
      ? Object.keys(data).map((key, index) => ({
        id: index + 1,  // 순차적인 번호 부여
        _key: key,      // Firebase key, 내부용
        ...data[key],
          }))
        : [];
      setPosts(postList.reverse());
    });

    return () => unsubscribe();
  }, []);

  const addPost = async () => {
    if (storeName && content) {
      try {
        const newPostRef = push(ref(database, 'posts'));
        const newId = posts.length > 0 ? posts[0].id + 1 : 1;
        await set(newPostRef, {
          id : newId,
          storeName,
          content,
          timestamp: serverTimestamp(),
        });
        setStoreName("");
        setContent("");
      } catch (error) {
        console.error("Error adding post: ", error);
      }
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerText}>식당 대신 전해드립니다</Text>
      </View>
      <FlatList
        data={posts}
        renderItem={({ item }) => <Post item={item} />}
        keyExtractor={(item) => item._key}
        style={styles.postList}
      />
      <KeyboardAvoidingView
        behavior={Platform.OS === "ios" ? "padding" : undefined}
        keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 0}
        style={styles.form}
      >
        <TextInput
          style={styles.input}
          placeholder="가게 이름"
          value={storeName}
          onChangeText={setStoreName}
        />
        <TextInput
          style={[styles.input, styles.contentInput]}
          placeholder="제보내용을 적어주세요"
          value={content}
          onChangeText={setContent}
          multiline
        />
        <TouchableOpacity style={styles.button} onPress={addPost}>
          <Text style={styles.buttonText}>글 작성</Text>
        </TouchableOpacity>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#f0f0f0",
  },
  header: {
    backgroundColor: "#4a4a4a",
    padding: 20,
    alignItems: "center",
  },
  headerText: {
    color: "white",
    fontSize: 20,
    fontWeight: "bold",
  },
  postList: {
    padding: 20,
  },
  post: {
    backgroundColor: "white",
    borderRadius: 8,
    padding: 15,
    marginBottom: 15,
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 1,
    elevation: 2,
  },
  postHeader: {
    flexDirection: "row",
    justifyContent: "flex-end",
    marginBottom: 10,
  },
  postNumber: {
    fontSize: 14,
    color: "#777",
  },
  postContent: {
    marginBottom: 10,
  },
  storeName: {
    fontWeight: "bold",
    marginBottom: 5,
  },
  postFooter: {
    alignItems: "flex-end",
  },
  timestamp: {
    fontSize: 12,
    color: "#999",
  },
  form: {
    padding: 20,
    backgroundColor: "#f9f9f9",
    borderTopWidth: 1,
    borderTopColor: "#eee",
  },
  input: {
    backgroundColor: "white",
    borderRadius: 4,
    borderWidth: 1,
    borderColor: "#ddd",
    padding: 10,
    marginBottom: 10,
  },
  contentInput: {
    height: 100,
    textAlignVertical: "top",
  },
  button: {
    backgroundColor: "#4a4a4a",
    borderRadius: 4,
    padding: 15,
    alignItems: "center",
  },
  buttonText: {
    color: "white",
    fontWeight: "bold",
  },
});

export default App;

 

성공했다. 여태까지 파일들을 깃헙에다 푸시만 해놓고 이젠 빌드하는 법 알아봐야지

 

고마워 클로드

고마워요 알려준 형님

 

이김에 하는 git cli 명령어 복습

 

깃 저장소 생성 : git init

 

 

깃 파일 추가 : git add -A

깃 상태 확인 : git status

깃 커밋 : got commit -m "커밋 메세지"

깃헙에 푸시 : git push origin main

깃 커밋 히스토리 : git log 탈출은 q

 

git remote add origin (깃헙 레포지토리 주소)

반응형