1. 데이터 배열 관리: filter와 splice의 차이점
JavaScript에서 배열 데이터를 조작할 때 filter와 splice는 특정 요소를 제거하는 데 사용될 수 있지만, 동작 방식과 원본 배열에 미치는 영향에서 근본적인 차이가 있다. 특히 React와 Redux 환경에서는 이 차이를 이해하는 것이 매우 중요하다.
1.1 filter
정의:
filter메서드는 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열을 생성하여 반환한다.특징 (불변성 유지): 원본 배열을 직접 수정하지 않고(Non-destructive), 조건에 맞는 요소로만 구성된 복사본을 반환한다.
프로젝트 적용 예시 (
boardsSlice.ts): 게시판을 삭제하는 리듀서 로직에서filter는 불변성을 유지하며 상태를 업데이트하는 데 사용된다.// src/store/slices/boardsSlice.ts deleteBoard: (state, { payload }: PayloadAction<TDeleteBoardAction>) => { // boardArray에서 삭제할 boardId와 일치하지 않는 요소만으로 새로운 배열을 만든다. // 이를 통해 원본 state.boardArray를 직접 수정하지 않고 상태를 업데이트한다. state.boardArray = state.boardArray.filter( (board) => board.boardId !== payload.boardId ); },React/Redux에서의 중요성: React의 상태나 Redux의 스토어는 불변성(Immutability)을 유지해야 한다.
filter는 원본을 훼손하지 않고 새로운 배열을 반환하므로, 상태를 안전하게 업데이트하고 React가 변경 사항을 감지하여 UI를 리렌더링하게 만드는 가장 이상적인 방법이다.
1.2 splice
정의:
splice메서드는 원본 배열의 요소를 삭제, 교체하거나 새 요소를 추가하여 원본 배열 자체를 수정한다.특징 (원본 배열 변경): 원본 배열을 직접 변경하며(Destructive), 제거된 요소를 배열 형태로 반환한다.
프로젝트 적용 예시 (
boardsSlice.ts): 드래그 앤 드롭으로 리스트 내 태스크 순서를 변경하는sort리듀서에서splice가 사용된다.// src/store/slices/boardsSlice.ts sort: (state, { payload }: PayloadAction<TSortAction>) => { // 같은 리스트 내에서 순서만 변경될 경우 if (payload.droppableIdStart === payload.droppableIdEnd) { // 해당 리스트를 찾는다. const list = state.boardArray[payload.boardIndex].lists.find( (list) => list.listId === payload.droppableIdStart ); // 'splice'를 사용하여 원래 위치에서 태스크를 잘라내고, const card = list?.tasks.splice(payload.droppableIndexStart, 1); // 새로운 위치에 잘라낸 태스크를 삽입한다. list?.tasks.splice(payload.droppableIndexEnd, 0, ...card!); } // (다른 리스트로 이동하는 로직 생략) },Redux Toolkit과
splice: 전통적인 Redux에서는splice처럼 원본 상태를 직접 수정하는 행위가 금지된다. 하지만 본 프로젝트에서 사용하는 Redux Toolkit의createSlice는 내부적으로 Immer.js 라이브러리를 사용한다. Immer는 개발자가 상태를 직접 수정하는 것처럼 보이는 코드(예:splice,push)를 작성하더라도, 이를 감지하여 실제로는 안전하게 불변성을 유지하는 새로운 상태 객체를 생성해준다. 따라서createSlice의reducers내부에서는splice와 같은 메서드를 예외적으로 사용하여 코드를 더 직관적으로 작성할 수 있다.
2. Redux 상태 관리의 핵심: dispatch 함수
Redux는 예측 가능한 방식으로 애플리케이션의 상태를 중앙에서 관리하는 라이브러리이다. dispatch 함수는 이 상태를 변경하는 유일한 수단으로서 Redux의 핵심 원칙을 지키는 데 결정적인 역할을 한다.
2.1 dispatch의 역할
dispatch는 액션(Action) 객체를 리듀서(Reducer)에 전달하는 함수이다.- Redux 스토어의 상태는 직접 수정할 수 없으며, 오직
dispatch를 통해서만 상태 변경을 요청할 수 있다.
2.2 상태 변경 플로우
이벤트 발생: 사용자가 UI와 상호작용한다 (예: '새로운 게시판 등록' 아이콘 클릭).
dispatch호출:SideForm.tsx의handleClick이벤트 핸들러 함수 내에서dispatch가addBoard,addLog액션과 함께 호출된다.// src/components/SideForm/SideForm.tsx const handleClick = () => { if (inputText) { // 'addBoard' 액션을 디스패치하여 스토어에 새 게시판을 추가하도록 요청한다. dispatch( addBoard({ board: { boardId: uuidv4(), boardName: inputText, lists: [] }, }) ); // 'addLog' 액션을 디스패치하여 활동 로그를 추가하도록 요청한다. dispatch( addLog({ logId: uuidv4(), logMessage: `게시판 등록: ${inputText}`, logAuthor: isAuth ? email : "guest", logTimestamp: String(Date.now()), }) ); } };리듀서 실행:
dispatch에 의해 전달된 액션은 스토어에 등록된boardsReducer와loggerReducer로 각각 전달된다.상태 업데이트: 각 리듀서는 액션의
type에 따라 분기 처리하고,payload데이터를 사용하여 새로운 상태 객체를 반환한다.UI 리렌더링: Redux 스토어의 상태가 변경되면,
useSelector훅 등을 통해 해당 상태를 구독하고 있던 모든 React 컴포넌트가 자동으로 리렌더링된다.
이러한 단방향 데이터 흐름은 애플리케이션의 상태 변화를 예측 가능하고 추적하기 쉽게 만들어 디버깅과 유지보수를 용이하게 한다.
3. Firebase를 이용한 로그인/로그아웃 기능 플로우
본 프로젝트는 Firebase Authentication을 사용하여 간편하고 안전한 소셜 로그인 및 로그아웃 기능을 구현했다. 전체적인 인증 플로우는 다음과 같다.
3.1 useAuth 훅
Redux 스토어의
user상태(email,id)를 구독하는 커스텀 훅이다.email상태의 존재 여부를 통해 현재 로그인 상태인지를 나타내는isAuth불리언 값을 계산하여 반환한다.이 훅을 통해 모든 컴포넌트는 인증 상태에 쉽게 접근하고 UI를 조건부로 렌더링할 수 있다.
// src/hooks/useAuth.ts import { useTypedSelector } from "./redux"; export function useAuth() { const { id, email } = useTypedSelector((state) => state.user); return { isAuth: !!email, // email이 존재하면 true, 아니면 false email, id, }; }
3.2 로그인 플로우
로그인 요청: 사용자가 로그인(
FiLogIn) 아이콘을 클릭하면BoardList.tsx의handleLogin함수가 실행된다.signInWithPopup호출: Firebase의signInWithPopup함수가 호출되어 Google 로그인 팝업창을 화면에 띄운다.// src/components/BoardList/BoardList.tsx const handleLogin = () => { signInWithPopup(auth, provider) .then((result) => { console.log("로그인 성공:", result.user.displayName); alert("로그인되었습니다."); }) .catch((error) => { console.error("로그인 중 에러 발생:", error); }); };onAuthStateChanged감지:App.tsx에 등록된onAuthStateChanged리스너가 이 인증 상태의 변화를 감지하고, Firebase로부터 받은user객체를 인자로 받아 콜백 함수를 실행한다.Redux 상태 업데이트: 콜백 함수 내에서
dispatch(setUser({ email, id }))가 호출되어 Redux 스토어의user상태가 업데이트된다.// src/App.tsx (useEffect 내부) onAuthStateChanged(auth, (user) => { if (user) { // 사용자가 있으면 user 정보로 Redux 상태를 업데이트한다. dispatch(setUser({ email: user.email!, id: user.uid })); } else { // 사용자가 없으면 Redux 상태를 초기화한다. dispatch(removeUser()); } });UI 업데이트:
useAuth훅이 상태 변경을 감지하고 새로운isAuth값(true)을 반환하면,BoardList.tsx의 UI는 "환영합니다!" 메시지 대신 "{email}님 안녕하세요!"와 로그아웃 아이콘을 표시하도록 자동으로 리렌더링된다.
3.3 로그아웃 플로우
로그아웃 요청: 사용자가 로그아웃(
GoSignOut) 아이콘을 클릭하면handleLogout함수가 실행된다.signOut호출: Firebase의signOut함수가 호출되어 Firebase 서버 및 클라이언트의 인증 세션을 종료시킨다.// src/components/BoardList/BoardList.tsx const handleLogout = () => { signOut(auth) .then(() => { alert("로그아웃되었습니다."); }) .catch((err) => { console.error("로그아웃 실패:", err); }); };onAuthStateChanged감지:onAuthStateChanged리스너가 다시 한번 상태 변화(로그아웃)를 감지하고, 이번에는user객체가null이므로 콜백 함수가dispatch(removeUser())를 호출한다.UI 업데이트:
isAuth가false로 변경됨에 따라 UI는 다시 로그인 아이콘과 "환영합니다!" 메시지를 표시하도록 리렌더링된다.