[React] useContext와 useReducer 복습

jiseong·2022년 1월 7일
0

T I Learned

목록 보기
162/291

카트에 아이템을 담을 때 전역적으로 state를 관리하고 싶어서 useContext와 useReducer를 사용했다.

  • useContext 사용 이유
    Provider로 감싼 하위 컴포넌트에 props를 전달하지 않아도 state에 접근을 할 수 있게 된다. (Props Drilling를 피할 수 있다.)

  • useReducer 사용 이유
    useContext로 내려받은 state에 대해서 state관리를 할 수 있게 된다. (state관리 재사용성에 유리)

라우터 구조

해당 어플리케이션의 라우터 구조는 다음과 같다.

export default function App() {
  return (
    <StateContextProvider>
      <Routes />
    </StateContextProvider>
  );
}

function Routes() {
  return (
    <BrowserRouter>
      <Route exact path="/">
        <OrderList />
      </Route>
      <Route exact path="/checkout">
        <Checkout />
      </Route>
      <Route exact path="/shopping-cart">
        <ShoppingCart />
      </Route>
      <Route exact path="/shopping">
        <Shopping />
      </Route>
    </BrowserRouter>
  );
}

생성

import React, { useContext, useReducer } from "react";

const reducer = (state, action) => {
    switch(action.type){
        case 'ADD_CART':
            const newProduct = {
                id: action.product.id,
                title: action.product.title,
                price: Number(action.product.price)
            };
            
            return {
                carts: [
                    ...state.carts,
                    newProduct
                ]
            };
        case 'RESET_CART':
            return {
                ...state,
                carts: []
            };
        default:
            throw new Error("reducer error");
    }
};

const StateContext = React.createContext(null);

const initState = {
  carts: [],
};

export function StateContextProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initState);
  const addProduct = (product) => dispatch({ type: 'ADD_CART', product });
  const resetCart = () => dispatch({ type: 'RESET_CART'});
  
  return (
    <StateContext.Provider value={{state, addProduct, resetCart}}>{children}</StateContext.Provider>
  );
}

접근

StateContext 하위에 있는 컴포넌트들은 useStateContext를 사용하여 value={{state, addProduct, resetCart}}에 대해서 접근할 수 있게 된다.

export function useStateContext() {
  return useContext(StateContext);
}

예시

예를 들면 카트에 담긴 상품목록에 접근하는 예시는 다음과 같다.

import { useStateContext } from "./StateContext";

export default function Checkout() {
  const {
    state: { carts },
    resetCart,
  } = useStateContext();

  return (
    <PageLayout>
      <Navigation />
      <Header>
        <h4>주문할 물품</h4>
      </Header>
      {carts.length === 0 && 
    	<Message>{'주문할 상품이 없습니다.'}</Message>
      }
      {carts.length > 0 &&
      <>
          <Carts carts={carts} />
          <CheckoutForm 
            onSubmit={handleSubmit}
            success={success}
            error={error} 
            loading={loading}
          />
      </>
      }
    </PageLayout>
  );
}

상태관리

useReducer를 사용하여 미리 정의해놓은 액션을 이용하면 전역적으로 적절한 상태관리를 할 수 있다.

예시

예를 들면 카트에 상품을 담는 예시는 다음과 같다.

import { useStateContext } from "./StateContext";

export default function Shopping() {
    const [products, setProducts] = useState([]);
    const {state, addProduct} = useStateContext();

    useEffect(() => {
        getAllProducts().then((res) => setProducts(res));
    }, []);
    
  function handleClick(selectedProduct){
    /* 중복적인 아이템에 대한 핸들링 필요 */
    
    const newProduct = {
        id: selectedProduct.id,
        title: selectedProduct.title,
        price: Number(selectedProduct.price)
    }
    
    // 카트에 새로운 아이템 추가
    // === dispatch({ type: 'ADD_CART', newProduct }); 
    addProduct(newProduct);
  }
    
  return (
   <PageLayout>
      <Navigation />
      <Header>
        <h4>상품 목록</h4>
      </Header>
      <div>
          {products.map(product => 
                <li key={product.id}>
                    <p>{product.title}</p>
                    <p>$ {product.price}</p>
                    <button onClick={() => handleClick(product)}>추가</button>
                </li>
          )}
       </div>
    </PageLayout>
  )
}

최종

느낀점

useContext를 다시 복습하면서 느낀점은 관련된 상태가 하나라도 변화하면 하위 컴포넌트들이 다시 리렌더링되는데 불필요한 리렌더링이 되지 않도록 방어가 필요할 것 같다.

0개의 댓글