티스토리 뷰

yarn add redux react-redux

리덕스 프로젝트 패턴

프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하여 사용. 

 - 코드의 재사용성이 높아지고

 - UI 또는 이벤트에 따라 특정 컴포넌트에 집중할 수 있다.

● 컨테이너 컴포넌트

(container/CounterContainer.jsx)

리덕스에서 값을 받아오거나 액션을 처리하는 컴포넌트

import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import Counter from "../compoents/counter/Counter";

import { decrease, increase, setText } from "../modules/counter";

const CounterContainer = () => {
  const dispatch = useDispatch();
  const { number, text } = useSelector((state) => {
    return {
      number: state.counter.number,
      text: state.counter.text
    };
  });

  const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);

  const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);

  const onSetText = useCallback(
    (str) => {
      dispatch(setText(str));
    },
    [dispatch]
  );

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

export default CounterContainer;

 

 

● 프레젠테이셔널 컴포넌트

(component/counter/counter.jsx)

상태관리가 이루어지지 않고 props를 받아서 UI를 보여주기만 하는 컴포넌트 

import React, { useState } from "react";

const Counter = ({ number, text, onIncrease, onDecrease, onSetText }) => {
  const [input, setInput] = useState("");

  return (
    <div>
      <h1> count : {number}</h1>
      <div>
        <button onClick={onDecrease}>-1</button>
        <button onClick={onIncrease}>+1</button>
      </div>
      <div>
        <p> Text : {text}</p>
        <input
          type="text"
          vlaue={input}
          onChange={(e) => setInput(e.target.value)}
        />
        <button onClick={() => onSetText(input)}>change text</button>
      </div>
    </div>
  );
};

export default Counter;

리덕스 파일 구조

● 코드 종류별로 파일 분리

- actions, constants, reducer 3개의 디렉터리를 만들고 그 안에 기능별로 파일을 하나식 만드는 방식. 

- 리덕스 공식 문서에 사용되는 기본 구조

- 코드를 종류에 따라 다른 파일에 작성하여 정리할 수 있어서 편리하지만, 새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야 하기때문에 불편할 수 있다.

 

● 기능별로 파일 하나에 작성(Ducks패턴)

- 기능에 따라 액션타입, 액션생성합수, 리듀서 함수를 파일 하나에 몰아서 작성하는 방식.

- 주로 Ducks패턴으로 많이 사용됨

* 위의 방법은 리덕스 프로젝트에서 주로 사용되는 파일 구조로 정해진 디렉터리 구조는 없으므로 작성자에 따라 다르게 사용이 가능하다.


액션타입 정의

● 리듀서 함수

(modules/count.js)

- 리듀서 기본 코드

//액션타입 정의 '모듈이름/액션이름'
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
const SETTEXT = 'counter/SETTEXT';

//액션 생성 함수
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
export const setText = (str) => ({ type: SETTEXT, text : str });

//초기값 설정
const initialState = {
  number: 0,
  text : ''
};

//리듀서 함수
//처음 렌더링 시 state가 undeinfed이면 초기값으로 initialState가 적용됨
function counter(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return { ...state, number: state.number + 1 };
    case DECREASE:
      return { ...state, number: state.number - 1 };
    case SETTEXT:
      return { ...state, text : action.payload };
    default:
      return state;
  }
}

export default counter;

 

- react-actions

createAction, handleActions으로 액션함수를 생성하고 액션을 처리하는 리듀서 함수를 간단하게 작성 가능

import { createAction, handleActions } from 'redux-actions';

//액션타입 정의 '모듈이름/액션이름'
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
const SETTEXT = 'counter/SETTEXT';

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
export const setText = createAction(SETTEXT, str => str);

const initialState = {
  number: 0,
  text: '',
};

const counter = handleActions(
  {
    [INCREASE]: (state, action) => ({
         ...state, 
         number: state.number + 1 
    }),
    [DECREASE]: (state, action) => ({
         ...state, 
         number: state.number - 1 
    }),
    [DECREASE]: (state, action) => ({ 
        ...state, 
        text: action.payload 
    }),
  },
  initialState,
);

export default counter;

 

● 루트 리듀서

(modules/index.js)

- store는 프로젝트에서 하나만 선언하여 사용하는 것이 규칙

- 기능별로 선언한 리듀서 함수들을 하나로 모아서 하나의 store로 사용할 수 있도록 한다.

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

 

● 스토어 만들기

(index.js)

- 루트 리듀서를 스토어로 선언

- 컴포넌트 내에서 스토어를 사용할 수 있도록 Provider로 App컴포넌트를 감사고 store를 props로 전달

(redux devTools : 크롬 개발도구에서 리덕스 스토어의 디버깅을 지원하는 도구. 크롬 확장프로그램 설치 후 이용가능)

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

const store = createStore(rootReducer, composeWithDevTools());

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

 


● 컨테이너에서 리듀서 참조

(container/CounterContainer.jsx)

- connect 함수

import React from "react";
import { connect } from "react-redux";
import Counter from '../compoents/counter/Counter';
import { decrease, increase, setText } from "../modules/counter";

const CounterContainer = ({ number, text, increase, decrease, setText }) => {
  return (
    <Counter
      number={number}
      text={text}
      onIncrease={increase}
      onDecrease={decrease}
      onSetText={setText}
    />
  );
};

//리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨줌
//파라미터로 받은 state는 현재 스토어가 지니고 있는 상태
const mapStateToProps = (state) => ({
  number: state.counter.number,
  text : state.couner.text
});

//액션 생성 함수를 컴포넌트의 props로 넘겨줌
//내장함수 dispatch를 파라미터로 받음.
const mapDispatchToProps = (dispatch) => ({
  increase: () => {
    dispatch(increase());
  },
  decrease: () => {
    dispatch(decrease());
  },
  setText: (str) => {
    dispatch(setText(str));
  }
});

//connect로 리덕스와 연동
export default connect(
  mapStateToProps, 
  mapDispatchToProps
)(CounterContainer);

 

react-redux

- useSelector, useDispatch메서드가 리덕스의 데이터를 참조하고 이벤트를 실행하는 과정을 대신 처리

import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import Counter from "../compoents/counter/Counter";
import { decrease, increase, setText } from "../modules/counter";

const CounterContainer = () => {
  const dispatch = useDispatch();
  const { number, text } = useSelector((state) => {
    return {
      number: state.counter.number,
      text: state.counter.text
    };
  });

  const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);

  const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);

  const onSetText = useCallback(
    (str) => {
      dispatch(setText(str));
    },
    [dispatch]
  );

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

export default CounterContainer;

 

 

 


참조 :

리액트를 다루는 기술(김민준 / 길벗)

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함