캡스톤디자인

React-Native 로그인/회원가입 플로우 navigation

10주차에는 로그인/회원가입 플로우 구현 및 서버 연동이다.

 

reactnavigation.org/docs/auth-flow/

 

리액트 네비게이션 독스 참고했다.

인턴때 내가 구현할 뻔 했던 기능이었던 것 같은데 여차저차해서 안 했던 것 같다. 리네 어케했었는지 다 까먹어서 고생중,..

 

앱 구동 시 스플레시 화면을 띄우고 로딩 되는 동안 AsyncStorage에서 로그인 여부를 확인한다.

AsyncStorage는 로컬 데이터베이스(핸드폰에 저장해둔 데이터)이다.

로그인 정보(토큰)가 없다면 로그인 네비게이터로, 있다면 메인스텍 네비게이터로 이동시킨다.

 

그림은 아래처럼 그렸지만 SplashScreen/AuthStack/MainStack을 스텍으로 쌓는 것이 아니라 상태에 따라 셋 중 하나만 스텍에 쌓는다.

이렇게 하면 로그인 후 메인화면에서 뒤로가기를 눌렀을 때 로그인 화면으로 돌아가지 않는다.

 

 

토큰을 저장할 로컬스토리지 라이브러리로는 expo-secure-store와 AsyncStorage중 고민을 하다가 npm weekly download 수가  더 많은 AsyncStorage를 사용하기로 했다.

hackernoon.com/react-native-for-beginners-fb3095968acf 에서 secure-store는 다른 것들과 달리 암호화를 거쳐 저장한다고 해서 한번 사용해볼까 했는데,,, expo 라이브러리이기도 하고, 사람들이 AsyncStorage를 많이 사용하는 데에는 이유가 있지 않을까? 하는 맘ㅎ

 

/**
 * 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 { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { MainModalStackNavigator, AuthStackNavigator } from './src/navigators'
import { SplashScreen } from './src/pages'
import { AuthContext } from './src/context'

const Stack = createStackNavigator()

export function App() {
  const [state, dispatch] = React.useReducer(
    (prevState: any, action: { type: string; token: any | undefined | null }) => {
      switch (action.type) {
        case 'RESTORE_TOKEN':
          return {
            ...prevState,
            userToken: action.token,
            isLoading: false,
          }
        case 'SIGN_IN':
          return {
            ...prevState,
            isSignout: false,
            userToken: action.token,
          }
        case 'SIGN_OUT':
          return {
            ...prevState,
            isSignout: true,
            userToken: null,
          }
      }
    },
    {
      isLoading: true,
      isSignout: false,
      userToken: null,
    },
  )

  React.useEffect(() => {
    // Storage에서 토큰 가져옴, 다른 화면으로 네비게이트
    const bootstrapAsync = async () => {
      let userToken

      try {
        userToken = await AsyncStorage.getItem('userToken')
      } catch (e) {
        // 토큰 가져오기 실패 FIXME: alert해주기
      }

      // FIXME: 토큰 유효한지 확인해주기

      // 스크린 언마운트됨, 버려짐
      dispatch({ type: 'RESTORE_TOKEN', token: userToken })
    }

    bootstrapAsync()
  }, [])

  const authContext = React.useMemo(
    () => ({
      signIn: async (data) => {
        // 여기서 아이디와 비밀번호 서버로 보내고 토큰 받아옴
        let userToken = 'temp'

        try {
          userToken = await 'dummy-auth-token'
        } catch (e) {
          // 실패 시 에러 처리
        }
        // 받아온 토큰 저장
        try {
          await AsyncStorage.setItem('@storage_Key', userToken)
        } catch (e) {
          // 토큰 저장 오류 처리
        }

        dispatch({ type: 'SIGN_IN', token: userToken })
      },
      signOut: () => dispatch({ type: 'SIGN_OUT', token: null }),
      signUp: async (data) => {
        let userToken = 'temp'
        // 서버에 회원가입 데이터 보내고 토큰 받아오기
        try {
          userToken = await 'dummy-auth-token'
        } catch (e) {
          // 실패 시 에러 처리
        }
        // 받아온 토큰 저장
        try {
          await AsyncStorage.setItem('@storage_Key', userToken)
        } catch (e) {
          // 토큰 저장 오류 처리
        }

        dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' })
      },
    }),
    [],
  )

  return (
    <AuthContext.Provider value={authContext}>
      <SafeAreaProvider>
        <NavigationContainer>
          <Stack.Navigator
            screenOptions={{
              headerShown: false,
            }}>
            {state.isLoading ? (
              // 토큰 확인 중 스플레시 화면
              <Stack.Screen name="Splash" component={SplashScreen} />
            ) : state.userToken == null ? (
              // 토큰 없으면 로그인 플로우
              <Stack.Screen
                name="AuthStackNavigator"
                component={AuthStackNavigator}
                options={{
                  title: 'Sign in',
                  animationTypeForReplace: state.isSignout ? 'pop' : 'push',
                }}
              />
            ) : (
              // 토큰 있음, main 화면으로
              <Stack.Screen name="MainModalStackNavigator" component={MainModalStackNavigator} />
            )}
          </Stack.Navigator>
        </NavigationContainer>
      </SafeAreaProvider>
    </AuthContext.Provider>
  )
}

export default App

 리액트 네비게이션에서 제공한 코드에서 우리 프로젝트에 맞게 조금 수정했다.

다만 context는 provider랑 이니셜 스테이트 등 관련 코드는 따로 context폴더에 모아놓고 싶었는데 얘는 이니셜 스테이트를 useMemo를 사용해서 따로 뺄 수가 없었다. 나중에 시간이  되면 좀 더 찾아보고 깔끔하게 바꾸고 싶다.

'캡스톤디자인' 카테고리의 다른 글

에러 메모  (1) 2021.05.26
react-native 모듈 간편하게 import 시키기  (2) 2021.05.09
react-native-google-maps pod error  (0) 2021.05.06
빌드 오류 수정  (0) 2021.05.04
구글 다이얼로그 플로우 연동 방법 조사  (1) 2021.04.27