개발자로 전직하자

[TIL] 36일차(20240202) - [React] Context API, Redux 복습 본문

유데미X사람인 부트캠프/TIL

[TIL] 36일차(20240202) - [React] Context API, Redux 복습

전직희망중 2024. 2. 5. 16:07

목차

    #1 ContextAPI로 로그인 상태관리

    import React, { createContext, useMemo, useState } from "react";
    
    const initialAuthContextValue = {
      isLoggedIn: false,
      toggleLogIn: () => {},
      userId: null,
      setUserId: () => {},
    };
    export const AuthContext = createContext(initialAuthContextValue);
    
    export function AuthProvider({ children }) {
      const [isLoggedIn, setIsLoggedIn] = useState(false);
      const [userId, setUserId] = useState(null);
    
      console.log("리렌더 AuthProvider");
    
      const value = useMemo(
        () => ({
          isLoggedIn,
          toggleLogIn: () => setIsLoggedIn((prev) => !prev),
          userId,
          setUserId,
        }),
        [userId, isLoggedIn]
      );
    
      return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
    }
    // App.js
    
    import { BrowserRouter, Route, Routes } from "react-router-dom";
    import "./App.css";
    import { AuthProvider } from "./contexts/auth.context";
    import HomePage from "./pages/HomePage";
    
    function App() {
      console.log("리렌더 App");
      return (
        <BrowserRouter>
          <AuthProvider>
            <Routes>
              <Route path="/" element={<HomePage />} />
            </Routes>
          </AuthProvider>
        </BrowserRouter>
      );
    }
    
    export default App;
    // Header에서 로그인 여부에 따른 변화주기
    
    import React, { useContext } from "react";
    import { AuthContext } from "../contexts/auth.context";
    
    function Header() {
      const authContext = useContext(AuthContext);
      const { isLoggedIn, toggleLogIn } = authContext;
      console.log("리렌더 Header");
      return (
        <header className="h-14 border">
          <button onClick={toggleLogIn}>로그인 토글</button>
          <div>로그인 여부: 로그인 {!isLoggedIn && "안 "}됨</div>
        </header>
      );
    }
    
    export default Header;

    #2 Redux로 장바구니 기능 묘사

    // cart.reducer.js
    
    export const ADD_ITEM = "cart/addItem";
    export const REMOVE_ITEM = "cart/removeItem";
    
    export const addItemActionCreator = (payload) => ({ type: ADD_ITEM, payload });
    export const removeItemActionCreator = (payload) => ({
      type: REMOVE_ITEM,
      payload,
    });
    
    const initialState = {
      items: [],
      totalPrice: 0,
    };
    
    export default function cartReducer(state = initialState, action) {
      const newState = { ...state };
    
      if (action.type === ADD_ITEM) {
        const newItem = action.payload;
        const newItems = [...state.items, newItem];
    
        newState.items = newItems;
      } else if (action.type === REMOVE_ITEM) {
        const itemIdToRemove = action.payload;
        const newItems = state.items.filter((item) => item.id !== itemIdToRemove);
    
        newState.items = newItems;
      }
    
      return newState;
    }
    // user.reducer.js (immer활용)
    
    import { produce } from "immer";
    
    const initialState = {
      isLoggedIn: false,
      lastVisitedAt: null,
      grade: null,
      profile: {
        nickname: null,
        age: null,
        location: null,
        avatar: null,
        experiences: [],
      },
      friends: [],
      likedGoods: [],
    };
    
    const LOG_IN = "user/logIn";
    const LOG_OUT = "user/logOut";
    const UPDATE_EXPERIENCE = "user/updateExperience";
    const UPDATE_AVATAR_SRC = "user/updateAvatarSrc";
    const ADD_FRIEND = "user/addFriend";
    const REMOVE_FRIEND = "user/removeFriend";
    const LIKE = "user/like";
    const UNLIKE = "user/unlike";
    
    export const logIn = () => ({ type: LOG_IN });
    export const logOut = () => ({ type: LOG_OUT });
    export const updateExperience = (payload) => ({
      type: UPDATE_EXPERIENCE,
      payload,
    });
    export const updateAvatarSrc = (payload) => ({
      type: UPDATE_AVATAR_SRC,
      payload,
    });
    export const addFriend = (payload) => ({ type: ADD_FRIEND, payload });
    export const removeFriend = (payload) => ({ type: REMOVE_FRIEND, payload });
    export const like = (payload) => ({ type: LIKE, payload });
    export const unlike = (payload) => ({ type: UNLIKE, payload });
    
    export function userReducer(state = initialState, action) {
      if (action.type === UPDATE_EXPERIENCE) {
        const newState = produce(state, (draft) => {
          const { id, data } = action.payload;
          const indexToUpdate = draft.profile.experiences.findIndex(
            (experience) => experience.id === id
          );
          draft.profile.experiences[indexToUpdate] = data;
        });
        return newState;
      } else if (action.type === ADD_FRIEND) {
        const newState = produce(state, (draft) => {
          const newFriend = action.payload;
          draft.friends.push(newFriend);
        });
    
        return newState;
      } else if (action.type === REMOVE_FRIEND) {
        const friendIdToRemove = action.payload;
        const newState = {
          ...state,
          friends: state.friends.filter((friend) => friend.id !== friendIdToRemove),
        };
    
        return newState;
      }
    }
    // store
    
    import { configureStore } from "@reduxjs/toolkit";
    import cartReducer from "./reducers/cart.reducer";
    
    const store = configureStore({
      reducer: {
        cart: cartReducer,
      },
    });
    
    export default store;
    // App.js
    
    import { nanoid } from "nanoid";
    import { useDispatch, useSelector } from "react-redux";
    import "./App.css";
    import {
      addItemActionCreator,
      removeItemActionCreator,
    } from "./store/reducers/cart.reducer";
    
    function App() {
      const items = useSelector((state) => state.cart.items);
      const dispatch = useDispatch();
    
      const handleClickAddItem = () => {
        const id = nanoid();
        const amount = 1;
        const item = { id, amount };
        const action = addItemActionCreator(item);
    
        dispatch(action);
      };
    
      const handleClickRemoveItem = (itemId) => () => {
        const action = removeItemActionCreator(itemId);
    
        dispatch(action);
      };
    
      return (
        <div className="App">
          <button onClick={handleClickAddItem}>랜덤 상품 추가하기</button>
    
          <ul>
            {items.map((item) => (
              <li key={item.id}>
                <span>{item.id}</span>
                <button onClick={handleClickRemoveItem(item.id)}>제거하기</button>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    export default App;

    #3 Redux Toolkit 사용

    // user.reducer.js (toolkit사용)
    
    import { createSlice } from "@reduxjs/toolkit";
    
    const userSlice = createSlice({
      initialState: {
        isLoggedIn: false,
        lastVisitedAt: null,
        grade: null,
        profile: {
          nickname: null,
          age: null,
          location: null,
          avatar: null,
          experiences: [],
        },
        friends: [],
        likedGoods: [],
      },
      name: "user2222",
      reducers: {
        toggleLogin(state, action) {
          state.isLoggedIn = !state.isLoggedIn;
        },
        updateExperience(state, action) {
          const { id, data } = action;
          const experienceIndexToUpdate = state.profile.experiences.findIndex(
            (experience) => experience.id === id
          );
          state.profile.experiences[experienceIndexToUpdate] = data;
        },
      },
    });
    
    export const userReducer = userSlice.reducer;
    export const { toggleLogin, updateExperience } = userSlice.actions;
    // 각각이 액션크리에이터에요.

    #4 실습(과제) : 쇼핑몰 구현하기