Dev/Projects

[Cloning] (Nomad Code) Twitter clone code with Firebase & React (1) - Firebase, React Router, Vice 최신 버전으로 사용하기

youngst 2022. 8. 11. 16:32

리액트 복습과 파이어 베이스 연습이 필요하여 노마드 코더(Nomad Coder)에 있는 트위터 클론 코딩 코스를 수강하였다. 이런 코스를 무료로 제공하고 있다는 점이 놀랍다. 이 강좌에서는 Firebase와 React Router의 이전 버전을 사용하고 있었고, 해당 버전의 설치 후 수강이 권장되었다. 하지만 나는 호기롭게 공식문서와 함께 최신 버전을 사용해보기로 하였고 이것은 큰 고난의 서막이었다....

 

 

React Router

리엑트 라우터가 최근 업데이트에서 크게 바뀌었다. 나는 입문자이기 때문에 애초에 이전 버전을 접해본적이 없어 잘 모르지만 강의와 비교하여 달라진 점을 찾아내느라 애를 먹었다. Upgrading from v5 - React Router

  • <Switch /> 대신 <Routes />
    • Switch로 Route들을 감싸던 방식 대신에 직관적인 이름의 Routes로 바꾸었다고 한다. Route 안에 자식으로 있어야 했던 요소(엘리먼트 또는 컴포넌트)는 Route에 attribute형태(element={})로 들어갈 수 있게 되었다. 덕분에 <Outlet />의 사용법이 조금 더 직관적으로 변한 것 같다.
  • <Redirect /> 대신 <Route  element={<Navigate />} />
    • 필요 시 해당 주소로 넘겨주던 redirect가 없어지고 대신 Navigate라는 컴포넌트가 생겼다. Redirect가 필요한 경로에 Route를 넣고 from 주소를 path로 넘겨주고 element로 Navigate를 넘겨주면 Redirect를 할 수 있다. 

 

Firebase란?

구글이 인수하여 운영하고 있는 백엔드 및 데이터베이스 제공 서비스이다. 백엔드와 관련한 단 한 줄의 코딩 없이도 신원확인, 데이터베이스 관리 등 앱 운영에 필요한 백엔드 전반을 무료로 사용할 수 있다!! 물론 규모가 커진다면 비용을 지불해야하지만 이 비용 또한 크지 않다. 시장 테스트용으로 앱을 출시할 때 유용하게 사용할 수 있는 서비스이다.

 

환경변수?

Firebase 서비스를 사용하기 위해서는 API key나 app ID 같은 것들을 이용해야한다. 하지만 이것들을 모두 Git-hub에 올린다면 아무나 key를 얻어 내 Firebase 서비스에 접근할 수 있게 되고 이는 큰일날 일이다... 이 때문에 key 값들을 환경변수라는 형태로 저장하고 사용하기를 노마드 코더에서 권장하였다. env라는 확장자를 갖는 파일에 전역 상수 형태로 환경 변수를 지정하여 gitignore에 등록하는 것이다. 나는 CRA가 아닌 Vite를 사용하였기 때문에 강의의 방식과는 조금 다르게 환경변수를 설정해주어야했다.

//.env.local -> Vite에서는 .local을 이용하여 git ignore를 사용하기를 권장하고 있다
VITE_API_KEY = "AIza....."
VITE_AUTH_DOMAIN = "ywit~.firebaseapp.com"
VITE_PROJECT_ID = 'ywit~'
VITE_STORAGE_BUCKET = "ywit~.appspot.com"
VITE_MESSAGING_SENDER_ID = "567......"
VITE_APP_ID = "1:567......:web:03f......"


//환경 변수 사용법 -> import.meta.env 를 이용하면 환경변수에 접근할 수 있다.
const firebaseConfig = {
  apiKey: import.meta.env.VITE_API_KEY,
  authDomain: import.meta.env.VITE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_APP_ID,
};

 

Firebase

리액트 라우터처럼 Firebase도 버전 9로 바뀌며 크게 변했다. 원래는 객체 지향 방식으로 .(dot)을 사용해 자식 메소드나 파라미터를 사용하는 방식이었다면, 이제는 React Hooks처럼 함수형 프로그래밍 방식으로 필요한 요소들을 직접 함수로 보내 사용할 수 있게 되었다. 덕분에 필요하지 않은 모든 요소까지 import하지 않아도 돼 app의 크기를 비약적으로 줄일 수 있게 되었다고 한다. 신형 import 문법을 이용해 필요한 함수들만 골라서 import 하면 필요한 함수들만 build 되기 때문이다.

 

환경변수로 지정한 firebaseConfig는 Firebase App에 전달해주어야 한다. 이전 버전과 달리 initializeApp(firebaseConfig) 메소드를 이용하여 Firebase App 인터페이스를 만들어 사용한다. 이 app만 export하는 대신 나는 Firebase interface들을 모아 따로 export해 주었다.

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  apiKey: import.meta.env.VITE_API_KEY,
  authDomain: import.meta.env.VITE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_APP_ID,
};

const fApp = initializeApp(firebaseConfig);

export const auth = getAuth(fApp);
export const db = getFirestore(fApp);
export const storage = getStorage(fApp);

Firebase - Auth

이전 버전에서는 firebase.auth()를 instance로 만들어 해당 인스턴스 내부의 메서드나 파라미터를 이용하는 방식을 사용했다. 하지만 새로운 버전에서는 getAuth를 통해 auth에 필요한 정보를 담고 있는 Auth를 interface 형태로 받아와 사용한다. 이 auth라는 인터페이스를 필요한 Firebase/Auth 메서드에 제공하면 이전 버전처럼 auth()에서 일일히 .(dot)을 사용하여 접근하지 않고 사용할 수 있다. 이전 버전부터 사용해오던 일부 개발자들은 불편함을 호소하는 것 같았지만 새로 배우는 입장에서는 이 방법이 더 간결한 것 같다. 물론 공식문서가 입문 개발자가 보기에는 불친절하여 강의에 맞춰 구현하는데는 꽤 애를 먹었다...

 

로그인 창 구현하기

signInWithPopup, signInWithEmailAndPassword, GoogleAuthProvider 등의 함수는 이제 auth 인스턴스 내의 메서드가 아니라 함수 그 자체로 제공된다. 첫번째 파라미터로 auth 인터페이스를 제공해주면 된다. Firebase는 SNS 로그인 API도 편리하게 사용할 수 있도록 제공한다. 이것 역시 사용법이 조금 바뀌어 로그인 provider는 new 키워드를 이용해 인스턴스로 만들어야한다. 내가 구현한 코드는 아래와 같다.

import { auth } from '../fBase';
import {
  signInWithPopup,
  signInWithEmailAndPassword,
  GoogleAuthProvider,
  createUserWithEmailAndPassword,
} from 'firebase/auth';
	
  (생략)  
  
  let provider;
  if (name === 'google') {
    provider = new GoogleAuthProvider();
  }
  const data = await signInWithPopup(auth, provider);
  
  (생략)
    
  const onSubmit = async (e) => {
    e.preventDefault();

    try {
      let data = 'a';
      if (newAccount) {
        // create account
        data = await createUserWithEmailAndPassword(auth, email, password);
      } else {
        //Log in
        data = await signInWithEmailAndPassword(auth, email, password);
      }
      //! Test
      console.log(data);
    } catch (error) {
      setError(error.message);
      console.log(error);
    }
  };
  
  (생략)

 

로그인 상태 관리

currentUser는 auth 인터페이스를 이용해 직접 접근이 가능하다. auth.currentUser를 이용하면 로그인 여부와 사용자 상태를 확인이 가능하다. 이는 추후에 state를 끌어올리거나 전역적으로 관리해주어야 한다.

 

로그인 창은 input 값을 state로 관리하는 제어 컴포넌트로 만든다. toggle 버튼을 구현하면 가입 창을 따로 구현하지 않고도 로그인과 가입을 동시에 할 수 있다. 이를 이용하면 사용자가 헷갈려 가입버튼을 눌렀을 때 에러 메세지를 출력하는 대신 바로 로그인이 되도록 구현할 수도 있을 것이다. 로그인 상태에는 persistance를(local, session, none) 이용할 수 있는데 이를 이용하면 로그인의 지속 상태를 결정할 수 있다.