프로젝트 배포 : Google Firebase
나의 개발 일기, 에러 해결 일지, 공부 기록 등을 저장하기 위한 웹 개발 프로젝트. 전역 상태 관리 라이브러리는 Context API를 사용하였으며 React.js, Google Firebase 등의 기본 개념을 이해하고 실제 사용 능력을 증진시킬 목적으로 기획되었음.
프로젝트의 각 페이지 컴포넌트들은 pages 폴더를 생성하고 각각 전용 폴더를 만들어 관리하였다. 페이지 내부에서 렌더링될 컴포넌트들은 components로 분리하여 관리하였으며 파이어베이스 사용을 위한 설정 파일은 configs 폴더에 저장하였다. 프로젝트에서 작동될 기능들은 원래 개별 기능마다 하나의 js 파일로 제작하려 했으나 기능 분류에 따라서 동일한 Context API Reducer를 공유해야하기 때문에 회원 기능들은 개별 파일로 분리되어 있으며, 게시글 기능과 댓글 기능은 각각 하나의 파일에 통합하여 customhooks에 관리하는 식으로 기능을 구현하였다.
CSS는 이전까지와 달리 module.css 개념을 적용하여 하나의 css 파일이 확실하게 js 파일에 종속됨으로써 클래스명의 중복 문제로 스타일 속성이 중첩될 수 있는 문제를 방지하였다.
프로젝트의 기능이 동작하는 로직은 어떻게 되어야 할까? 프로그래밍에서는 동일한 기능을 구현하더라도 사람마다 그 방식이 판이하게 다른 경우가 많다.
내 프로젝트의 기능 동작 기본 구조는 다음과 같다.
pages, components의 프론트는 기능을 동작해야하는 경우가 발생하면, customhooks에 구현되어있는 백엔드 함수를 호출하고, DB에 데이터를 저장하거나 조회하는 등의 동작을 수행한 다음 백에서 프론트로 돌려줄 값이 존재한다면 Context API를 통해 State로 전달해준다.
import { createContext, useEffect, useReducer } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { appAuth } from '../configs/firebase';
const AuthContext = createContext();
const authReducer = (state, action) => {
switch (action.type) {
case 'login':
return { ...state, user: action.payload };
case 'logout':
return { ...state, user: null };
case 'isAuthReady':
return { ...state, user: action.payload, isAuthReady: true };
default:
return state;
}
}
const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
user: null,
isAuthReady: false
});
useEffect(() => {
const unsubscribe = onAuthStateChanged(appAuth, (user) => {
dispatch({ type: 'isAuthReady', payload: user })
});
return unsubscribe;
}, []);
// console.log(state);
return (
<AuthContext.Provider value={{ ...state, dispatch }}>
{children}
</AuthContext.Provider>
);
};
export { AuthContext, AuthContextProvider };
Context API 기능을 제공하는 AuthContextProvider 컴포넌트. Provider 함수의 State 관리 및 제공 범위는 프로젝트 전체로 index.js에서 AuthContextProvider 컴포넌트가 최상위 컴포넌트가 되도록 코드를 작성하였다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import './ResetCSS.css';
import App from './App';
import { AuthContextProvider } from './context/AuthContext';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<AuthContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</AuthContextProvider>
// </React.StrictMode>
);
원래는 하나의 Reducer를 AuthContextProvider 내부에서만 사용하였는데, 여러 기능을 구현하다보니 분리할 필요가 발생하였다. 결국 회원 기능 쪽 Reducer와 게시글 기능, 댓글 기능의 Reducer가 각각 존재하는 상황이 되었는데 게시글과 댓글 쪽은 State를 전역으로 뿌릴 필요는 없어서 Provider를 만들지 않았다. 다른 취준생분의 프로젝트를 보니 복수의 Reducer를 만들어 State를 별개로 관리하고 Provider 컴포넌트를 여럿 사용하는 개념 자체는 이상한게 아니지만 내 구현 방식은 전역 변수 관리라는 의의를 침해하는 게 아닌가 생각되기도 한다. (Reducer가 하나의 컴포넌트에만 연결되어있긴 한데, 다른 곳에서도 호출은 가능하니까 괜찮은 듯도 싶고..)
const storeReducer = (state, action) => {
switch (action.type) {
case 'isPending':
return { isPending: true, document: null, success: false, error: null }
case 'addDoc':
return { isPending: false, document: action.payload, success: true, error: null }
case 'getDoc':
return { isPending: false, document: action.payload, success: true, error: null }
case 'updateDoc':
return { isPending: false, document: action.payload, success: true, error: null }
case 'deleteDoc':
return { isPending: false, document: null, success: true, error: null }
case 'error':
return { isPending: false, document: null, success: false, error: action.payload }
default:
return state
};
};
switch-case 문에 왜 break 키워드가 없을까?
-> 원래 문법대로면 switch-case 문에는 반드시 break 키워드를 사용해주어야 한다. 그렇지 않으면 해당되는 case 이후에 있는 모든 코드를 실행시켜버리기 때문. 그런데 switch-case 문이 함수 내부에 위치하고 있을 경우 break 키워드 대신 return 키워드를 사용해도 문제가 없다. 함수 내에서 return 키워드는 그 즉시 해당 함수를 종료시키는 역할을 하기 때문.
switch-case 문과 스프레드... 연산자.
-> 기존에 존재하고 있는 State 값이 있을 때, 전체 값을 바꿔야 한다면 그냥 값을 대입해버리면 되지만 일부 값만을 변경하고 나머지를 유지해야할 상황이 있다. 이럴 때 사용할 수 있는 방법이 스프레드... 연산자의 사용이다. 기존에 존재하는 값을 우선 가져온 다음 수정이 필요한 값만 따로 변경하면 기존의 값을 유지하면서 특정 값만을 수정할 수 있다.