차근차근 따라하는 redux-toolkit 기초 가이드라인

Daisy🌼·2022년 9월 9일
1

내가 자꾸 까먹어서 작성해보는 redux-toolkit 기초 가이드라인 😅
(본 포스팅은 【한글자막】 React 완벽 가이드 with Redux, Next.js, TypeScript를 수강한 내용을 바탕으로 작성했습니다.)

⭐️ toolkit 그거 어떻게 하는건데

redux-toolkit의 핵심 개념은 기존의 Redux와 큰 차이가 없으나, 사용법이 조금 다르기 때문에 처음에 배울 때 종종 어려움을 겪었습니다. 그 결과 주석을 달지 않고서는 돌아서면 까먹는 지경에 이르렀기에(...) 저는 코드에 하나씩 주석을 달아가며 이해해보려고 합니다. 비록 완벽하지는 않지만 redux-toolkit을 활용할 때 가끔씩 찾아와 리마인드하고 익힐 수 있는 기초 가이드라인이 되었으면 좋겠습니다.😊

⭐️ 나만의 toolkit 가이드라인

1. 구현하고 싶은 기능을 생각해봅니다.

ex) 버튼을 눌러 장바구니 컴포넌트가 토글(보여지거나 사라지거나)되도록 하고 싶다!

2. 어떤 state와 액션이 필요한지 정합니다.

ex) 버튼을 클릭할 때마다 true 또는 false로 업데이트되는 state가 필요하다.
	-> 장바구니 컴포넌트가 보여지는지 여부를 잘 나타나도록 isCartVisible이라는 이름은 어떨까?

ex) 그리고 액션은 단순히 click이라는 이름보다는 toggle을 사용하는 게 더 적합할 것 같다.

3. 정했다면 우선 slice 작업을 해봅시다.

// 📂 src/store/ui-slice.js

// 슬라이스를 생성하는 함수를 불러옵니다.
import { createSlice } from "@reduxjs/toolkit";

// 생성되는 슬라이스 객체를 uiSlice라는 이름으로 저장합니다.
const uiSlice = createSlice({
  name: 'ui',
	// state의 초기값을 지정해줍니다.
	// 처음에는 카트 컴포넌트를 보여주고 싶지 않기 때문에 초기값을 false로 설정해주었습니다.
  initialState: { isCartVisible: false },
	// dispatch로 받아오는 액션에 따라 실행하는 리듀서 메서드입니다.
  reducers: {
	// toggle이라는 액션을 받아오면 아래와 같이 state를 변경하겠군요!
    toggle(state) {
      state.isCartVisible = !state.isCartVisible;
    }
  }
});

// 아까 createSlice로 슬라이스 객체를 생성해 uiSlice라는 변수에 저장한다고 했죠?
// 이 슬라이스 객체를 콘솔에 확인해보면 actions라는 프로퍼티가 있습니다.
// 여기에 바로 액션 생성자 함수가 담겨있습니다. 이걸 외부에서 쓸 수 있도록 export해줍니다.
export const cartActions = uiSlice.actions;
/* export const { toggle } = uiSlice.actions; */
// 👆 또는 이렇게 구조분해할당으로 action 이름을 바로 가져다쓸 수 있게 할 수도 있습니다.
// 나중에 스토어의 index.js에서 가져다 쓰기 때문에 슬라이스도 export 해줍니다.
export default cartSlice;

4. 슬라이스 작업이 끝났다면 스토어를 만듭니다.

// 📂 src/store/index.js

// 스토어를 생성하는 함수
import { configureStore } from "@reduxjs/toolkit";
// export한 uiSlice를 불러옵니다.
import uiSlice from "./cart-slice.js";

// 스토어를 생성해서 store라는 변수에 저장합니다.
const store = configureStore({
	// 여기 reducer 프로퍼티가 있습니다. 리덕스를 사용하면 하나의 애플리케이션에는
	// 하나의 스토어만 존재해야 한다는 원칙이 있기 때문에, 전역 상태를 관리하는 하나의
	// 리듀서를 정의합니다. 대신 하나의 객체에 여러 리듀서들을 담습니다.
  reducer: { ui: uiSlice.reducer }
})

export default store;

5. 만든 스토어를 공급해줍니다.

// 📂 src/index.js

import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
// Provider 컴포넌트로 감싸서 스토어를 공급해줍니다.
root.render(<Provider store={store}><App /></Provider>);

6. state 변경을 일으키는 컴포넌트에서 작업합니다.

  • 앞서 버튼을 클릭했을 때 장바구니가 토글되는 기능을 구현한다고 가정했습니다.

  • 따라서 state의 변경을 일으키는 컴포넌트는 바로 버튼입니다.

  • CartButton이라는 컴포넌트에서 작업할 준비를 합니다.

// 📂 src/components.CartButton.js
const CartButton = () => {

  return (
    <button>
      <span>장바구니 보기</span>
    </button>
  );
};

export default CartButton;

7. 이를 위해서는 액션과 디스패치가 필요합니다.

  • 액션은 state변경을 일으키는 액션과 정보를 나타내는 역할을 하고,

  • 디스패치는 액션을 리듀서에 전달하는 역할을 합니다.

// 📂 src/components.CartButton.js

// 액션을 불러옵니다.
import { uiActions } from '../../store/ui-slice';
// 액션 객체를 전달할 dispatch를 생성하는 useDispatch 함수를 불러옵니다.
import { useDispatch } from 'react-redux';

const CartButton = () => {

// dispatch 함수를 생성해줍니다.
const dispath = useDispatch();

// '장바구니 보기' 버튼을 클릭하면(onClick) state가 변경되도록 하고 싶습니다.
// 따라서 클릭 이벤트를 처리하는 toggleCartHandler라는 이벤트 핸들러를 하나 정의합니다.
// 이벤트 핸들러를 만든 다음, 핸들러 안에서 액션 객체를 생성해 dispatch해줍니다!
const toggleCartHandler = ()=> {
  // 액션 생성자 함수를 호출해 액션을 생성하고, 생성된 액션을 dispatch합니다.
	dispatch(uiActions.toggle());
}

  return (
    <button onClick={toggleCartHandler}>
      <span>장바구니 보기</span>
    </button>
  );
};

export default CartButton;

8. state 변경에 영향을 받는 컴포넌트로 가서 작업합니다.

  • '장바구니 보기' 버튼을 클릭해 state가 변경되면 일어나는 일에 대해 생각해봅니다.

  • 여기서는 장바구니가 보여지고 사라지도록 하겠습니다.

  • 따라서 장바구니 컴포넌트인 Cart 컴포넌트로 가서 업데이트된state를 적용해줄 예정입니다.

import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';

function App() {
  return (
    <Layout>
      {isCartVisible && <Cart />} // 논리 연산자 &&을 쓰는 이유가 궁금하다면 아래를 참고해주세요!
    </Layout>
  );
}

export default App;

🙋‍♀️ &&가 여기서 왜 나오나요?
👉 논리 연산자를 활용해 표현식을 평가하는 단축 평가입니다. 논리 연산자라고 한다면 ||&&, ?이 있죠? 그 중에서 ||&&을 사용해 표현식을 전부 평가하지 않고 도중에 생략한다음, 연산 결과를 결정한 피연산자를 반환하는 것을 의미합니다.

🙋‍♀️ 조금 더 설명해주세요...
👉 예를 들어 OR 연산자인 ||을 사용하면 하나만 true여도 식이 true로 평가되기 때문에, 첫 번째 피연산자 즉 앞에 있는 게 true면 뒤에 있는 건 평가하지 않습니다. 반면 AND 연산자인 &&을 사용하면 모든 피연산자가 true여야만 식이 true로 평가되기 때문에, 첫 번째 피연산자 즉 앞에 있는게 false면 뒤에 있는 건 평가하지 않고 식을 false로 평가합니다. &&을 사용한 식이 true로 평가받기 위해서는 적어도 첫 번째 피연산자가 true여야 합니다.

🙋‍♀️ 여기서는 어떻게 쓰인건가요?
이를 위 코드에 적용해보면, 만약 isCartVisible 이라는 state가 false면 하나라도 false이기 때문에 평가가 끝납니다. 따라서 논리 연산 결과를 결정한 앞에 것만 반환되고, 뒤에 있는Cart컴포넌트가 반환되지 않습니다. (렌더링되지 않습니다.) 만약 isCartVisible이 true면 뒤에 있는 게 연산 결과를 결정합니다. 그리고 Cart 컴포넌트가 존재하므로 평가식이 true로 평가될 것이며, 연산 결과를 결정한 뒤에 있는 피연산자, 즉 Cart 컴포넌트가 반환됩니다. (렌더링됩니다.) 결론은 컴포넌트를 렌더링할 때 앞에 state를 조건으로 걸어주고 있는 것입니다. state가 true일 때만 컴포넌트가 보여지도록! 삼항연산자로 조건부 렌더링하는 것보다 훨씬 간편합니다.

9. state 변경 사항을 조회합니다.

  • useSelectorstate를 반환하는 함수입니다.

  • 특정 state만 관심있는 경우, 어떻게 하는 지 코드를 보며 설명하겠습니다.

// 📂 src/App.js

import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';
import { useSelector } from 'react-redux';

function App() {
// useSelector는 전체 state를 받습니다. 이 중에서 관심있는 state만 가져와 저장해야 합니다.
// 여기서는 isCartVisible이라는 state만을 가져오고 싶습니다. 어떻게 하면 될까요?
// 바로 스토어에서 우리가 관심있는 state가 저장되어있는 slice에 매칭되는 key를 적어주면 됩니다.
// 한 칸 더 아래 코드 블럭에서 좀 더 자세히 설명해보겠습니다.
const showCart = useSelector((state) => state.ui.isCartVisible)
  return (
    <Layout>
      {isCartVisible && <Cart />}
    </Layout>
  );
}

export default App;
// 📂 src/store/index.js

import { configureStore } from "@reduxjs/toolkit";
import cartSlice from "./cart-slice,js";

// 여기 스토어가 있습니다.
const store = configureStore({
	// reuducer 프로퍼티 안에 객체가 있는데, 우리가 관심있는 state가 저장되어 있는
	// 슬라이스를 찾고, 그 슬라이스에 붙은 key명을 써주면 됩니다.
	// 현재 우리는 isCartVisible이라는 state를 가져다 쓰고 싶고,
	// 이는 uiSlice라는 슬라이스에 정의되어 있습니다.
	// 그리고 그 슬라이스는 스토어에 ui라는 이름으로 지정되어있네요. 
	// 여기있는 key 즉 ui로 접근해 우리가 원하는 state를 변수에 담아서 쓰면 됩니다.
	// 이제 바로 위에서 봤던 코드블럭으로 다시 올라가 이해해봅시다.
  reducer: { ui: uiSlice.reducer }
})

export default store;

🤔 정말로 스토어의 reducer 객체 안에 있는 key와 슬라이스를 매칭하는건지 확인하고 싶으면, key 이름을 마음대로 바꾸어 봅시다. 둘 중 하나라도 이름이 다르면 제대로 실행되지 않는다는 것을 알 수 있었습니다.


정리해보자면, redux-toolkit 역시 기존의 Redux와 마찬가지로 복잡하고 다양한 상태 변화를 예측 가능하도록 하나의 스토어에서 관리합니다. 사용법이 조금씩 다르지만, 저는 아래 3가지 항목이 redux-toolkit, 나아가 상태를 관리할 때 꼭 고려할 만한 사항이라고 생각합니다.

  • 어떤 state가 왜 필요한 지
  • 어떤 이벤트와 액션에 의해 state가 변경되는 지
  • state의 변경으로 인해 어떤 컴포넌트가 영향을 받을지

이러한 명확한 체크리스트를 가지고 작업한다면, 안 그래도 복잡한 상태 관리를 조금 더 명확하게 할 수 있을 것 같습니다!

profile
커피와 재즈를 좋아하는 코린이 | 좋은 글 좋은 코드를 쓰고 싶습니다

0개의 댓글