redux - Ducks 패턴

연주·2023년 2월 6일
0

TIL

목록 보기
36/37

이전 게시글과 같은 기능의 redux로 상태관리하는 것인데, 폴더구조를 다르게 해보았다.

🔑 Ducks 패턴

모듈 : Ducks 패턴을 사용하여 액션 타입, 액션 생성 함수, 리듀서를 작성한 코드

📌 counter 모듈 만들기

  1. 액션 타입 정의 하기 ( counter )

📁 modules/counter.js

const INCREASE = 'counter/INCREASE'
const DECREASE = 'counter/DECREASE'

// 액션 타입 정의

액션 타입 정의 : 모듈 이름/액션이름
2. 액션 생성 함수 만들기

export const increse = () => ({ type: INCREASE })
export const decrese = () => ({ type: DECREASE })

// 액션 생성 함수 만들기
  1. 초기상태 및 리듀서 함수 만들기
const initialState = {
  number: 0
}

// 초기 상태에 number를 0으로 해줌

function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        number: state.number + 1
      }
    case DECREASE:
      return {
        number: state.number - 1
      }
    default:
      return state
  }
}

export default counter

// 리듀서 함수는 현재 상태를 참조하여 새로운 객체를 생성해서 반환 하는 코드를 만듬

📌 logged 모듈 만들기 (위에랑 같은 방식으로)

📁 modules/isLogged.js

const SIGN_IN = 'isLogged/SIGN_IN'

// 액션 타입 정의

export const sign_in = () => ({ type: SIGN_IN })

// 액션 생성 함수 만들기


// 초기 상태
const initialState = {
  sign: false
}

// 리듀서 함수
const isLogged = (state = initialState.sign, action) => {
  switch (action.type) {
    case 'SIGN_IN':
      return !state;
    default:
      return state;
  }
}
export default isLogged;

📌 루트 리듀서 만들기

여러개의 리듀서를 하나로 합치는 것

📁 modules/index.js

import { combineReducers } from "redux";
import counter from "./counter";
import isLogged from "./isLogged";

const rootReducer = combineReducers({
  counter, isLogged
})

export default rootReducer

combineReducers 이라는 유틸 함수를 이용해서 하나로 쉽게 처리 가능


📌 리액트에서 리덕스 적용

📁 src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';

const store = createStore(rootReducer)

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

리액트에서 store를 사용할 수 있도록 react-redux에서 제공하는 Provider로 감싼다.
이 컴포넌트를 사용할 때는 storeprops로 전달해 주어야한다.

🧷tip
chrome - redux devTolls 설치

npm install redux-devtools-extension

설치 후, index.js에 import 해준다.

import { devToolsEnhancer } from 'redux-devtools-extension';

const store = createStore(rootReducer, devToolsEnhancer())

📌 컨테이너 컴포넌트 만들기

컨테이너 컴포넌트 : 리덕스 스토어와 연동된 컴포넌트를 컨테이너
컴포넌트에서 리덕스 스토어에 접근하여 원한느 상태를 받아 오고, 액션도 디스패치에 줄 차례

📁 containers/CounterContainer.js

import Counter from "../components/Counter"

const CounterContainer = ({ number, increse, decrese }) => {
  return <Counter />
};

export default CounterContainer;

리덕스와 연동하려면 react-redux에서 제공하는 connect 함수와 함께 사용해야 한다.

connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)

mapStateToProps : 리덕스 스토어 안에 상태를 컴포너트의 props로 넘겨주기 위해 설정하는 함수
mapDispatchToProps : 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수

예를들면,

const makeContainer = connect(mapStateToProps,mapDispatchToProps)makeContainer(타깃컴포넌트)

📍 본격적으로 CounterContainer 컴포넌트에서 connect 사용

📁 containers/CounterContainer.js

import { connect } from "react-redux"
import Counter from "../components/Counter"
import { increse, decrese } from "../modules/counter"

const CounterContainer = ({ number, increse, decrese }) => {
  return <Counter number={number} onIncrese={increse} onDecrese={decrese} />
}

const mapStateToProps = state => ({
	//state를 파라미터로 받아오고, 이 값은 현재 스토어가 지니고 있는 상태
  number: state.counter.number
})
const mapDispatchToProps = dispatch => ({
	// store의 내장 함수 dispatch를 파라미터로 받아온다.
  increse: () => {
    dispatch(increse())
    // 액션 생성 함수를 불러와서 액션객체를 만들고 디스패치
  },
  decrese: () => {
    dispatch(decrese())
  }
})

export default connect(mapStateToProps, mapDispatchToProps,)(CounterContainer)

👉🏼 bindActionCreators 유틸 함수 사용해서 간편하게 쓰기


export default connect(
	state => ({
    	number : state.counter.number,
    }),
    dispatch => 
    	bindActionCreators(
        {
        	increse,
            decrese,
        },
        dispatch,
        ),
)(CounterContainer)

📁 src/App.js

import CounterContainer from "./containers/CounterContainer";
import IsLogged from "./components/IsLoggde";
function App() {
  return (
    <>
      <CounterContainer />
      <IsLogged />
    </>
  );
}

export default App;

🔑 redux-actions 라이브러리

리덕스를 훨씬 더 편하게 사용할 수 있다.

npm install redux-actions
  • switch/case문이 아닌 handleActions라는 함수를 사용해 각 액션마다 업데이트 함수를 설정하는 형식

📌 counter 모듈 만들기

📁 modules/counter.js

import { createAction, handleActions } from "redux-actions"

const INCREASE = 'counter/INCREASE'
const DECREASE = 'counter/DECREASE'

// 액션 타입 정의

export const increse = createAction(INCREASE)
export const decrese = createAction(DECREASE)

// 액션 생성 함수 만들기

const initialState = {
  number: 0
}

// 초기 상태에 number를 0으로 해줌
const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({ number: state.number + 1 }),
    [DECREASE]: (state, action) => ({ number: state.number - 1 }),
  },
  initialState,
)

export default counter

📌 Hooks를 사용하여 컨테이너 컴포넌트 만들기

useSelector를 사용하여 connect 함수를 사용하지 않고 리덕스의 상태를 조회할 수 있다.

const 결과 = useSelector(상태 선택 함수)

useDispatch : 컴포넌트 내부에서 스토어의 내장함수 dispatch를 사용 할 수 있게 해줌

const dispatch = useDispatch();
dispatch({ type : 'SAMPLE_ACTION'});

useCallback 으로 액션을 디스ㅐ치 하는 함수를 감싸주어, 컴포너느 성능을 최적화한다.

📁 containers/CounterContainer.js

import { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import Counter from "../components/Counter";
import { increse, decrese } from "../modules/counter";

const CounterContainer = () => {
  const number = useSelector((state) => state.counter.number);
  const dispatch = useDispatch();
  const onIncrease = useCallback(() => dispatch(increse()), [dispatch]);
  const onDecrease = useCallback(() => dispatch(decrese()), [dispatch]);

  return (
    <Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
  );
};

export default CounterContainer;

작은 프로젝트에서 리덕스를 적용하면 오히려 프로젝트의 복잡도가 높아질 수 있다.
하지만, 규모가 큰 프로젝트에 리덕스를 적용하면 상태를 더 체계적으로 관리할 수 있다!

🔍 참고자료 : 리액트를 다루는 기술


💬 다양한 방법으로 redux를 학습하였는데, 실무에서는 어떤 방법으로 쓰는 지 궁금하다.
프로젝트에 적용해 보지 않아서 적용해 보고 싶다!
아무래도 동영상은 영어로 되어있어서, 책으로 본 게 설명을 자세히 들을 수 있었다.

profile
성장중인 개발자🫰

0개의 댓글