카테고리 없음

[50일차]filter와 splice, Redux의 dispatch, 로그인과 로그아웃

PARKpatchnotes 2025. 11. 20. 16:07

1. 데이터 배열 관리: filtersplice의 차이점

JavaScript에서 배열 데이터를 조작할 때 filtersplice는 특정 요소를 제거하는 데 사용될 수 있지만, 동작 방식과 원본 배열에 미치는 영향에서 근본적인 차이가 있다. 특히 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)를 작성하더라도, 이를 감지하여 실제로는 안전하게 불변성을 유지하는 새로운 상태 객체를 생성해준다. 따라서 createSlicereducers 내부에서는 splice와 같은 메서드를 예외적으로 사용하여 코드를 더 직관적으로 작성할 수 있다.


2. Redux 상태 관리의 핵심: dispatch 함수

Redux는 예측 가능한 방식으로 애플리케이션의 상태를 중앙에서 관리하는 라이브러리이다. dispatch 함수는 이 상태를 변경하는 유일한 수단으로서 Redux의 핵심 원칙을 지키는 데 결정적인 역할을 한다.

2.1 dispatch의 역할

  • dispatch액션(Action) 객체를 리듀서(Reducer)에 전달하는 함수이다.
  • Redux 스토어의 상태는 직접 수정할 수 없으며, 오직 dispatch를 통해서만 상태 변경을 요청할 수 있다.

2.2 상태 변경 플로우

  1. 이벤트 발생: 사용자가 UI와 상호작용한다 (예: '새로운 게시판 등록' 아이콘 클릭).

  2. dispatch 호출: SideForm.tsxhandleClick 이벤트 핸들러 함수 내에서 dispatchaddBoard, 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()),
          })
        );
      }
    };
  3. 리듀서 실행: dispatch에 의해 전달된 액션은 스토어에 등록된 boardsReducerloggerReducer로 각각 전달된다.

  4. 상태 업데이트: 각 리듀서는 액션의 type에 따라 분기 처리하고, payload 데이터를 사용하여 새로운 상태 객체를 반환한다.

  5. 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 로그인 플로우

  1. 로그인 요청: 사용자가 로그인(FiLogIn) 아이콘을 클릭하면 BoardList.tsxhandleLogin 함수가 실행된다.

  2. 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);
        });
    };
  3. onAuthStateChanged 감지: App.tsx에 등록된 onAuthStateChanged 리스너가 이 인증 상태의 변화를 감지하고, Firebase로부터 받은 user 객체를 인자로 받아 콜백 함수를 실행한다.

  4. 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());
      }
    });
  5. UI 업데이트: useAuth 훅이 상태 변경을 감지하고 새로운 isAuth 값(true)을 반환하면, BoardList.tsx의 UI는 "환영합니다!" 메시지 대신 "{email}님 안녕하세요!"와 로그아웃 아이콘을 표시하도록 자동으로 리렌더링된다.

3.3 로그아웃 플로우

  1. 로그아웃 요청: 사용자가 로그아웃(GoSignOut) 아이콘을 클릭하면 handleLogout 함수가 실행된다.

  2. signOut 호출: Firebase의 signOut 함수가 호출되어 Firebase 서버 및 클라이언트의 인증 세션을 종료시킨다.

    // src/components/BoardList/BoardList.tsx
    
    const handleLogout = () => {
      signOut(auth)
        .then(() => {
          alert("로그아웃되었습니다.");
        })
        .catch((err) => {
          console.error("로그아웃 실패:", err);
        });
    };
  3. onAuthStateChanged 감지: onAuthStateChanged 리스너가 다시 한번 상태 변화(로그아웃)를 감지하고, 이번에는 user 객체가 null이므로 콜백 함수가 dispatch(removeUser())를 호출한다.

  4. UI 업데이트: isAuthfalse로 변경됨에 따라 UI는 다시 로그인 아이콘과 "환영합니다!" 메시지를 표시하도록 리렌더링된다.