qcoding

[ReactNative Instagram clone #3_로그인 및 Auth , Redux user 상태관리 본문

ReactNative

[ReactNative Instagram clone #3_로그인 및 Auth , Redux user 상태관리

Qcoding 2022. 5. 11. 12:53
반응형

* 이번 글에서는 Firebase에서 제공하는 email 회원가입 기능을 통해 회원가입 / 로그인을 진행하고, user의 정보가 존재하는 것과 하지 않는 것에 따라서 navigation이 어떻게 분류되며, Redux를 통해 user정보와 user의 post를 받아오는 것을 정리하려고 한다.

 

* 해당 포스트에서 사용하는 라이브러리를 사용하기 위하여 아래와 같이 설치를 진행한다.

// navigation을 사용하기 위한 라이브러리
npm install @react-navigation/native
expo install react-native-screens react-native-safe-area-context
npm install @react-navigation/stack
expo install react-native-gesture-handler
npm install @react-navigation/bottom-tabs  
npm install @react-navigation/material-bottom-tabs react-native-paper react-native-vector-icons

// firebase를 사용하기 위한 라이브러리
expo install firebase   // https://docs.expo.dev/guides/using-firebase/
expo install @react-native-async-storage/async-storage

// redux를 사용하기 위한 라이브러리
npm install --save redux react-redux
npm i redux-thunk

 

1) 로그인 및 회원가입 , 로그인 여부에 따른 Navigation 분류 

Register.js

--> Firebase V9 문법이 변경됨에 따라 moule 형태로 import 해 와서 사용하는 것으로 변경되었다. 기본적으로 getAuth() 함수를 사용하여 유저 auth 정보를 받아오게 되는데 createUserWithEmailAndPassword() 함수를 사용하여 email과 password 정보를 받아서 FireStore에 저장하게 된다.

 

--> Firestore에 저장되는 형태는 users colleciion --> doc.id : userUid 의 형태로 저장된다. 이 값이 저장되면 로그인할 때 해당 collection에서 user 정보를 받아올 수가 있다.

import { View,TextInput,Button} from 'react-native';
import React,{useState}from 'react';
import {
    getAuth,
    createUserWithEmailAndPassword,
  } from "firebase/auth";
  import { getFirestore, setDoc, doc } from 'firebase/firestore';

const Register = () => {
  const [userInfo,setUserInfo]=useState({
    email:"",
    password:"",
    name:""
  })

;

  const SignUp=async()=>{
    console.log("회원가입")
    const {email,password,name}=userInfo;
    // Firebase V9 문법이 많이 바뀜
    const auth =await  getAuth();
    const db = await getFirestore()
    // 회원가입을 진행한 후 collection에 추가함.
    await createUserWithEmailAndPassword(auth,email,password).then( async (result)=>{
        const userUID=result.user.uid
        //  console.log("result user : ",userUID)
          const docRef = await setDoc(doc(db, "users",userUID), {
            uid:userUID,
            name,
            email
          });
    }).catch((error)=>{
        console.log(error)
    });

  }


  return (
    <View style={{flex:1,justifyContent:"center"}}>
        <TextInput
            placeholder='name'
            // 아래 처럼 prevState를 가져와서 처리함.
            onChangeText={(txt)=>{setUserInfo({...userInfo,name:txt})}}
        />
        <TextInput
            placeholder='email'
            onChangeText={(txt)=>{setUserInfo({...userInfo,email:txt})}}
        />
        <TextInput
            placeholder='password'
            secureTextEntry={true}
            onChangeText={(txt)=>{setUserInfo({...userInfo,password:txt})}}
        />
        <Button
            title="Signup"
            onPress={()=>{SignUp()}}
        ></Button>

    </View>
  )
}

export default Register
Login.js
-->앞에서 회원가입 후에 로그인을 진행할 수 있는 데, 이때 사용되는 함수는 signInWithEmailAndPassword()로 사용자가 입력한 email과 password로 user정보가 있으면 result를 받게 되고 이 때 다시 App.js에서 onAuthStateChanged() 함수를 통해 auth 정보가 변경된 것을 감지하게 된다. 변경된 정보가 감지되어 user가 auth 인증이 되면 login되어 앱에서 제공하는 기능들을 사용학 수 있게 된다. 
import { View,TextInput,Button} from 'react-native';
import React,{useState}from 'react';
import {
    getAuth,
    signInWithEmailAndPassword,
  } from "firebase/auth";

export default function Login() {
const [userInfo,setUserInfo]=useState({
    email:"",
    password:"",
    })

  const auth = getAuth();
  const SignIN=async()=>{
    console.log("로그인")
    const {email,password}=userInfo;
    await signInWithEmailAndPassword(auth,email,password).then((result)=>{
        console.log(result)
    }).catch((error)=>{
        console.log(error)
    });

  }


  return (
<View style={{flex:1,justifyContent:"center"}}>
        <TextInput
            placeholder='email'
            onChangeText={(txt)=>{setUserInfo({...userInfo,email:txt})}}
        />
        <TextInput
            placeholder='password'
            secureTextEntry={true}
            onChangeText={(txt)=>{setUserInfo({...userInfo,password:txt})}}
        />
        <Button
            title="SignIn"
            onPress={()=>{SignIN()}}
        ></Button>

    </View>
  )
}
 

App.js

--> 위에서 언급한 것처럼 onAuthStateChanged()가 유저의 로그인을 감지하게 되고 user 가 있을 경우 loggedIn 상태를 true로 하여 Main.js가 있는 Stack으로 보내주게 된다. 

import React,{useState,useEffect}from 'react';
import { StyleSheet, Text, View,Button,LogBox,StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
export default function App() {
  const [loaded,setLoaded]=useState(true)
  const [loggdIn,setloggdIn]=useState(false)

  const auth = getAuth();

  const chkSigned = async ()=>{
    await onAuthStateChanged(auth, (user) => {
      if (user) {
        // User is signed in, see docs for a list of available properties
        // https://firebase.google.com/docs/reference/js/firebase.User
        const uid = user.uid;
        setloggdIn(true)
        setLoaded(false)
        // ...
      } else {
        console.log("로그인 안됨")
        setloggdIn(false)
        setLoaded(false)
      }
    })

  };

  useEffect(()=>{
    chkSigned();
  },[])

  if (loaded){
    return(
      <>
        <StatusBar></StatusBar>
        <View style={{flex:1,justifyContent:"center"}}>
          <Text>Loading...</Text>
      </View>
     </>
    )
  }

  // LoggIn이 되면 Main으로 들어가는데, 여기서는 tab navigator를 써서 이동가능하게함. 
  // 아래 페이지를 두고 안에서 이동가능하게 함.
  // LoggIn이 안되면 Lading page로 들어가서 Register 또는 Login 페이지로 이동가능함.
  return (loggdIn?
  <>
  <StatusBar></StatusBar>
  <Provider store={store}>
    <NavigationContainer>
        <Stack.Navigator initialRouteName="Main">
          <Stack.Screen name="Main" component={Main} options={{headerShown:false}}></Stack.Screen>
          <Stack.Screen name="Add" component={Add}></Stack.Screen>
          <Stack.Screen name="Save" component={Save}></Stack.Screen>
          <Stack.Screen name="Comment" component={Comment}></Stack.Screen>
        </Stack.Navigator>
    </NavigationContainer>
  </Provider>
  </>:<>
  <StatusBar></StatusBar>
  <NavigationContainer>
      <Stack.Navigator initialRouteName="Landing">
        <Stack.Screen name="Lading" component={Landing} options={{headerShown:false}}></Stack.Screen>
        <Stack.Screen name="Register" component={Register} options={{headerShown:false}}></Stack.Screen>
        <Stack.Screen name="Login" component={Login} options={{headerShown:false}}></Stack.Screen>
      </Stack.Navigator>
  </NavigationContainer>
  </>

  );
}

2) 로그인 시 redux에서 user 및 user Posts 받아오기 

2-1) redux 파일 구조

-> redux 폴더안에는 위와 같이 Action creator / Action / Reducer로 분리 되어있다. 해당 프로젝트에서는 reducer를 user / users 2개를 사용하고, index.js에서 통합시켜 하나의 store를 만들어서 사용한다. 각 파일 내용을 통해서 어떤식으로 구조화 되어 있는 지 확인해 보자.

 

2-2) redux 코드

App.js

-> App.js에서는 store를 실제 컴포넌트들로 전달하는 역활을 한다. reducer를 받아오고 미들웨어로 비동기처리를 위해 thunk를 사용하여 store를 생성한 뒤 Provider 태그를 통해 전달한다.

// redux
import { Provider } from 'react-redux'
import { createStore,applyMiddleware } from 'redux';
import rootReducer from './redux/reducers'
import thunk from 'redux-thunk'
import Save from './components/main/Save';


// store 생성
const store=createStore(rootReducer,applyMiddleware(thunk))

// <Provider> 태그로 store를 전달함
  <Provider store={store}>
    <NavigationContainer>
        <Stack.Navigator initialRouteName="Main">
          <Stack.Screen name="Main" component={Main} options={{headerShown:false}}></Stack.Screen>
          <Stack.Screen name="Add" component={Add}></Stack.Screen>
          <Stack.Screen name="Save" component={Save}></Stack.Screen>
          <Stack.Screen name="Comment" component={Comment}></Stack.Screen>
        </Stack.Navigator>
    </NavigationContainer>
  </Provider>

action/index.js  ( = Action Creator)

-> user 정보를 가져오기 위한 action 생성함수를 정의한다. Firebase V9 문법을 사용해서 users collection 내에 접속한 사용자의 uid로 정보를 조회해서 가져온다. 아래 코드에서 action 객체를 의미하는 것이

{type: USER_STATE_CHANGE,currentUser:docSnap.data()} 이며 USER_STATE_CHANGE의 action은 constant/index.js에 정의 되어 있고, 실제 dispatch를 통해 reducer로 해당 action이 전달되어 처리된다. 

import { getFirestore, collection, getDocs, getDoc, doc, query, orderBy, onSnapshot } from 'firebase/firestore';
import { getAuth } from "firebase/auth";
import { USER_STATE_CHANGE} from '../constant';

// paramsUid는 다른 사람의 id를 클릭해서 profile로 연결할 때 생기는 값
export const fetchUsers=(paramsUid)=>{
    return(
        async(dispatch)=>{
            // user정보
            const auth = await getAuth();
            const {uid} = auth.currentUser;
            // console.log("userID는",uid)
            // FireStore DB
            const db = await getFirestore();
            // data 가져오기(db,collection이름,docID)
            const docRef = doc(db, "users", paramsUid?paramsUid:uid);
            const docSnap = await getDoc(docRef);
            if (docSnap.exists()) {
                // console.log("user정보가 있음")
                // dispatch ( action생성함수 ) 이 들어간다. 
                // action생성함수는 actions/index.js에 정의되어 있고 
                // 실제 action에 대한 수행은 reducers/user
                dispatch({type: USER_STATE_CHANGE,currentUser:docSnap.data()})
            } else {
            // doc.data() will be undefined in this case
            console.log("user정보가 존재하지 않음")
            }
        }
    )
}

constant/index.js  ( = Action )

-> Action에 대한 정의가 되어 있으며 해당 정보는 reducer에서 switch 문에 case별로 분류하기 위해 전달된다.

export const USER_STATE_CHANGE='USER_STATE_CHANGE'

reducers/index.js  ( = rootReducer)

-> reducer가 분리 되었을 경우 각각을 합쳐주는 rootReduer 형태가 존재한다. 합칠 때는 combineReducers() 함수를 사용하여 합쳐준다.

import { combineReducers } from "redux";
import {user} from './user'

const rootReducer=combineReducers({
    userState:user,
})

export default rootReducer

reducers/user.js  ( = Reducer)

-> 실제 action이 동작하는 부분으로 state 관리가 실제 일어나는 부분이다. 여기서는 로그인한 user의 정보를 Firebase에서 가져와서 currentUser의 state에 담아 두고 사용할 수 있게한다.

import {USER_STATE_CHANGE,USER_POSTS_STATE_CHANGE,USER_FOLLOWING_STATE_CHANGE,CLEAR_DATA} from "../constant"

const initialState={
    currentUser:null,

}

export const user =(state=initialState,action)=>{
    switch(action.type){
        case USER_STATE_CHANGE:
            return {
                ...state,
                currentUser: action.currentUser
            }
        default:
            return state;
        }
}

Main.js  

--> redux에 있는 정보를 실제 사용할 때 hook을 사용하여 가져온다. useEffect를 통해 렌더링 될때 바로 dispatch를 통해  action creator를 호출하여 action을 생성하고 생성된 action을 reducer로 처리하여 변경된 cuurnetUser state를 useSelector hook을 통해서 가져온다. 정보를 가져올 수 있는 이유는 App.js에서 provider 태그를 통해서 store 정보를 하위 컴포넌트에게 전달하고 있기 때문이다. 

import React,{useEffect} from 'react'
import { useSelector,useDispatch} from 'react-redux'
import { fetchUsers} from '../redux/actions'

const {currentUser}= useSelector(state => state.userState);

  useEffect(()=>{

    // user 정보
    dispatch(fetchUsers());
  
  },[])
반응형
Comments