이번 시간에는 리덕스 말고 리액트 전용 도구만을 이용하여 state를 관리하는 방법을 보여드리겠습니다!
context 폴더 생성 후 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에서 사용할 수 있습니다.
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 폴더는 쓰이지 않습니다! 삭제해도 됩니다!
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;
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를 통해서 데이터를 다른 컴포넌트에 배포할 수 있습니다!
이제 좋아요 기능을 추가해봅시다!
즐겨찾기 추가 기능을 위해 product-context.js 파일로 이동해보겠습니다!
= product-context.js
export const ProductsContext = React.createContext({
products: [],
toggleFav: (id) => {},
});
IDE와 자동완성을 위해 toggleFav를 추가해줍니다!
import { ProductsContext } from "../../context/products-context";
const ProductItem = (props) => {
const toggleFav = useContext(ProductsContext).toggleFav;
const toggleFavHandler = () => {
toggleFav(props.id);
};
이제 즐겨찾기 기능이 되는 것을 볼 수 있습니다.
이제 좋아요한 목록을 보는 페이지를 렌더링 해보겠습니다!
import { ProductsContext } from "../context/products-context";
const Favorites = (props) => {
const favoriteProducts = useContext(ProductsContext).products.filter(
(p) => p.isFavorite
);
가장 큰 단점은 고빈도 업데이트에는 그렇지 못합니다. 기본적으로 자주 변경되는 것은 성능의 측면에서 Context APi가 적합하지 않습니다.
다음으로 리덕스 대신 다른 대안을 살펴보겠습니다!
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 가 아닌 것만을 저장하도록 했다!
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 파일을 추가해주겠습니다!
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를 불러올 수 있습니다.