지난 글과 동일하게 타입스트립트 리액트 네이티브를 세팅한다.
npx react-native init weather --template react-native-template-typescript
페이지를 나눌 것이므로 먼저 폴더 세팅부터 해준다.
루트 위치에 src를 만들고, 그 내부에
apis
components
pages
utils
폴더를 추가한다.
그리고 pages에 랜딩 페이지와 메인 페이지 파일을 추가한다.
tailwind
쓰다가 StyleSheet
쓰려니까 너무 귀찮아서 프로젝트에 tailwind
을 추가하기로 했다.
npm모듈에서 검색해보니 주간 다운로드 기록이 twrnc가 가장 높고 최근 업데이트 기록도 있어서 twrnc를 쓰기로 했다.
사용방법은 아주 간단하다.
npm i twrnc
설치하고 일반 리액트에서 tailwind 모듈을 쓰는것과 동일한 방식으로 사용하면 된다.
// srcs/pages/Page_Landing.tsx
import React from 'react';
import {View, Text} from 'react-native';
import tw from 'twrnc';
const PageLanding = () => {
return (
<View style={tw`flex-1 bg-[#dfdfdf] items-center justify-center`}>
<Text style={tw`text-[#51245f] font-bold text-[20]`}>Hello</Text>
</View>
);
};
export default PageLanding;
잘 뜨는지 확인하기 위해 App.tsx
를 수정하자.
// App.tsx
import React from 'react';
import {SafeAreaView, StatusBar} from 'react-native';
import PageLanding from './srcs/pages/Page_Landing';
import tw from 'twrnc';
const App = () => {
return (
<SafeAreaView style={tw`flex-1`}>
<StatusBar hidden />
<PageLanding />
</SafeAreaView>
);
};
export default App;
SafeAreaView
는 ios에서 둥근 부분 혹은 노치부분에 자동으로 패딩을 적용해준다고 해서 지금은 ios 테스트가 불가능하지만 나중을 대비해 그대로 사용해봤다.
안드로이드에서 테스트했을때는 일반 View
처럼 나타난다.
이동할 대상 페이지도 만들어두자.
// srcs/pages/Page_Main.tsx
import React from 'react';
import {View, Text} from 'react-native';
import tw from 'twrnc';
const PageMain = () => {
return (
<View style={tw`flex-1 bg-[#dfdfdf] items-center justify-center`}>
<Text style={tw`text-[#245f29] font-bold text-[20]`}>Wolrd</Text>
</View>
);
};
export default PageMain;
페이지 이동을 위해 React Navigation을 사용할 것이다.
패키지를 설치한다.
npm install @react-navigation/native
NavigationContainer
는 탐색 트리를 관리하고 탐색 상태를 포함하는 구성 요소다.
이 구성 요소는 모든 네비게이터 구조를 래핑해야하므로 일반적으로 앱의 루트인 App.js
에서 이 구성 요소를 렌더링하는 것이 좋다.
npm install react-native-screens react-native-safe-area-context
React Native 0.60 이상부터 연결은 자동이라 별도의 링크는 필요하지 않다고 한다.
(0.60이하일 경우는 홈페이지를 참고해서 세팅)
// android/app/src/main/java/<your package name>/MainActivity.java
...
import android.os.Bundle;
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
...
모바일에서는 히스토리 개념이 없다.
대신, native stack navigator
가 화면 간 전환과 탐색 기록 관리 기능을 제공한다.
사용자가 앱과 상호 작용할 때 앱이 탐색 스택에서 항목을 push/pop해서 사용자에게 다른 화면이 표시되도록 만든다.
npm install @react-navigation/native-stack
createNativeStackNavigator
는 {Screen, Navigator}
객체를 반환하는 함수한다.
둘 다 네비게이터를 구성하는 데 사용되는 React 구성 요소인데, Navigator
는 경로를 구성하기 위해 Screen
을 자식으로 포함해야 한다.
App.tsx
구성// App.tsx
import React from 'react';
import PageLanding from './srcs/pages/Page_Landing';
import PageMain from './srcs/pages/Page_Main';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
const App = () => {
const Stack = createNativeStackNavigator();
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Landing" component={PageLanding} />
<Stack.Screen name="Main" component={PageMain} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
이렇게 하면 맨 처음 진입했을때 자동으로 첫번째 Stack.Screen
의 컴포넌트가 렌더링 된다.
그런데 상단에 원하지 않았던 헤더가 표시되었다.
이 헤더는 자유롭게 커스터마이징 할 수 있어서 다른 페이지에서는 유용하게 쓸 수 있겠지만 랜딩 페이지에서는 무의미하므로 옵션을 줘서 제거한다.
// App.tsx
...
<Stack.Screen
name="Landing"
// 이 옵션을 추가한다.
options={{headerShown: false}}
component={PageLanding}
/>
...
헤더가 잘 설정되었다.
1초 뒤에 랜딩페이지가 자동으로 메인 페이지로 전환되도록 할 것이다.
// srcs/pages/Page_Landing.tsx
import React, {useEffect} from 'react';
import {View, Text} from 'react-native';
import tw from 'twrnc';
const PageLanding = ({navigation}) => {
useEffect(() => {
setTimeout(() => {
navigation.navigate('Main');
}, 1000);
});
return (
<View style={tw`flex-1 bg-[#dfdfdf] items-center justify-center`}>
<Text style={tw`text-[#51245f] font-bold text-[20]`}>Hello</Text>
</View>
);
};
export default PageLanding;
이렇게 하면 전환은 되긴 하는데 navigation
가 any이 될 수 있다는 타입 오류를 띄운다.
타입 지정을 해주어야 한다.
리액트 네비게이터 타입 관련 글을 참고해 타입을 구성했다.
// @types/route.d.ts
declare type NavParamList = {
Landing: undefined;
Main: undefined;
};
declare type Props = NativeStackScreenProps<NavParamList, string>;
페이지명 뒤에 props
로 넘겨줄 값의 타입을 지정해야하는데, 둘다 props
값이 없으므로 undefined
로 지정했다.
App.tsx
의 stack에 타입을 전달한다.
// App.tsx
...
const Stack = createNativeStackNavigator<NavParamList>();
...
Page_Landing.tsx
의 navigation
에 타입을 전달한다.
// srcs/pages/Page_Landing.tsx
...
const PageLanding = ({navigation}: Props) => {
...
이렇게 하면 오류 없이 페이지 전환이 잘 되는것을 확인 할 수 있다.
props 전달을 한번 해보자.
world 대신 앞에서 전달된 이름을 띄우기로 하자.
먼저 타입을 수정한다.
// @types/route.d.ts
declare type NavParamList = {
Landing: undefined;
Main: {name: string};
};
declare type Props = NativeStackScreenProps<NavParamList, string>;
Main
이 name
이라는 string
을 받는다고 명시했다.
이제 랜딩페이지에서 props
를 같이 보낸다.
// srcs/pages/Page_Landing.tsx
...
const PageLanding = ({navigation}: Props) => {
useEffect(() => {
setTimeout(() => {
// 이부분 변경
navigation.navigate('Main', {
name: 'hokim',
});
}, 1000);
});
...
메인페이지에서 props
를 받는다.
// srcs/pages/Page_Main.tsx
...
const PageMain = ({route}: Props) => {
const {name} = route.params;
return (
<View style={tw`flex-1 bg-[#dfdfdf] items-center justify-center`}>
<StatusBar hidden={false} />
<Text style={tw`text-[#245f29] font-bold text-[20]`}>{name}</Text>
</View>
);
};
export default PageMain;
이렇게 하면 메인 화면에서 보낸 hokim이 나타난다.
랜딩 페이지에서 메인 페이지로 간 후, 뒤로가기 버튼을 누르면 랜딩 페이지로 이동한다.
랜딩 페이지로 갈 수 없도록 막아야한다.
CommonActions
로 스택 기록을 초기화 할 것이다.
// srcs/pages/Page_Landing.tsx
...
import {CommonActions} from '@react-navigation/native';
const PageLanding = ({navigation}: Props) => {
useEffect(() => {
setTimeout(() => {
navigation.dispatch(
CommonActions.reset({
index: 1,
routes: [
{
name: 'Main',
params: {name: 'hokim'},
},
],
}),
);
}, 1000);
}, []);
...
dispatch
메서드는 작업 생성자(CommonActions
)를 통해 탐색 상태를 수동으로 업데이트한다.
routes
에 이동해야 할 페이지와 props
를 지정하고 index
를 1로 해 첫번째 route로 페이지를 이동시킨다.
이렇게 하면 히스토리가 없기 때문에 헤더의 뒤로가기 버튼이 사라지고, 기기의 뒤로가기 버튼을 누르면 앱이 종료되게 된다.
상세 코드는 링크 참조:
https://github.com/hokim2407/react-native-nav/commit/9fd91863372d1a7d4b0ccd35cc3f4ac302f591f2