보일러 플레이트를 사용하면 좋겠지만, 리덕스에 익숙하기 때문에 정리하는 의미에서 아래 순서대로 설치를 해보도록 하겠습니다.
1. React Native (0.65) with Typescript
RN 템플렛을 사용할 경우
npx react-native init MyApp --template react-native-template-typescript
expo bare work flow 를 사용할 경우
expo init --template expo-template-bare-typescript --name MyApp
2. React Navigation
설치
yarn add @react-navigation/native
디펜던시 설치
yarn add react-native-screens react-native-safe-area-context
ios경우
npx pod-install ios
안드로이드 경우
실행되도록 android/app/src/main/java/<your package name>/MainActivity.java 에 아래 추가
import android.os.Bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
스택내비게이션 만들기 위해 라이브러리 설치
yarn add @react-navigation/native-stack
파라미터를 타입스크립트를 이용해서 전달하는 스크린 코딩 Code screens with typed parameters to pass.
import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
type RootStackParamList = {
Home: undefined;
Count: { count: number };
};
const RootStack = createNativeStackNavigator<RootStackParamList>();
type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
type CountScreenProps = NativeStackScreenProps<RootStackParamList, 'Count'>;
function HomeScreen({ route, navigation }: HomeScreenProps) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Count"
onPress={() => navigation.navigate('Count',
{
count: 7,
})}
/>
</View>
);
}
function CountScreen({ route, navigation }: CountScreenProps) {
const { count } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Count Screen</Text>
<Text>카운터: {JSON.stringify(count)}</Text>
<Button
title="Go Home"
onPress={() => navigation.navigate('Home')}
/>
</View>
);
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<RootStack.Navigator initialRouteName="Home">
<RootStack.Screen name="Home" component={HomeScreen} />
<RootStack.Screen name="Count" component={CountScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
}
export default App;
Redux
install
yarn add react-redux redux
스토어 폴더에 redux.ts 를 작성
import { Action, combineReducers, createStore } from "redux";
//액션 타입
export const ActionTypes = {
increment: 'INCREMENT',
decrement: 'DECREMENT',
} as const
//스테이트 타입
export type Count = {
value: number
}
interface incrementAction extends Action {
type: typeof ActionTypes.increment,
}
interface decrementAction extends Action {
type: typeof ActionTypes.decrement,
}
export type CountActionTypes = incrementAction | decrementAction
//액션 크리에이터 작성 - 실제로 액션을 리턴하는 함수
export const increment = (): CountActionTypes => {
return {
type: ActionTypes.increment
}
}
export const decrement = (): CountActionTypes => {
return {
type: ActionTypes.decrement
}
}
const initialState: Count = {
value: 0,
}
//리듀서
export const CountReducer = (state = initialState, action: CountActionTypes): Count => {
switch (action.type) {
case ActionTypes.increment:
return { ...state, value: state.value + 1 }
case ActionTypes.decrement:
return { ...state, value: state.value - 1 }
}
return state;
}
const RootReducer = combineReducers({
count: CountReducer,
})
export type RootState = ReturnType<typeof RootReducer>
const store = createStore(RootReducer)
export default store
index.js 에 App 을 프로바이더로 감싸기
import { AppRegistry } from 'react-native';
import React from 'react';
import App from './App';
import { name as appName } from './app.json';
import { Provider } from 'react-redux';
import store from './store/redux';
const ReduxApp = () => (
<Provider store={store}>
<App />
</Provider>
)
AppRegistry.registerComponent(appName, () => ReduxApp);
App.tsx 화일에 추가하여 리덕스 스테이트 변경해 보기 useDispatch 덕분에 많이 편해졌다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* Generated with the TypeScript template
* https://github.com/react-native-community/react-native-template-typescript
*
* @format
*/
import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useDispatch, useSelector } from 'react-redux';
import store, { decrement, increment, RootState } from './store/redux';
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Count: undefined;
};
const RootStack = createNativeStackNavigator<RootStackParamList>();
type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;
type CountScreenProps = NativeStackScreenProps<RootStackParamList, 'Count'>;
function HomeScreen({ route, navigation }: HomeScreenProps) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Text>
<Button
title="Go to Profile"
onPress={() => navigation.navigate('Profile',
{
userId: '나야나',
})}
/>
<Button
title="Go to Count"
onPress={() => navigation.navigate('Count')}
/>
</Text>
</View>
);
}
function ProfileScreen({ route, navigation }: ProfileScreenProps) {
const { userId } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile Screen</Text>
<Text>userId: {JSON.stringify(userId)}</Text>
<Button
title="Go Home"
onPress={() => navigation.navigate('Home')}
/>
</View>
);
}
function CountScreen({ route, navigation }: CountScreenProps) {
const countState = useSelector((state: RootState) => state.count)
const dispatch = useDispatch()
const OnIncrement = () => {
dispatch(increment())
}
const OnDecrement = () => {
dispatch(decrement())
}
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Count Screen</Text>
<Text>카운터2: {countState.value}</Text>
<Text>
<Button
title="위로"
onPress={OnIncrement}
/>
<Button
title="아래로"
onPress={OnDecrement}
/>
</Text>
<Button
title="Go Home"
onPress={() => navigation.navigate('Home')}
/>
<Text>{JSON.stringify(store.getState())}</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<RootStack.Navigator initialRouteName="Home">
<RootStack.Screen name="Home" component={HomeScreen} />
<RootStack.Screen name="Profile" component={ProfileScreen} />
<RootStack.Screen name="Count" component={CountScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
}
export default App;
redux-persist 인스톨
먼저 리덕스의 스테이트를 저장하기 위해서는 redux-persist 라이브러리가 필요합니다.
yarn add redux-persist
AsyncStorage 인스톨
이번에 지속데이터를 저장할 공간으로 사용할 AsyncStorage 를 인스톨 합니다. 6.0 이전까지는 포함되어 있었으나, 6.0 이후 버전을 사용할 때는 커뮤니티 판을 사용.
yarn add @react-native-community/async-storage
*아이폰의 경우 ios 폴더에서 npx pod-install ios 을 실행해 줍니다.
index.js화일에서 아래와 같이 적용합니다.
/**
* @format
*/
import { AppRegistry } from 'react-native';
import React from 'react';
import App from './App';
import { name as appName } from './app.json';
import { Provider } from 'react-redux';
import { store } from './store/redux';
import { persistStore } from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
const persistor = persistStore(store);
const ReduxApp = () => (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
)
AppRegistry.registerComponent(appName, () => ReduxApp);
redux.ts
import { Action, combineReducers, createStore } from "redux";
import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer } from "redux-persist";
//액션 타입
export const ActionTypes = {
increment: 'INCREMENT',
decrement: 'DECREMENT',
} as const
//스테이트 타입
export type Count = {
value: number
}
interface incrementAction extends Action {
type: typeof ActionTypes.increment,
}
interface decrementAction extends Action {
type: typeof ActionTypes.decrement,
}
export type CountActionTypes = incrementAction | decrementAction
//액션 크리에이터 작성 - 실제로 액션을 리턴하는 함수
export const increment = (): CountActionTypes => {
return {
type: ActionTypes.increment
}
}
export const decrement = (): CountActionTypes => {
return {
type: ActionTypes.decrement
}
}
const initialState: Count = {
value: 0,
}
//리듀서
export const CountReducer = (state = initialState, action: CountActionTypes): Count => {
switch (action.type) {
case ActionTypes.increment:
return { ...state, value: state.value + 1 }
case ActionTypes.decrement:
return { ...state, value: state.value - 1 }
}
return state;
}
const RootReducer = combineReducers({
count: CountReducer,
})
export type RootState = ReturnType<typeof RootReducer>
// const store = createStore(RootReducer)
// export default store
const persistConfig = {
key: "root",
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, RootReducer);
// store.js
export const store = createStore(
persistedReducer,
// composeWithDevTools()
);
// // store.js
// export const store = createStore(
// reducers,
// )
export const persistor = persistStore(store);