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 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 을 적절히 재 구성 해야 함...