dispatch / state 변경

이윤희·2025년 1월 31일

React/Next 일기

목록 보기
34/52

지난번에 chatGPT한테 물어봤던거 (dispatch 로 상태변경하는 거)
상태 함수 안 쓰고 그냥 했다가 또 에러남

Redux에서는 모든 state는 반드시 Slice의 reducers 안에서 관리해야 하며, 변경할 때는 reducers의 함수를 dispatch를 통해 호출해야 한다

다시 얌전히 reducers에 함수 만들러 갔다.

오늘의 목표는 +버튼을 누르면 해당 id를 가진 제품의 count 를 증가시키는 것
뺑이 시작...


Cart.js

import { useSelector, useDispatch } from "react-redux";
import { changeName, changeAge } from "../store/userSlice.js";

function Cart() {
  let state = useSelector((state) => {
    return state;
  });
  let dispatch = useDispatch();
  let cart = useSelector((state) => state.cart);

// 중략
<tbody>
{cart.map((_, i) => {
  return (
    <tr key={i}>
      <td>{cart[i].id}</td>
      <td>{cart[i].name}</td>
      <td>{cart[i].count}</td>
      <td>
        <button
          onClick={() => {
            /* 클릭하면 이 id 번째의 수량을 1 증가시킨다 */
          }}
        >
          {" "}
          +{" "}
        </button>
      </td>
    </tr>
  );
})}
</tbody>

store.js

import { configureStore, createSlice } from "@reduxjs/toolkit";
import user from "./store/userSlice.js";
import cart from "./store/userSlice.js";

export default configureStore({
  reducer: {
    user: user.reducer,
    stock: stock.reducer,
    cart: cart.reducer,
  },
});

../store/userSlice.js

import { createSlice } from "@reduxjs/toolkit";

let cart = createSlice({
  name: "cart",
  initialState: [
    { id: 0, name: "White and Black", count: 2 },
    { id: 2, name: "Grey Yordan", count: 1 },
  ],
});

let user = createSlice({
  name: "user", 
  initialState: { name: "kim", age: 20 }, 
  reducers: {
    changeName(state) {
    },
    changeAge(state, a) {
      state.age += a.payload;
    },
  },
});

export default user;

export let { changeName, changeAge } = user.actions;

map 을 썼으니까 i를 파라미터로 넘기면 될거같은데 그걸 받아 쓰는 쪽에서 어떻게 해야 되는지 모르겠다.

GPT한테 물어봄


GPT

🛠 Redux를 사용하여 + 버튼을 누르면 해당 상품의 count 증가시키기

지금 구현하고자 하는 목표는 Redux 상태에 저장된 cart 배열에서 특정 id를 가진 상품의 count 값을 증가시키는 것입니다. 이를 단계별로 쉽게 설명해드릴게요.


🚀 목표

  1. Redux 상태 (cart) 안에 있는 상품 리스트에서 특정 상품을 찾아야 합니다.
  2. 해당 상품의 count 값을 증가해야 합니다.
  3. Redux Reducer 함수 (increaseCount)를 사용하여 상태를 변경합니다.
  4. 버튼 클릭 이벤트에서 해당 상품의 id를 Redux 액션으로 전달해야 합니다.

✅ 단계별 설명 및 코드 수정

🔹 1단계: cartSlice.js에서 increaseCount 액션을 만들기

우리는 Redux의 createSlice를 사용하여 cart 상태를 관리하고 있습니다.
여기에 상품의 count를 증가시키는 increaseCount 리듀서 함수를 추가해야 합니다.

📌 수정된 cartSlice.js

import { createSlice } from "@reduxjs/toolkit";

let cart = createSlice({
  name: "cart",
  initialState: [
    { id: 0, name: "White and Black", count: 2 },
    { id: 2, name: "Grey Yordan", count: 1 },
  ],
  reducers: {
    increaseCount(state, action) {
      // payload에 받은 id를 가진 상품 찾기
      let index = state.findIndex((item) => item.id === action.payload);
      if (index !== -1) {
        state[index].count += 1; // 해당 상품의 count 증가
      }
    },
  },
});

// 액션과 리듀서를 내보내기
export default cart;
export let { increaseCount } = cart.actions;

🔎 설명

✔️ increaseCount(state, action)

  • action.payload버튼을 클릭한 상품의 id이 들어옴.
  • state.findIndex((item) => item.id === action.payload)를 사용하여 해당 상품이 cart 배열에서 몇 번째(index)인지 찾음.
  • state[index].count += 1;을 실행하여 count 값을 증가시킴.

item이 뭔데

ㄴ item은 state 배열에 있는 각각의 요소(객체)
🔹 findIndex() 내부에서는 item이 state 배열의 각 요소를 순차적으로 가리킨다
🔹 item.id === action.payload는 현재 요소(item)의 id가 액션에서 전달된 id와 같은지 확인하는 조건
🔹 이 조건을 만족하는 첫 번째 요소의 인덱스(index)를 반환

잠만!! 근데 왜 item.id가 action.payload 와 같아야하지?

dispatch(increaseCount( cart[i].id ))
여기서 파라미터로 cart[i].id를 넘겨줬으니까
actions.payload에는 cart[i].id가 들어있음!!!



🔹 2단계: store.js에서 cartSlice.js를 올바르게 연결

이제 우리가 만든 increaseCount 액션을 store.js에 연결해야 합니다.

📌 수정된 store.js

import { configureStore } from "@reduxjs/toolkit";
import user from "./store/userSlice.js";
import cart from "./store/cartSlice.js"; // cartSlice.js에서 import

export default configureStore({
  reducer: {
    user: user.reducer,
    cart: cart.reducer, // cart 리듀서를 store에 등록
  },
});

🔎 설명

✔️ cart.reducerstore.js에 등록함으로써, Redux에서 cart 상태를 관리할 수 있게 됩니다.


🔹 3단계: Cart.js에서 increaseCountdispatch로 실행

이제 Cart.js에서 increaseCount 액션을 버튼 클릭 시 실행하도록 수정해야 합니다.

📌 수정된 Cart.js

import { useSelector, useDispatch } from "react-redux";
import { increaseCount } from "../store/cartSlice.js"; // import 추가

function Cart() {
  let cart = useSelector((state) => state.cart);
  let dispatch = useDispatch();

  return (
    <tbody>
      {cart.map((item) => (
        <tr key={item.id}>
          <td>{item.id}</td>
          <td>{item.name}</td>
          <td>{item.count}</td>
          <td>
            {/* 버튼 클릭 시 해당 상품의 id를 increaseCount에 전달 */}
            <button onClick={() => dispatch(increaseCount(item.id))}>
              +
            </button>
          </td>
        </tr>
      ))}
    </tbody>
  );
}

export default Cart;

🔎 설명

✔️ useDispatch()를 사용하여 Redux 액션을 실행하는 함수를 가져옴.
✔️ <button onClick={() => dispatch(increaseCount(item.id))}>+</button>
→ 버튼을 클릭하면 해당 상품의 idincreaseCount 액션의 payload로 전달하여 실행.


🎯 실행 흐름 정리

1️⃣ 사용자가 + 버튼을 클릭
2️⃣ 클릭한 상품의 idincreaseCount(item.id)로 Redux 액션에 전달됨
3️⃣ Redux cartSlice.jsincreaseCount 리듀서에서 cart 배열을 찾아 해당 id를 가진 상품의 count를 증가
4️⃣ Redux store가 업데이트됨
5️⃣ Cart.js에서 useSelector(state => state.cart)를 통해 변경된 상태를 다시 렌더링


🎉 최종적으로 동작하는 기능

+ 버튼을 클릭하면 해당 idcount가 1씩 증가합니다!
✅ Redux의 상태 관리 원칙을 준수하면서, cart 상태를 직접 수정하지 않고, dispatch를 통해 변경하는 구조입니다.
✅ 이제 Redux를 활용한 cart의 상태 관리가 정상적으로 작동합니다! 🚀


💡 추가적으로 공부하면 좋은 개념

  • Redux ToolkitcreateSlice 동작 방식
  • findIndex()를 사용하여 배열에서 특정 요소 찾기
  • useSelectoruseDispatch의 역할
  • Redux의 store.js에서 여러 slice를 관리하는 방법

이제 이해가 되셨나요? 😃 혹시 더 궁금한 점이 있다면 언제든지 질문하세요!


여기서

increaseCount(state, action) {
      // payload에 받은 id를 가진 상품 찾기
      let index = state.findIndex((item) => item.id === action.payload);
      if (index !== -1) {
        state[index].count += 1; // 해당 상품의 count 증가
      }

이 부분이 이해가 안 가서 다시 물어봤다.

왜 state[index].count 인가요? state.findIndex()는 왜 하는 건가요?



GPT

🔍 state.findIndex()state[index].count += 1의 역할

Redux에서 상태(state)를 변경할 때, 직접 state의 특정 요소를 수정할 수 없습니다. 배열 내에서 특정 상품을 찾아 그 count 값을 증가시키는 과정을 이해하는 것이 중요합니다.

이제 increaseCount 함수가 어떻게 동작하는지 단계별로 상세히 설명드릴게요.


📌 1. findIndex()의 역할

우리는 cart라는 배열을 Redux에서 관리하고 있습니다.
이 배열 안에는 여러 개의 객체(상품 정보)가 들어 있습니다.

initialState: [
  { id: 0, name: "White and Black", count: 2 },
  { id: 2, name: "Grey Yordan", count: 1 },
],

예를 들어, 위의 배열에서 id === 2인 상품의 count 값을 증가시키려면 해당 상품이 배열의 몇 번째(index)에 있는지 알아야 합니다.

findIndex()를 사용하는 이유

👉 Redux의 상태는 배열이고, 특정 id를 가진 객체의 count를 증가하려면 해당 객체가 배열의 몇 번째(index)인지 알아야 합니다.
👉 findIndex()는 조건을 만족하는 첫 번째 요소의 배열 위치(index)를 반환합니다.

let index = state.findIndex((item) => item.id === action.payload);

위 코드를 실행하면,

  • action.payload는 버튼 클릭 시 전달된 id 값입니다.
  • state.findIndex()state 배열에서 item.id === action.payload인 요소가 몇 번째에 있는지 찾습니다.
  • 찾으면 그 요소의 인덱스(index)를 반환하고,
  • 찾지 못하면 -1을 반환합니다.

✅ 예제

다음 cart 배열이 있다고 가정해 보겠습니다.

let cart = [
  { id: 0, name: "White and Black", count: 2 },
  { id: 2, name: "Grey Yordan", count: 1 },
];

여기서 action.payload = 2라고 하면:

let index = cart.findIndex((item) => item.id === 2);
console.log(index); // 출력: 1

id: 2인 상품은 cart[1]에 있기 때문에 index = 1이 됩니다.


📌 2. state[index].count += 1의 역할

이제 상품의 index를 찾았으니, 해당 상품의 count 값을 증가시킬 수 있습니다.

if (index !== -1) {
  state[index].count += 1; // 해당 상품의 count 증가
}

여기서 state[index]index 위치에 있는 상품 객체를 의미합니다.
즉, cart[1]을 찾았다면 cart[1].count를 증가시키는 것입니다.

✅ 예제

let cart = [
  { id: 0, name: "White and Black", count: 2 },
  { id: 2, name: "Grey Yordan", count: 1 },
];

let index = cart.findIndex((item) => item.id === 2);
if (index !== -1) {
  cart[index].count += 1;
}

console.log(cart);
/*
출력 결과:
[
  { id: 0, name: "White and Black", count: 2 },
  { id: 2, name: "Grey Yordan", count: 2 } // count가 1 증가됨
]
*/

이렇게 해서 특정 id를 가진 상품의 count 값만 증가할 수 있습니다.


📌 3. findIndex() 대신 map()을 사용하면 안 되나요?

비슷한 기능을 하는 map()을 사용하면 어떻게 될까요?

let newCart = state.map((item) => {
  if (item.id === action.payload) {
    return { ...item, count: item.count + 1 };
  }
  return item;
});

가능은 하지만, findIndex()state[index].count += 1을 사용하는 방식이 더 직관적이고 효율적입니다.
왜냐하면:
1. map()은 배열의 모든 요소를 순회하면서 새로운 배열을 반환합니다. → 불필요한 연산이 많아짐.
2. findIndex()는 첫 번째로 일치하는 요소를 찾으면 즉시 중단합니다. → 연산이 더 효율적.
3. Redux Toolkit에서는 Immer를 사용하기 때문에 state[index].count += 1처럼 직접 변경하는 것처럼 써도 자동으로 불변성을 유지해 줍니다.


🎯 최종 정리

코드역할
state.findIndex((item) => item.id === action.payload)id 값을 가진 상품이 배열의 몇 번째(index)에 있는지 찾음
state[index].count += 1찾은 indexcount 값을 증가
if (index !== -1)상품이 존재하는 경우에만 증가하도록 방지

📌 즉, findIndex()를 사용해 특정 상품의 index를 찾고, 해당 상품의 count를 증가시키는 것!
✔️ state[index].count += 1을 사용하면 Redux Toolkit이 자동으로 불변성을 유지하기 때문에 상태 관리가 간편해집니다.


+ 덤 : 느슨한 비교, 엄격한 비교

if (idx !== -1) {
        state[idx].count++;
      }

이 부분 작성할 때

if (idx != -1) { 
        state[idx].count++;
      }

이렇게 작성하니까 오류라고 빨간 줄 뜬다.
!= 가 안 되고 !== 이어야만 하는 이유가 뭐지?

GPT

좋은 질문입니다! 😊

JavaScript에서는 !=!==의 차이가 데이터 타입(type)을 검사하는 방식에서 발생합니다.


1️⃣ != (느슨한 비교, Loose Equality)

!=타입 변환을 허용하는 비교 연산자입니다.
즉, 비교하는 두 값이 타입이 다르면 자동으로 변환한 후 비교합니다.

console.log(0 != "0");  // false  (문자열 "0"이 숫자 0으로 변환됨)
console.log(false != 0); // false  (false가 0으로 변환됨)
console.log(null != undefined); // false  (둘 다 비슷한 "없음" 값으로 취급됨)

2️⃣ !== (엄격한 비교, Strict Equality)

!==타입 변환 없이 값을 직접 비교합니다.
즉, 데이터 타입이 다르면 무조건 true가 나옵니다.

console.log(0 !== "0");  // true  (숫자와 문자열이 다름)
console.log(false !== 0); // true  (불리언과 숫자가 다름)
console.log(null !== undefined); // true  (null과 undefined는 다름)

3️⃣ !=를 쓰면 안 되는 이유

🔹 findIndex()가 찾지 못했을 때 -1을 반환하는데, -1숫자 타입(Number)입니다.
🔹 !=는 타입 변환을 하기 때문에, 만약 state[idx].countundefined라면 비교 과정에서 예상치 못한 동작이 발생할 수 있습니다.
🔹 Redux에서는 명확한 타입 비교(===, !==)를 권장하기 때문에 !==를 사용해야 합니다.

📌 안전한 코드 작성 원칙
JavaScript에서는 항상 === 또는 !==를 사용하는 것이 좋습니다.
(!===는 타입 변환이 발생하므로 예기치 않은 버그가 발생할 수 있음)


4️⃣ 결론

🚀 != 대신 !==를 사용해야 하는 이유:
1. 타입 변환을 방지하여 코드의 예측 가능성을 높임
2. findIndex()의 반환값 -1숫자이므로, 정확한 비교를 위해 !== 사용
3. Redux 및 최신 JavaScript 코드 스타일에서는 엄격한 비교(!==, ===)를 권장


혼자 작성해봤는데

reducers: {
increaseCount(state, action) {
	let idx = state.findIndex( (item) => item.id === actions.id);
      if(idx !== -1){
        state[id].count++;
      }

미묘하게 다틀림
바보다... actions.id 가 아니라 actions 안에 들어있는 내용물(payload) 를 가져왔어야 했고
[id]는 idx 를 하려고 했는데 헷갈려서 id 라고 쓴거;;;
action 인데 actions라고 함
어이없네

0개의 댓글