본문 바로가기
IT

React Native Expo (하이브리드앱)1 with TypeScript 프로젝트생성

by SOGNOD 2024. 11. 13.
반응형

React Native 하이브리드앱 개발

스터디하며 격어보았던 수많은 버전 이슈들을 뒤로하고 ㅠㅠ;;
정리하는 차원에서 다시 타이핑 합니다.

Expo with TypeScript

Expo는 스터디 및 개발 초기단계에 사용하고, 앱스토어에 올릴때는 제외할 예정 입니다.

요구조건:
Android targetSDK 34 지원 ... 이미 targetSDK 35 를 바라보아야 하는건 아닐지?
React Native Expo
TypeScript 
앱 실행시 2초간 인트로화면 보여짐
메인화면, 하단메뉴 네이티브 영역
하단메뉴에 [home] [mypage] 를 두고, mypage 버튼을 누르면 Webview 에 웹페이지 출력

 

1. Expo CLI 설치

설치하지 않았으면 ...

> npm install -g expo-cli

 

2. Expo 를 사용해 TypeScript 프로젝트 생성

> npx create-expo-app myApp --template
√ Choose a template: » Blank (TypeScript)

devDependencies 에 TypeScript 추가

> npm install --save-dev typescript @types/react @types/react-native

added 2 packages, and audited 1101 packages in 4s
132 packages are looking for funding
  run `npm fund` for details
3 moderate severity vulnerabilities

 

3. 추가 패키지 설치

웹 뷰용 react-native-webview 설치

> npx expo install react-native-webview
Installing 1 SDK 51.0.0 compatible native module using npm.
> npm install
added 2 packages, and audited 1103 packages in 3s
132 packages are looking for funding
  run `npm fund` for details
3 moderate severity vulnerabilities

 

4. react-native-screens 4.x 버전 재 설치

의존성 충돌 해결용

앞으로 사용할 @react-navigation/bottom-tabs는 react-native-screens 가 4.x 이상이 필요 합니다.

> npm install react-native-screens@latest
added 9 packages, and audited 1112 packages in 5s
132 packages are looking for funding
  run `npm fund` for details
3 moderate severity vulnerabilities

 

5. Footer 와 Navigation 용 추가 설치

위 react-native-screens 4.x 설치해야 의존성 출돌나지 않음

> npm install @react-navigation/native@latest @react-navigation/bottom-tabs@latest react-native-vector-icons@latest
added 23 packages, and audited 1135 packages in 9s
133 packages are looking for funding
  run `npm fund` for details
3 moderate severity vulnerabilities

위 설치는 의존성 출돌로 진행 했으며, 아래 추가 설치 진행

> npx expo install  react-native-safe-area-context react-native-gesture-handler react-native-reanimated 
› Installing 3 SDK 51.0.0 compatible native modules using npm
> npm install
up to date, audited 1135 packages in 2s
133 packages are looking for funding
  run `npm fund` for details
3 moderate severity vulnerabilities

 

6. Footer 스타일 및 아이콘 추가

Footer의 탭에 아이콘을 추가하려면 react-native-vector-icons를 설치합니다.

ChatGPT 패키지 설치 가이드
> expo install react-native-vector-icons

성공한 설치 가이드
> npm i --save-dev @types/react-native-vector-icons

 

7. 소스코드

지금 까지 설치한 모듈에 실행 가능한 소스코드... 한번 저장하고 갑니다...
추가 설치 후 의존성 충돌이 빈번해서 ㅠㅠ"

 /tsconfig.json

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true
  }
}

 

/package.json

{
  "name": "so_app",
  "version": "1.0.0",
  "main": "expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-navigation/bottom-tabs": "^7.0.1",
    "@react-navigation/native": "^7.0.0",
    "@react-navigation/stack": "^7.0.0",
    "expo": "~51.0.39",
    "expo-status-bar": "~1.12.1",
    "react": "18.2.0",
    "react-native": "0.74.5",
    "react-native-gesture-handler": "~2.16.1",
    "react-native-reanimated": "~3.10.1",
    "react-native-safe-area-context": "4.10.5",
    "react-native-screens": "^4.0.0",
    "react-native-vector-icons": "^10.2.0",
    "react-native-webview": "13.8.6"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@types/react": "~18.2.45",
    "@types/react-native": "^0.72.8",
    "@types/react-native-vector-icons": "^6.4.18",
    "typescript": "^5.6.3"
  },
  "private": true
}

 

/babel.config.js

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-reanimated/plugin'],
  };
};

 

/App.tsx

import App from './src/App';

export default App;

 

/src/App.tsx

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Intro from './screens/Intro';
import Main from './screens/Main';

const Stack = createStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        {/* 앱 실행 시 Intro를 먼저 표시 */}
        <Stack.Screen name="Intro" component={Intro} />
        {/* Intro 이후 Main으로 전환 */}
        <Stack.Screen name="Main" component={Main} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

 

Main.tsx에서 Footer(Tab Navigation) 구현

Main.tsx를 수정하여 Footer(Tab Navigation)와 함께 화면을 구성합니다. 여기서는 @react-navigation/bottom-tabs를 사용합니다.

/src/screens/Main.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Home from './Home';
import MyPage from './MyPage';

const Tab = createBottomTabNavigator();

const HomeScreen = () => (
  <View style={styles.screen}>
    <Text style={styles.text}>Home Screen</Text>
  </View>
);

const ProfileScreen = () => (
  <View style={styles.screen}>
    <Text style={styles.text}>Profile Screen</Text>
  </View>
);

export default function Main() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        headerShown: true, // 상단 헤더 숨김
        tabBarStyle: {
          backgroundColor: '#f8f8f8',
          borderTopWidth: 1,
          borderTopColor: '#ccc',
        },
        tabBarIcon: ({ color, size }) => {
          // 각 탭의 아이콘 설정
          let iconName: string;
          switch (route.name) {
            case 'Home':
              iconName = 'home-outline';
              break;
            case 'Profile':
              iconName = 'person-outline';
              break;
            default:
              iconName = 'help-circle-outline';
          }
          // You can return any component that you like here!
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#007aff', // 활성화된 탭 색상
        tabBarInactiveTintColor: '#8e8e93', // 비활성화된 탭 색상
      })}
    >
      {/* Footer에서 표시할 탭 화면 */}
      <Tab.Screen name="Home" component={Home} />
      <Tab.Screen name="Profile" component={MyPage} />
    </Tab.Navigator>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

 

/src/screens/Intro.tsx

import React, { useEffect } from 'react';
import { View, Image, StyleSheet } from 'react-native';

export default function Intro({ navigation }: { navigation: any }) {
  useEffect(() => {
    const timer = setTimeout(() => {
      navigation.replace('Main'); // 메인 화면으로 전환
    }, 2000); // 2초 후 전환

    return () => clearTimeout(timer); // 타이머 클린업
  }, [navigation]);

  return (
    <View style={styles.container}>
      <Image source={require('../assets/logo/logo.png')} style={styles.logo} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#ffffff', // 배경색 설정
  },
  logo: {
    width: 150, // 로고 너비
    height: 150, // 로고 높이
    resizeMode: 'contain', // 로고 크기 조정
  },
});

 

React Native WebView 웹페이지

웹뷰가 화면의 상단 네이티브 영역(상태 표시줄, 즉 시간, 배터리, 안테나 아이콘이 표시되는 영역)까지 겹쳐서 보이는 문제는 SafeAreaView 를 사용하여 해결

/src/screens/MyPage.tsx 

 
import React, { useState } from 'react';
import { View, StyleSheet, ActivityIndicator, Platform } from 'react-native';
import { WebView } from 'react-native-webview';
import { SafeAreaView } from 'react-native-safe-area-context';

const WebViewScreen: React.FC = () => {
  const [currentUrl, setCurrentUrl] = useState<string>('https://google.com');

  const handleNavigationStateChange = (event: any) => {
    if (Platform.OS === 'android' && event.url !== currentUrl) {
      setCurrentUrl(event.url);
    }
  };

  return (
    // 콘텐츠가 네이티브 상태 표시줄과 겹치지 않도록!
    <SafeAreaView style={styles.container}>
      <WebView
        source={{ uri: currentUrl }}
        onNavigationStateChange={handleNavigationStateChange}
        startInLoadingState={true}
        renderLoading={() => (
          <ActivityIndicator size="large" color="#007aff" style={styles.loader} />
        )}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#ffffff', // 배경색
  },
  loader: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default WebViewScreen;

/src/screen/Home.tsx

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function Home() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>This is the Home Screen.</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  text: { fontSize: 18 },
});

Intro 와 Index, Main 을 적절히 재 구성 해야 함...

반응형