추후에 검색 기능을 구현할 때와 사용자 인증 상태를 여러 컴포넌트에서 사용할 때 여러 컴포넌트에 props driling이 필요하기 때문에 이를 방지하고 내부적으로 Immer가 탑재 되어 상태의 불변성을 자동으로 유지
시켜 주어서 편리한 전역 상태관리 라이브러리인 redux-tookit을 사용하기로 결정하였다.

만들고자 하는 어플리케이션이 소규모 어플리케이션이라 다른 대안으로는 React hooks인 context api가 있지만 여러 상태를 관리 해야할 때 여러개의 Provider를 감싸주어야 되서 코드가 지저분해 질 수 있고 상태의 불변성을 유지해주는데 있어서 redux-toolkit이 더욱 편리하기 때문에 redux-toolkit을 선택하였다.

 

아직 백엔드 api가 만들어지지 않았기 때문에 dummy data를 사용하여 구현하였고 일단 상품 데이터를 조회해 오는 reducer만을 구현하였다. 추후에는 상품을 추가하는 reducer 등등을 추가하고 cart slice, auth slice 등도 추가적으로 구현할 것이다.

1. products slice 구현

src/store/slice/products.ts

import { PayloadAction, createSlice } from '@reduxjs/toolkit';

import { Product } from '../../common/types/products';

type State = {
  products: Product[];
};

const initialState: State = {
  products: [],
};

const productsSlice = createSlice({
  name: 'products',
  initialState,
  reducers: {
    getProducts: (state: State, action: PayloadAction<Product[]>) => ({
      products: [...action.payload],
    }),
  },
});

export const productsActions = productsSlice.actions;

export default productsSlice.reducer;

먼저 initialState의 타입을 지정해주고 reducer의 state, action 파라미터에 위와같이 각각 타입을 지정해주었다. 공식문서를 보니 state에는 initialState에 지정된 타입을, action에는 타입을 PayloadAction<>으로 지정한 뒤 제네릭에 액션으로 들어올 데이터의 타입을 지정해주면 되어서 그렇게 해주었다.

2. store 구현

src/store/index.ts

import { configureStore } from '@reduxjs/toolkit';

import productsSlice from './slice/products';

const store = configureStore({
  reducer: {
    products: productsSlice,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

store에 productsSlice를 넣어주고 state를 사용할 컴포넌트에서 useSelector 이용 시에 타입을 지정해줘야되기 때문에 위 코드와 같이 RootState 타입을 만들어 주고 export해주었다.

그리고 useDispatch에 필요한 타입을 AppDispatch로 정의해주고 export해주었다. (참고: 공식문서 )

 

Usage With TypeScript | Redux Toolkit

 

redux-toolkit.js.org

3. Provider에 store 전달

src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';

import App from './App';
import NotFound from './common/components/NotFound';
import Home from './home/page/Home';
import Electronics from './electronicProducts/page/Electronics';
import Clothes from './clothes/page/Clothes';
import store from './store';

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    errorElement: <NotFound />,
    children: [
      { index: true, path: '/', element: <Home /> },
      { path: '/electronics', element: <Electronics /> },
      { path: '/clothes', element: <Clothes /> },
    ],
  },
]);

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement,
);
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <RouterProvider router={router} />
    </Provider>
  </React.StrictMode>,
);

store를 react-redux의 Provider 컴포넌트에 전달해주었다.

4. clothes컴포넌트에서 dispatch와 useSelector로 활용

import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';

import { Product } from '../../common/types/products';
import Products from '../../common/components/products/Products';
import { AppDispatch } from '../../store';
import { productsActions } from '../../store/slice/products';
import { RootState } from '../../store';

const Clothes: React.FC = () => {
  const dispatch = useDispatch<AppDispatch>();
  const products = useSelector((state: RootState) => state.products.products);

  useEffect(() => {
    dispatch(productsActions.getProducts(data));
  }, []);

  return <Products products={products} />;
};

export default Clothes;

store를 만들어줄 때 만들었던 RootState타입과 AppDispatch타입을 각각 useSelector와 useDispatch에 타입으로 지정해주었다.

+ Recent posts