리덕스를 리액트 훅으로 바꾸기

맛없는콩두유·2022년 9월 29일
0

프로젝트 시작 및 리덕스를 교체해야하는 이유

이번 시간에는 리덕스 말고 리액트 전용 도구만을 이용하여 state를 관리하는 방법을 보여드리겠습니다!

대안: 컨텍스트 API 사용

context 폴더 생성 후 products-context.js 파일을 만들겠습니다!

  • products-context.js
import React, { useState } from "react";

export const ProductsContext = React.createContext({
  products: [],
});

export default (props) => {
  const [productsList, setProductsList] = useState([
    {
      id: "p1",
      title: "Red Scarf",
      description: "A pretty red scarf.",
      isFavorite: false,
    },
    {
      id: "p2",
      title: "Blue T-Shirt",
      description: "A pretty blue t-shirt.",
      isFavorite: false,
    },
    {
      id: "p3",
      title: "Green Trousers",
      description: "A pair of lightly green trousers.",
      isFavorite: false,
    },
    {
      id: "p4",
      title: "Orange Hat",
      description: "Street style! An orange hat.",
      isFavorite: false,
    },
  ]);
  return (
    <ProductsContext.Provider value={{ products: productsList }}>
      {props.children}
    </ProductsContext.Provider>
  );
};

productsList가 바뀔 떄 마다 즉, 이 state를 업데이트할 때마다 결과적으로 컴포넌트가 재구성 됩니다. 그럴 때마다 Provider가 새로운 값을 가지게 되고 그러면 Provider를 따르는 모든 자식들도 새로운 값을 가지게 됩니다.

이 Provider를 index.js에서 사용할 수 있습니다.

  • index.js
import React from "react";
import ReactDOM from "react-dom/client";

import { BrowserRouter } from "react-router-dom";

import "./index.css";
import App from "./App";
import ProductsProvider from "./context/products-context";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <ProductsProvider>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </ProductsProvider>
);

이제 더 이상 store 폴더는 쓰이지 않습니다! 삭제해도 됩니다!

  • Containers/ Products.js
import React, { useContext } from "react";

import ProductItem from "../components/Products/ProductItem";
import { ProductsContext } from "../context/products-context";
import "./Products.css";

const Products = (props) => {
  const productList = useContext(ProductsContext).products;

  return (
    <ul className="products-list">
      {productList.map((prod) => (
        <ProductItem
          key={prod.id}
          id={prod.id}
          title={prod.title}
          description={prod.description}
          isFav={prod.isFavorite}
        />
      ))}
    </ul>
  );
};

export default Products;
  • Component/ ProductItem.js
import React from "react";
import Card from "../UI/Card";
import "./ProductItem.css";
// import { toggleFav } from "../../store/actions/products";

const ProductItem = (props) => {
  const toggleFavHandler = () => {
    // dispatch(toggleFav(props.id));
  };

  return (
    <Card style={{ marginBottom: "1rem" }}>
      <div className="product-item">
        <h2 className={props.isFav ? "is-fav" : ""}>{props.title}</h2>
        <p>{props.description}</p>
        <button
          className={!props.isFav ? "button-outline" : ""}
          onClick={toggleFavHandler}
        >
          {props.isFav ? "Un-Favorite" : "Favorite"}
        </button>
      </div>
    </Card>
  );
};

export default ProductItem;

이로서 useContext를 통해서 데이터를 다른 컴포넌트에 배포할 수 있습니다!

이제 좋아요 기능을 추가해봅시다!

컨텍스트 API로 즐겨찾기 추가

즐겨찾기 추가 기능을 위해 product-context.js 파일로 이동해보겠습니다!

= product-context.js

export const ProductsContext = React.createContext({
  products: [],
  toggleFav: (id) => {},
});

IDE와 자동완성을 위해 toggleFav를 추가해줍니다!

  • component/ProductItem.js
import { ProductsContext } from "../../context/products-context";

const ProductItem = (props) => {
  const toggleFav = useContext(ProductsContext).toggleFav;
  const toggleFavHandler = () => {
    toggleFav(props.id);
  };

이제 즐겨찾기 기능이 되는 것을 볼 수 있습니다.

이제 좋아요한 목록을 보는 페이지를 렌더링 해보겠습니다!

  • containers/ Favorites.js
import { ProductsContext } from "../context/products-context";
const Favorites = (props) => {
  const favoriteProducts = useContext(ProductsContext).products.filter(
    (p) => p.isFavorite
  );

컨텍스트 API요약(리덕스 대신 사용하지 않는 이유)

가장 큰 단점은 고빈도 업데이트에는 그렇지 못합니다. 기본적으로 자주 변경되는 것은 성능의 측면에서 Context APi가 적합하지 않습니다.

다음으로 리덕스 대신 다른 대안을 살펴보겠습니다!

store로 커스텀 훅 시작하기

  • hooks=store/ store.js
import { useState, useEffect } from "react";

let globalState = {};
let listeners = [];
let actions = {};

const useStore = () => {
  const setState = useState(globalState)[1];
  useEffect(() => {
    listeners.push(setState);

    return () => {
      listeners = listeners.filter((li) => li !== setState);
    };
  }, [setState]);
};

useState를 통해 setState에 두 번쨰 globalState를 저장 후 useEffect를 통해서 listners에 push하고 return으로 filter를 이용해 setState 가 아닌 것만을 저장하도록 했다!

스토어 훅 완성하기

  • hooks-store / store.js
import { useState, useEffect } from "react";

let globalState = {};
let listeners = [];
let actions = {};

export const useStore = () => {
  const setState = useState(globalState)[1];

  const dispatch = (actionIdentifier, payload) => {
    const newState = actions[actionIdentifier](globalState, payload);
    globalState = { ...globalState, ...newState };

    for (const listener of listeners) {
      listener(globalState);
    }
  };

  useEffect(() => {
    listeners.push(setState);

    return () => {
      listeners = listeners.filter((li) => li !== setState);
    };
  }, [setState]);

  return [globalState, dispatch];
};

export const initStore = (userActions, initialState) => {
  if (initialState) {
    globalState = { ...globalState, ...initialState };
  }
  actions = { ...actions, ...userActions };
};

콘크리트 저장소 만들기

hooks-store안에 products=store.js 파일을 추가해주겠습니다!

  • products=store.js
import { initStore } from "./store";

const configureStore = () => {
  const actions = {
    TOGGLE_FAV: (curState, productId) => {
      const prodIndex = curState.products.findIndex((p) => p.id === productId);
      const newFavStatus = !curState.products[prodIndex].isFavorite;
      const updatedProducts = [...curState.products];
      updatedProducts[prodIndex] = {
        ...curState.products[prodIndex],
        isFavorite: newFavStatus,
      };
      return { products: updatedProducts };
    },
  };
  initStore(actions, {
    products: [
      {
        id: "p1",
        title: "Red Scarf",
        description: "A pretty red scarf.",
        isFavorite: false,
      },
      {
        id: "p2",
        title: "Blue T-Shirt",
        description: "A pretty blue t-shirt.",
        isFavorite: false,
      },
      {
        id: "p3",
        title: "Green Trousers",
        description: "A pair of lightly green trousers.",
        isFavorite: false,
      },
      {
        id: "p4",
        title: "Orange Hat",
        description: "Street style! An orange hat.",
        isFavorite: false,
      },
    ],
  });
};

export default configureStore;

커스텀 저장소 사용하기

커스텀 저장소를 사용하기 위해서 index.js로 이동해 ProductsProvider를 없앱시다!

Products.js에 useContext 대신에 usestore를 불러올 수 있습니다.

  • containers/ Products.js
profile
하루하루 기록하기!

0개의 댓글