redux 프로젝트 생성하기

최경락 (K_ROCK_)·2022년 6월 6일
0

개요

  • 현재 TS로 TODO 앱을 만들면서 상태를 props drilling 을 이용하여 내려주고 있는데, 상태관리를 redux 를 이용하여 전역에서 관리하고자 한다.
  • 아직 redux에 대한 이해가 부족해 바로 프로젝트에 적용시키는 것 보단 간단한 카운터 앱을 만들어보는 것으로 포문을 열기로 했다.
  • 공식문서 상에서 redux 만 단독으로 설치하는 것 보단 redux-toolkit(RTK) 을 권장하여, RTK 를 설치하여 진행했다.
    → 그러나 RTK 을 설치하기는 하지만, 기존 redux 문법을 사용하여 작성한다.

redux 프로젝트 세팅

  • 프로젝트를 세팅은 두가지 방법으로 나눌 수 있을 것 같다.
    1. 기존의 프로젝트에 적용하기

      npm i react-redux @reduxjs/toolkit
      
      # 바닐라 redux 가 toolkit에 포함되어 있어 따로 설치할 필요가 없다.
    2. 새 프로젝트를 만들어 create-react-app 의 template 사용하기.

      npx create-react-app [파일경로] --template redux
      # JS 환경이라면 위의 명령어로 설정가능
      
      npx create-react-app [파일경로] --template redux-typescript
      # TS 환경이라면 위의 명령어로 설정가능

      → CRA의 템플릿을 이용할 경우 react-redux, @reduxjs/toolkit 라이브러리가 기본적으로 설치되며, 간단한 예제 코드가 App 컴포넌트에 작성되어 있다.

Redux 란?

  • 상태관리 라이브러리 중 하나로, store, reducer, action 으로 나뉘며 각각의 역할은 아래와 같다.

store

  • 상태들이 관리되는 하나의 저장소이다.
  • store에 상태를 객체 형식으로 저장해두고, 필요한 경우 store를 통해 해당 상태를 가져 올 수 있다.

action

  • reducer 에게 어떤 행동을 할지 전달하는 역할을 한다.
  • reduceractiontype을 기준으로 상태를 변화시키는 로직을 수행하게 된다.

reducer

  • action 을 기반으로 새로운 값을 만들어 상태를 갱신하는 역할을 한다.
  • if 혹은 switch-case 문법을 이용하여 actiontype 에 따라 상태를 변화시키는 로직을 실행한다.

연습해보기

// 파일 구조

|- src
  |- App.tsx
  |- redux
    |- store.ts
    |- counter
      |- actions.ts
      |- reducer.ts
      |- types.ts

actions 만들기

//actions.ts

const plusCounter = () => {
  return {
    type: "PLUS_COUNTER"
  }
}

const minusCounter = () => {
  return {
    type : "MINUS_COUNTER"
  }
}

const clearCounter = () => {
  return {
    type : "CLEAR_COUNTER"
  }
}
  • 각각 더하기, 빼기, 초기화하기 3가지의 action을 만들어 주었다.
  • 하지만, 해당 type들이 actions 뿐만 아니라 reducer에도 사용되기 때문에 재사용성을 위해 type을 따로 파일로 분리하고, 변수로 관리한다.
// types.ts

export const PLUS_COUNTER = 'PLUS_COUNTER';
export const MINUS_COUNTER = 'MINUS_COUNTER';
export const CLEAR_COUNTER = 'CLEAR_COUNTER';
// actions.ts
import { PLUS_COUNTER, MINUS_COUNTER, CLEAR_COUNTER } from './types';

export const plusCounter = () => {
  return {
    type: PLUS_COUNTER,
  };
};

export const minusCounter = () => {
  return {
    type: MINUS_COUNTER,
  };
};

export const clearCounter = () => {
  return {
    type: CLEAR_COUNTER,
  };
};
  • actions 또한 외부에서 사용되어야 하므로, 각 actionexport 키워드를 붙혀준다.

reducer 만들기

  • 위에서 지정했던 typesimport 해주고, 상태의 초기값을 initialState 에 객체형식으로 지정해주고, reducer를 작성한다.
import { PLUS_COUNTER, MINUS_COUNTER, CLEAR_COUNTER } from './types';

const initialState = {
  count : 0
};

const counterReducer = (state = initialState, action : {type : string}) => {};
  • 매개변수의 state = initialState 문법은, 해당 위치에 인자가 들어오지 않은 경우 초기값을 해당 값으로 가진다 라는 뜻이다.
  • switch-case 문을 이용하여 action 의 타입이 일치하는 경우 return 내부에 있는 로직을 실행 할 수 있게끔 한다.
import { PLUS_COUNTER, MINUS_COUNTER, CLEAR_COUNTER } from './types';

const initialState = {
  count : 0
}

const counterReducer => (state = initialState, action : {type : string}) => {
  switch(action.type){
    case PLUS_COUNTER : 
      return {
        ...state,
        count : state.count + 1
      }
      
    case MINUS_COUNTER : 
      return {
        ...state,
        count : state.count - 1
      }

    case CLEAR_COUNTER : 
      return {
        ...state,
        count : 0
      }

    default: 
      return state
  }
}

export default counterReducer
  • case 에서 state 를 스프레드 문법으로 가져오는데, 이는 상태를 직접 변경하지 않는다는 원칙 때문에 해당 객체를 복사하여 값을 변경하는 것이다.
  • 즉, initialState의 값을 직접 변경하는 것이 아니라, 전체를 복사해 불러오고 원하는 값만 변경시켜 상태를 갱신하는 것이라고 볼 수 있겠다.

store 만들기

  • store 를 만들기 위해서는 reducer 가 인자로 들어가야한다.
  • 이제 필요한 action, reducer 들이 만들어졌으니, store 를 만들고 프로젝트에 연결하자.
import { legacy_createStore as createStore } from '@reduxjs/toolkit';
import counterReducer from './counter/reducer';
const store = createStore(counterReducer);

export default store;
// 내보내어 사용한다.

→ RTK 를 사용하는 경우 configureStore 를 사용하지만, 지금 프로젝트에서는 기존의 redux 의 core 기능만 사용해 볼 것이기 때문에 위와 같이 legacy_createStore 를 사용한다.

연결하기

  • index.tsx 에서 에서 react-reduxProvider 를 이용하여 감싸주고, 사용할 storeprops 로 전달한다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

import { Provider } from 'react-redux';
import store from './redux/store';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

상태 가져오기

  • 유튜브등을 참고한 바로는 위에서 만들어진 상태를 가져오는 데에는 여러 방법이 있는데, 내가 시도해 본 두가지 방법을 함께 소개한다.
  1. connect 함수와 mapStateToProps, mapDispatchToProps 함수

    import React from 'react';
    import './App.css';
    import { connect } from 'react-redux';
    import { plusCounter } from './redux/counter/actions';
    
    function App(props: { count: number; plusCounter: any }) {  // 타입지정
    
      return (
        <div className="App">
          <div>{props.count}</div>
          <button onClick={() => props.plusCounter()}>+</button>
        </div>
      );
    }
    
    const mapStateToProps = (state: { count: number }) => {
      return {
        count: state.count,
      };
    };
    
    const mapDispatchToProps = (dispatch: any) => {
      return {
        plusCounter: () => dispatch(plusCounter()),
      };
    };
    
    export default connect(mapStateToProps, mapDispatchToProps)(App);
    • mapStateToProps 는 store에 저장된 상태를 props로 전달하며, connect 함수의 첫번째 인자로 사용된다.
    • mapDispatchToProps 는 reducer 로 action 을 전달하는 함수를 props 로 전달되며, connect 함수의 두번째 인자로 사용된다.
    • 원하는 props 명으로 상태나, Dispatch 함수를 전달 할 수 있다는 점은 장점이 될 수 있지만, TS 환경에서 타입을 특정하기가 어렵다는 느낌이 들었다.
      → 위의 예제에서도 타입에러가 발생했을때 any 를 사용해 벗어났다.
  2. useSelector, useDispatch Hook 사용하기

    import React from 'react';
    import './App.css';
    import { useSelector, useDispatch } from 'react-redux';
    import { plusCounter } from './redux/counter/actions';
    
    function App() {
      const state = useSelector((state: { count: number }) => state);
      const dispatch = useDispatch();
    
      return (
        <div className="App">
          <div>{state.count}</div>
          <button onClick={() => dispatch(plusCounter())}>+</button>
        </div>
      );
    }
    
    export default App;
    • useSelectorstore에 저장된 상태를 반환하여 해당 변수에 저장한다.
    • useDispatch 는 함수를 변수에 지정하여 사용하고, 그 인자로 action 이 사용된다.
    • connect 함수를 사용하는 방법보단 Hook 을 사용하는 방법이 직관적으로 보인다.

+

  • 위의 과정을 통해 간단한 숫자 카운터가 완성되었다.
  • 위의 예제를 토대로 문자열, 배열, 객체 등을 조작하는 연습을 해보고 Todo 에 적용 시켜봐야겠다.

0개의 댓글