Redux를 코드로 파헤쳐보자🦉 (Cmarket Hooks)

돌리의 하루·2023년 2월 27일
0
post-thumbnail

redux구조와 순서를 다시 살펴보면,

처음에는, 상태 변경되어야 하는 이벤트가 발생할 때 상태에 대한 정보가 담긴
Action객체가 생성됩니다.
👇

이 Action객체는 Dispatch함수의 인자로 전달됩니다.
👇

Dispatch함수는 Action객체를 다시 Reducer함수로 전달해줍니다.
👇

Reducer함수는 Action객체의 값을 확인하고 그 값에 따라서 전역 상태 저장소 Store의 상태를 변경합니다.
👇

위의 순서가 모두 끝나고 상태가 변경된다면, React는 화면을 다시 렌더링합니다.

이렇게 과정이 진행되는데, 이걸 되새김질 하면서 코드를 살펴보려고합니다😌

export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId,
    },
  };
};

정말정말 단순하게 생각하기 위해서,
위 코드를 해체해봅시다.
itemId를 인자로 받는 addToCart는

{
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId,
    }

를 리턴합니다.
이걸 action 객체라고 볼 수 있겠죠!
addToCart는 객체를 생성하는 함수로, 액션생성자라고 불러줍니다.

이 객체를 dispatch로 전달해줍시다.

payload에는 왜 quantity, itemId를 담았느냐! 라고 한다면,
우리가 상태를 변경할 때 어떤 값들이 필요할까를 생각해보고 그에 맞는 값을 넣어주면 됩니다.

export default function ShoppingCart() {
  const state = useSelector((state) => state.itemReducer);
  const { cartItems, items } = state;
  const dispatch = useDispatch();
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) => el.itemId)
  );
  
const handleClick = (item) => {
    if (cartItems.map((el) => el.itemId).includes(item.id)) {
      dispatch(notify("이미 추가된 상품입니다."));
    } else {
      dispatch(addToCart(item.id));
      dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`));
    }
  };

dispatch 함수의 인자로 앞서 본 객체를 넣어줄 차례입니다.

handleClick이라는 함수에 item을 인자로 주고,
조건문으로 원래 있던 카트아이템과 itemId를 비교후 같은 itemId가 있으면,
(장바구니에 내가 새로 추가하려는 아이템이 있으면)

dispatch(notify("~"))로 이미 장바구니에 있으므로 추가할수 없다는 설명을 붙여주고,
없다면 dispatch로 addToCart를 인자로 받고 notify로 추가했다는 설명을 덧붙입니다.
(참고로 notify 함수는 따로 있습니다)

const itemReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TO_CART:
      return Object.assign({}, state, {
        //Object.assign을 쓰는 이유 : redux의 불변성때문
        cartItems: [...state.cartItems, action.payload], //원래있던 아이템목록인 ...state.cartItems에 action.payload를 더한다.
      });
      break;
      //...이하생략

이제 Dispatch함수가 전달해주는 Action객체를 reducer 함수로 전달해주는과정을 살펴봅시다!🧐

+)🙋🏼‍♀️번외로 reducer 함수 새로운 state를 만드는 순수함수이어야 한다는 점, 다들 알고계시죠?

💁🏻‍♂️ redux의 속성중 하나인 불변성을 지키려면 state를 수정해서는 안되는데,
이를 보통 spread 연산자를 사용하거나 Object.assign을 통해 새로운 객체를 만들어 리턴합니다.
🙆🏻‍♂️ shallow copy가 아닌 deep copy를 통해서 값 자체의 복사를 나타내 다른 주소값을 지니게 되면서, 불변성을 지키는겁니다!

다시 reducer 함수에 대해 살펴보면, 결국 reducer가 리턴을 해주는 값이 => 새로운 값이 될겁니다.

원래 상태를 state라는 변수로 가져오고, 원래 있던 cartItems에 새로운 값을 덮어씌워주기 위해서 action.payload를 붙여줄겁니다!😤

return Object.assign({}, state, {
        cartItems: [...state.cartItems, action.payload], 
      });

짜잔~ 잠깐! payload는 갑자기 여기서 왜 튀어나왔냐!

{
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId,
    }

액션생성자인 addToCart에서 받은 객체 안의 key인 payload를 보면 알 수 있습니다. 우리가 추가하려는 모습을 그대로 간직하고 있는 모습이죠! action객체이니 action.payload값으로 가져오는겁니다.

그렇다면 이제 빠르게
1. 카트 아이템을 삭제하기
2. 카트 수량 조정하기
의 코드도 복기해봅시다!🐳

카트 아이템 삭제하기

//index.js
export const removeFromCart = (itemId) => {
  return {
    type: REMOVE_FROM_CART,
    payload: {
      itemId,
    },
  };
};
//ShoppingCart.js
export default function ShoppingCart() {
  const state = useSelector((state) => state.itemReducer);
  const { cartItems, items } = state;
  const dispatch = useDispatch();
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) => el.itemId)
  ); 
  
const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId));
    dispatch(removeFromCart(itemId));
  };
}
//itemReducer.js
//switch문은 addToCart에 있는 코드와 동일합니다.
case REMOVE_FROM_CART:
      return Object.assign({}, state, {
        cartItems: state.cartItems.filter(
          (el) => el.itemId !== action.payload.itemId
        ),
      });
      break;

카트 수량 조정하기

//index.js
export const setQuantity = (itemId, quantity) => {
  return {
    //TODO
    type: SET_QUANTITY,
    payload: {
      quantity,
      itemId,
    },
  };
};
//ShoppingCart.js
export default function ShoppingCart() {
  const state = useSelector((state) => state.itemReducer);
  const { cartItems, items } = state;
  const dispatch = useDispatch();
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) => el.itemId)
  );
  
const handleQuantityChange = (quantity, itemId) => {
    dispatch(setQuantity(itemId, quantity));
  };
}
//itemReducer.js
//switch문은 addToCart에 있는 코드와 동일합니다.
case SET_QUANTITY:
      let idx = state.cartItems.findIndex(
        (el) => el.itemId === action.payload.itemId //여기서 itemId가 같은 인덱스를 찾아놨으니 밑에서 인덱스를 사용해서 map으로 새 배열 만들면 되겠다.
      );
      return Object.assign({}, state, {
        cartItems: state.cartItems.map((el, elidx) => {
          return idx === elidx ? action.payload : el;
        }),
      });
      break;
      
      default:
      return state;
profile
진화중인 돌리입니다 :>

0개의 댓글