Google Firebase와 React를 이용한 트위터 클론 만들기 1

Hyuno Choi·2021년 1월 30일
1
post-thumbnail

소스 코드: https://github.com/soonitoon/dwitter
"드위터" 페이지(모바일) : https://soonitoon.github.io/dwitter

이 글은 Nomad Coder"트위터 클론코딩" 강의를 바탕으로 작성되었습니다.


2021년 1월 28일 목요일 새벽

1월 3일에 첫 포스팅을 하고 거의 한 달 만의 포스팅입니다!😆
2주 동안 개인적인 사정으로 공부도 못하고, 트위터 클론 코딩도 꽤 많은 오류를 잡느라 완성이 늦어져 어느새 1월 말까지 왔네요ㅠㅠ

포스팅 방향은 처음부터 끝까지 모든 과정을 담는 것보다는 주요 포인트, 제작하면서 겪은 문제들을 중심으로 잡을 계획입니다.

이번에는 트위터를 클론 코딩한 "드위터"를 만들어보았습니다. 드위터에서는 Firebase가 가장 중요한 역할을 담당합니다.

  • 인증 기능
  • "드윗" 및 첨부 이미지 저장
  • 유저 정보 저장

이 모든 백엔드 기능을 Firebase로 간단하게 구현할 수 있었어요😎
Firebase 관련 내용은 코드 부분에서 자세히 다루겠습니다.

개요

드위터는 간단한 실시간 채팅 서비스입니다. 사용자는 로그인 후 글을 작성할 수 있고, 자신 및 다른 사용자는 사이트를 새로고침 하지 않고도 작성된 글을 실시간으로 볼 수 있습니다.
드위터는 크게 세 화면으로 구성되어 있습니다.

왼쪽에서부터 순서대로-

  • 로그인 화면
  • 프로필 화면

입니다.

사용자는 Google 혹은 GitHub 계정을 통해 간편하게 로그인 할 수 있고, 직접 이메일 주소로 계정을 만들수도 있습니다.

홈에서 사용자는 모든 드윗들을 볼 수 있고, 자신이 작성한 드윗은 삭제나 수정이 가능합니다✍ 또한 글을 작성할 때 사진📷을 함께 첨부할 수 있습니다.

프로필 화면에서 사용자는 자신의 이름을 수정할 수 있고 로그아웃 할 수 있습니다. 상단바 밑으로는 자신이 작성한 드윗만 모아서 볼 수 있습니다.

구성

파일 구성

서비스 접속 시 App.js에서 로딩 화면을 띄우고, 로딩이 완료되면 Router로 가게 됩니다.

로그인이 되어있지 않다면 Router에서 AuthForm을 띄웁니다. 로그인 상태라면 NavigationHome화면을 보여줍니다.

AuthForm은 소셜 로그인 부분을 AuthWithSocial을 불러와 사용하고, Home은 드윗 입력 양식과 드윗들을 DwitteFactory, Dwitte을 불러와 렌더링합니다.

네비게이션을 통해 Profile로 이동할 수 있습니다.

라우터

HashRouter

라우터는 react-router-domHashRouter를 사용했습니다. 굳이 해쉬라우터를 사용한 이유는 gh-pages를 통해 배포하기 위해서입니다.

github 페이지의 주소는 github.com/soonitoon/dwitter에서 시작하게 되는데, 빌드된 파일은 github.com/soonitoon/dwitter/tree/gh-pages에 존재하므로 브라우저 라우터를 사용했을 때 경로에 오류가 생기게 됩니다.

해쉬라우터를 쓰면 주소창에 # 표시가 뜨는 게 싫다는 분들도 있는 것 같은데 저는 그닥 신경쓰이지 않는 것 같습니다.🙃

라우팅 과정

라우팅 과정은 크게 로그인 여부에 따라 나뉘게 됩니다. 로그인 여부에 따라 라우팅 하는 과정은 자바스크립트의 삼항 연산자를 사용하여 구현했습니다.

  • ✅로그인 상태라면?
    => 네비게이션 컴포넌트 렌더링
    => 경로에 따라 홈과 프로필 중 하나 렌더링

  • ❎로그아웃 상태라면?
    => 로그인 화면 렌더링

HomeProfile 경로는 중첩되는 것을 막고자 "/" 경로에 exact 속성을 주었습니다.

Firebase 세팅

Firebase에서 필요한 기능들을 import하고 다른 파일에서 사용할 수 있도록 적당한 이름으로 export했습니다.

인증 기능

기초 만들기

인증 기능을 구현하기 위해서 먼저 로그인 여부를 판단하는 코드를 작성했습니다.

  1. React-hook의 useState를 사용하여 isLoggedIn이라는 state를 만들었습니다.

  2. 이 state는 AuthService.currentUser 값으로 초기화 되는데, 이 currentUser는 로그인 되어 있으면 현재 유저 정보를 반환합니다. 즉, isLoggedIn의 값이 null이냐 아니냐에 따라 로그인 상태를 구분할 수 있습니다.

  3. 그리고 이 isLoggedIn state를 라우터에 prop으로 전달해줍니다. 그러면 라우터에서 지금 로그인이 되어 있는지 아닌지 판단하고 로그인 창과 홈 화면 중 하나를 띄워줄 수 있겠죠!

Router.js 중 로그인 여부를 판단하는 부분입니다. 삼항 연산자로 직관적으로 구현할 수 있습니다. isLoggedIn이 true면 Home 부분을, false면 로그인 페이지를 라우팅해줍니다.

로그인 문제 1

AuthService.currentUser는 현재 유저 정보를 받아오고, 이 값을 변수로 state로 저장해두었다가 router에서 null인지 아닌지 판단하는 것은 문제가 없어 보입니다.
하지만 맨 위의 코드에서 useEffect부분 없이 로그인 여부를 판단하려 한다면 언제나 로그아웃 상태가 되는 오류가 발생합니다.🤔

그 이유는 Firebase에서 유저 정보를 받아오는 데 시간이 필요하기 때문입니다.

isLoggedIn state가 설정되는 것은 해당 코드가 실행된 직후이지만 AuthService.currentUser을 통해 유저 정보를 요청하고, 그것을 받아오는 데는 시간이 걸립니다.
유저 정보를 받아오기 전에 isLoggedIn state 값은 null 로 초기화되고, 나중에 유저 정보를 받아와도 router는 언제나 로그아웃 상태라고 인식합니다.😢

또한 로그인 여부를 한 번만 확인하는 것이 아니라, 사용자가 로그아웃 버튼을 누를 때에도 로그인 상태를 바꾸고 로그인 여부를 확인해야 합니다.

실시간 로그인 여부 확인

다행이도 Firebase에는 이 문제를 해결할 수 있는 간단한 방법이 있습니다! 바로 useEffect함수 내에 콜백 함수로 AuthService.onAuthStateChanged를 넣어주는 것입니다.
그러면 AuthService.onAuthStateChanged가 side-effect로 마치 이벤트 리스너처럼 로그인 정보의 변화를 실시간으로 감지합니다.

위의 코드에서 해당 부분만 잘라 가져왔습니다.

AuthService.onAuthStateChanged가 로그인 변화를 감지하면 콜백 함수에 user 정보를 넘겨주게 됩니다. 이때 유저 정보가 있다면 로그인 상태로 바꾸고, null이라면 로그인 상태를 해제합니다(UserObj 부분은 뒤에서 설명하겠습니다.).

그리고 AuthService.onAuthStateChanged 함수가 유저 정보를 받아올 동안에 사용자에게 보여줄 로딩 화면이 필요합니다.
로딩 화면이 없다면 사용자는 로그인이 되어 있음에도 몇 초 동안은 로그인을 하라는 화면을 보게 될테니까요.

로딩 화면 만들기

로딩 화면을 만들기 위해서 App() 컴포넌트에 state로 init을 추가해줍니다. init의 초기값은 false로, 이 상태에서는 App() 컴포넌트가 router 대신 init 화면을 렌더링합니다.
AuthService.onAuthStateChanged() 함수의 콜백 함수가 user 정보를 받고 로그인 상태를 업데이트할 때 init state도 true로 업데이트 됩니다.

이후 Router가 로그인 여부에 따라 각각 화면을 렌더링합니다.

웹 프로그래밍을 배우면서 생소했던 부분 중 하나가 이러한 비동기 처리였습니다. 파이썬으로 프로그래밍을 처음 배웠을 때만 해도 코드는 당연히 위에서 아래로만 흐르는 것이었습니다.
앞으로는 익숙해져야 할 사고방식인 것 같습니다.

완성된 로그인 & 로그아웃 기능입니다!

로그인 & 회원가입 Form

로그인과 회원가입 기능을 구현하기 위해서 input 태그 네 개와 button 두 개를 만드는 것은 보기에 좋지 않을 것 같았어요.😒

그래서 로그인-회원가입 전환 버튼 기능을 만들어 두 개의 input 태그와 하나의 button으로 두 가지 기능을 모두 구현했습니다.

State 만들기

로그인 & 회원가입 기능을 구현하기 위해 먼저 state를 설정합니다.

사용자로부터 input 태그로 받은 email 주소와 비밀번호를 저장할 state를 만들어줍니다.

그리고 로그인인지 회원가입인지 판단할 수 있도록 newAccount라는 state를 만들어서 그 값이 true 라면 회원가입으로 진행될 수 있도록 했습니다.

로그인 & 회원가입 과정에서 생길 수 있는 오류를 잡아 어떤 오류인지 화면에 보여주기 위해 error state도 만들었습니다.

함수 구현

1. 토글

가장 간단한 함수입니다. 로그인-회원가입 간 전환 버튼에 연결되는 함수인데요, 현재 newAccount 값을 반대 값으로 업데이트 해줍니다.

setState 함수는 콜백 함수에게 첫 번째 인자로 기존값을 넘겨준다.

변수 앞에 "!"를 붙이면 반대 값이 된다.

이 간단한 함수를 구현하면서 알게된 유용한 두 가지 정보입니다.
만약 몰랐다면 if 문으로 현재 값을 확인하고 반대 값으로 바꿔주고... 아니라면 else 문에서 바꿔주는 더 긴 코드가 됐을 것 같습니다.

확실히 앞의 코드가 더 직관적이고 깔끔합니다.

2. email 주소, 비밀번호 입력 받아오기

그 다음으로 필요한 함수는 사용자가 input 태그에 입력한 내용을 받아와 state에 넣어주는 함수입니다.

이 함수를 만들 때 email과 비밀번호를 각각 담당하는 두 개의 함수를 만들지 않고 하나의 onChange 함수로 구현했는데, 이 과정이 저에게는 상당히 재밌었습니다.😆

하나의 함수로 두 가지 input을 다루기 위해 각각의 input 태그에 name="email" name="password" 속성을 줬습니다.

그리고 onChange 함수에서 받은 event 인자의 name을 이용해서 바뀌는 value가 email인지 비밀번호인지 판단합니다.

name 속성으로 태그를 구분하는 방법으로 같은 기능을 하는 함수를 하나로 묶었습니다. 이런 식으로 코드의 길이를 줄이고 재사용성을 높이는 방법을 찾는 습관을 들여야겠습니다.👍

3. Form 제출

input을 통해 받아두었던 email과 비밀번호 state 값을 제출하는 함수입니다.

이미 newAccount state로 토글을 만들어두었기 때문에 위의 onChange 함수와 같이 조건문을 통해 로그인&회원가입을 하나의 함수로 구현할 수 있습니다.

함수 내의 조건문에서 newAccounttrue이면 = 회원가입 상태라면 AuthService.createUserWithEmailAndPassword(email, password)를 통해 Firebase에 새 계정을 추가합니다.

마찬가지로, newAccountfalse이면 AuthService.signInWithEmailAndPassword(email, password)를 통해 Firebase에 로그인하게 됩니다.

로그인 & 회원가입 중에 생길 수 있는 오류(잘못된 비밀번호, 이미 존재하는 메일 주소 등)들을 catch 구문으로 잡아서 error state에 업데이트 해줍니다.

역시나 Firebase에 새 계정을 만들고 로그인 하는 과정은 시간이 걸리기 때문에 async, await를 사용하여 비동기로 처리했습니다.

소셜 로그인

소셜 로그인은 기존 계정으로 간편하게 가입할 수 있다는 장점 때문에 많은 웹 사이트에서 사용하고 있습니다.

저도 드위터에 소셜 로그인을 넣었는데, Firebase의 소셜 로그인 구현은 일반 로그인보다 간단했습니다...🙇‍♀️
그야말로 코드 한 줄로 구현할 수 있으니까요.

저는 드위터 소셜 로그인 제공자로 Google과 GitHub를 선택했습니다.

소셜 로그인을 수행하는 함수도 앞의 email 로그인 함수와 같은 논리로 작성했습니다.
늘 그래왔듯이, 소셜 로그인이 실패할 경우 보여줄 에러 메시지를 만들기 위해 state로 LocalLoginError를 만들어줍니다.

Google 버튼, GitHub 버튼에 각각 name 속성을 주고 버튼을 눌렀을 때 event로부터 받아온 name에 따라 다른 provider를 세팅해줍니다.

설정된 providerAuthService.signInWithPopup(provider)에 인수로 들어가면 팝업 창이 뜨면서 소셜 로그인이 진행됩니다.

로그인 과정 중 생긴 오류는 catch구문으로 넘어가 LocalLoginError state의 값으로 세팅됩니다.

AuthService.onAuthStateChanged 함수가 실시간으로 로그인 변화를 감지하기 때문에 로그인 후 화면 전환은 앞에서 만들어둔 코드가 알아서 실행됩니다.

다음 포스팅에서는 로그인 이후 홈 화면 제작에 대해 다루겠습니다!

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글