리액트에서 Firebase 연동하기

임찬수·2021년 11월 19일
0

개요

리액트 기초 과제인 퀴즈퀴즈 작업중 Firestore를 사용하여 DB관리를 하려고 한다.
로컬에서 더미 데이터로 1차 테스트는 완료된 상태인데, Firebase를 처음 사용해봤고 강의를 한번 보고 기억하고, 이해하기가 어려우니 다시 한번 강의를 복습하면서 나에게 필요한 내용을 흐름과 사용법에 맞추어 정리해보고자 한다.

목표

  • Firebase 환경 설정
  • Firestore 설정
  • 리액트에서 Firebase 연동하기
  • 리액트에서 Firestore에 저장된 데이터 다루기

시작

Fisebase 프로젝트 생성하기

Fisestore DB 만들기

리액트에 Firebase 연동하기

  • 패키기 설치하기
    프로젝트에서 firebase를 연동하기 위해서 먼저 패키지를 설치해 주어야 한다.
yarn add firebase
  • SDK 코드 삽입
    SDK는 프로젝트 웹앱 등록 시에 생성되는데 해당 코드를 얻었다면 파이어베이스 초기화 과정에서 SDK를 삽입한다.

파이어베이스 초기화를 위한 코드 작성을 하기 위해서 스크립트 파일을 만들어 준다.

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "발급된 api 키",
  authDomain: "quizquiz-986fe.firebaseapp.com",
  projectId: "quizquiz-986fe",
  storageBucket: "quizquiz-986fe.appspot.com",
  messagingSenderId: "718714704774",
  appId: "1:718714704774:web:cd0ba3652bacc7acddd207",
  measurementId: "G-46NWHPDB9R"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);

내가 만든 firebase.js는 파이어베이스 객체를 만들기 위한 작업으로 나중에 Cloud Firestore를 import해서 사용할 수 있는 환경을 만들어 준 것이다.

  • Firestore 개발 환경 설정하기
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics"; // analytics를 사용하지 않는다면 삭제
import { getFirestore } from "firebase/firestore";

...

export const db = getFirestore() // App.js에서 사용 할 DB 객체 만들기

이렇게 Firebase를 연동한 후 Firestore 설정까지 끝내고나면 우리가 export한 db객체를 사용해 내 Cloud DB에 접근이 가능하다.

파이어스토어 다루기

  • 컴포넌트에서 데이터 접근하기
    앞서 정의한 firebase객체 db변수를 import하여
    firestore에서 제공하는 메서드를 활용하여 컨트롤 할 수 있다.
import { db } from './firebase'
import { collection, getDoc, getDocs } from 'firebase/firestore'

데이터에 접근하는 방식이 비동기 통신이기 때문에 async await를 활용하여 데이터를 온전히 받아오는게 가능하다.

  useEffect( async () => {
    const query = await getDocs(collection(db, 'question'))
    query.forEach(doc => {
      console.log(doc.id, doc.data())
    })

그렇다면 우리는 redux에서 관리하고 있는 상태값을 비동기로 값을 받아오거나 변경하는 등의 작업을 할때 액션 생성 함수를 작성 할 수 있게 해주는 미들웨어를 사용해야한다.

redux-thunk

redux-thunk는 위에서 설명한 바와 같이 액션 생성 함수를 작성 할 수 있게하여 액션이 발생하기 전에 조건을 주거나 어떠한 행동을 처리할 수있게 한다.

먼저 redux-thunk를 사용하기 위해서는 패키지를 설치해야한다.

yarn add reudx-thunk

패키지를 설치 하였다면 store를 생성하는 부분에서 옵셔널한 기능을 엮어 주어야 한다.

// configStore.js
import { createStore, combineReducers, applyMiddleware, compose  } from 'redux'
import thunk from 'redux-thunk'

import user from './modules/user'
import question from './modules/question'
import rank from './modules/rank'

const middlewares = [thunk]
const inhancer = applyMiddleware(...middlewares)
const rootReducer = combineReducers({user, question, rank})

const store = createStore(rootReducer, inhancer)

export default store

위 코드를 보면 applyMiddleware를 import하고 redux-thunk를 import해왔다.
createStore() 할때 reducer들과 옵셔널한 기능을 추가로 넣어줄수 있는데 이때 미들웨어의 묶을음 inhancer로 만들때 applyMiddleware를 사용해서 넣어 줄수 있다.

Firestore 데이터 다루기 상세

  • 모든 데이터 가져오기: getDocs()
export function loadCardFB() {
  return async (dispatch) => {
    const cardListData = await getDocs(collection(db, 'card'))
    const dbArr = []
    // 여기서 forEach는 Array 내장 메서드가 아니다.
    // firebase 객체에서 제공하는 메서드 임
    cardListData.forEach(card => {
      const cardObj = {
        id: card.id,
        ...card.data()
      }
      dbArr.push(cardObj)
    })

    dispatch(loadCard(dbArr))
  }
}
  • 하나의 데이터 추가하기: addDoc(), getDoc()
    데이터를 추가하는데 addDoc()과 getDoc()이 같이 쓰이는 이유는
    새롭게 생성된 데이터의 고유 id값을 참조하기 위해서 이다.
    addDoc()의 사용은 아래와 같이 쓰일 수 있다.
// 데이터 추가하기
const docRef = await addDoc(collection(db, '컬렉션명'), 추가할 데이터)

firestore에 추가되면서 자동으로 생성된 아이디를 참조하기 위해서 getDoc()을 사용한다.

const _doc = await getDoc(docRef)
_doc.id // 생성된 고유 id

마지막으로 액션 생성 함수를 살펴보면 다음과 같이 작성 할 수 있다.

export function addCardFB(newCard) {
  return async (dispatch) => {
    const docRef = await addDoc(collection(db, 'card'), newCard)
    const _doc = await getDoc(docRef)

    const card = {
      id: _doc.id,
      ...newCard
    }

    dispatch(addCard(card))
  }
}
  • 데이터 삭제하기: doc(), deleteDoc()
    삭제하고자 하는 데이터를 참조하기위해서 doc() 함수를 사용하는데 궁금한 점은 getDoc() 함수도 데이터를 추가할때 먼저 addDoc()으로 데이터를 추가한다음 getDoc()으로 그 데이터를 참조한다.

차이점을 확인해보고 싶어서 두 객체를 콘솔에 찍어보았다.

우선 두 함수가 리턴하는 객체의 차이는 _document의 유무이다.

getDoc()을 사용해서 추가한 데이터의 고유 id값을 꺼내오기 위해서는 실제 데이터베이스에 값을 불러올 자료가 필요하기 때문에 _document 속성이 존재한다.

하지만 doc()는 데이터베이스에 있는 필드를 삭제하기만 하면 되기때문에 아이디 값으로 조회에 delete만 해버리게 할 수 있는것으로 보인다.

나는 아래와 같이 데이터를 삭제하는 액션 함수를 정의 했다.

export function deleteCardFB(cardId) {
  return async (dispatch) => {
    const docRef = doc(db, 'card', cardId)
    console.log('[doc]', docRef)

    await deleteDoc(docRef)
    dispatch(deleteCard(cardId))
  }
}
  • 데이터 변경하기: updateDoc(), doc(), getState 파라미터
    업데이트 할때 등장했던 getState는 액션 함수 안에서 리덕스의 스토어 값을 참조 할 수 있다.
export function modifyCardFB(modifiedCard) {
  return async (dispatch, getState) => {
    const docRef = doc(db, 'card', modifiedCard.id)
    await updateDoc(docRef, modifiedCard.card) 

    const _cardList = getState().card.list
    const index = _cardList.findIndex(card => card.id === modifiedCard.id)
    const modifiedData = {
      index,
      ...modifiedCard
    }
    dispatch(modifyCard(modifiedData))
  }
}
profile
프론트엔드 개발자가 되기 위한 정보를 정리합니다.

0개의 댓글