React - (Redux)

김정욱·2020년 10월 5일
1

React

목록 보기
10/22
post-thumbnail

Redux VS Context API

  • 공통점 : 상태를 전역적으로 관리할 수 있는 도구
  • Redux 의 차이점
    1) 미들웨어
    : 미들웨어를 사용할 수 있어서 리듀서(reducer)에 의해 처리되기 전에 특정 동작 수행 가능

    2) 유용한 함수와, Hooks
    : Context API를 쓸 때에는 Context와 Provider를 설정하고 커스텀 Hook을 따로 만들어서 사용하여 번거로움
      하지만, ReduxuseSelector / useDispatch / useStore과 같은 Hooks로 쉽게 상태를 관리

    3) 하나의 커다란 상태
    : 기능별로 Context를 관리하는 Context API / Redux하나의 커다란 객체에서 상태를 관리

Redux

[ Redux에서 사용되는 키워드 ]


[ 사용하기 ]

1) reducer 만들기
2) action 및 action 생성함수 만들기

( /modules/counter.js )

/* 액션 타입 만들기 */
// Ducks 패턴을 따를땐 액션의 이름에 접두사를 넣어주세요.
// 이렇게 하면 다른 모듈과 액션 이름이 중복되는 것을 방지 할 수 있습니다.
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

/* 액션 생성함수 만들기 */
// 액션 생성함수를 만들고 export 키워드를 사용해서 내보내주세요.
export const setDiff = diff => ({ type: SET_DIFF, diff });
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });

/* 초기 상태 선언 */
const initialState = {
  number: 0,
  diff: 1
};

/* 리듀서 선언 */
// 리듀서는 export default 로 내보내주세요.
export default function counter(state = initialState, action) {
  switch (action.type) {
    case SET_DIFF:
      return {
        ...state,
        diff: action.diff
      };
    case INCREASE:
      return {
        ...state,
        number: state.number + state.diff
      };
    case DECREASE:
      return {
        ...state,
        number: state.number - state.diff
      };
    default:
      return state;
  }
}

( /modules/todos.js )

/* 액션 타입 선언 */
const ADD_TODO = 'todos/ADD_TODO';
const TOGGLE_TODO = 'todos/TOGGLE_TODO';

/* 액션 생성함수 선언 */
let nextId = 1; // todo 데이터에서 사용 할 고유 id
export const addTodo = text => ({
  type: ADD_TODO,
  todo: {
    id: nextId++, // 새 항목을 추가하고 nextId 값에 1을 더해줍니다.
    text
  }
});
export const toggleTodo = id => ({
  type: TOGGLE_TODO,
  id
});

/* 초기 상태 선언 */
// 리듀서의 초기 상태는 꼭 객체타입일 필요 없습니다.
// 배열이여도 되고, 원시 타입 (숫자, 문자열, 불리언 이여도 상관 없습니다.
const initialState = [
  /* 우리는 다음과 같이 구성된 객체를 이 배열 안에 넣을 것입니다.
  {
    id: 1,
    text: '예시',
    done: false
  } 
  */
];

export default function todos(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return state.concat(action.todo);
    case TOGGLE_TODO:
      return state.map(
        todo =>
          todo.id === action.id // id 가 일치하면
            ? { ...todo, done: !todo.done } // done 값을 반전시키고
            : todo // 아니라면 그대로 둠
      );
    default:
      return state;
  }
}

3) root reducer 만들기 (/modules/index.js)
: 여러개의 리듀서를 하나로 관리하기 위한 rootReducer를 만들기

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

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

export default rootReducer;

: 'redux' 에서 'combineReducers'를 빼서 여러 리듀서를 하나로 만든다.


4) 리액트 프로젝트에 리덕스 적용하기 (/index.js)
: 'react-redux' 사용 ($ yarn add react-redux)

import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';

const store = createStore(rootReducer);

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

참고) React 정석 패턴으로 컴포넌트 만들기
: 하나의 기능을 하는 컴포넌트를 만들 때 2개로 분리하여 컨테이너에서 프리젠테이셔널 컴포넌트를 호출해서 구성

  • UI에 집중하는 컴포넌트 => 프리젠테이셔널 컴포넌트
  • 로직에 집중하는 컴포넌트 => 컨테이너 컴포넌트

( Counter.js ) - 프리젠테이셔널 컴포넌트

import React from 'react';

function Counter({ number, diff, onIncrease, onDecrease, onSetDiff }) {
  const onChange = e => {
    // e.target.value 의 타입은 문자열이기 때문에 숫자로 변환해주어야 합니다.
    onSetDiff(parseInt(e.target.value, 10));
  };
  return (
    <div>
      <h1>{number}</h1>
      <div>
        <input type="number" value={diff} min="1" onChange={onChange} />
        <button onClick={onIncrease}>+</button>
        <button onClick={onDecrease}>-</button>
      </div>
    </div>
  );
}

export default Counter;

( CounterContainer.js ) - 컨테이너 컴포넌트

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Counter from '../components/Counter';
import { increase, decrease, setDiff } from '../modules/counter';

function CounterContainer() {
  // useSelector는 리덕스 스토어의 상태를 조회하는 Hook입니다.
  // state의 값은 store.getState() 함수를 호출했을 때 나타나는 결과물과 동일합니다.
  const { number, diff } = useSelector(state => ({
    number: state.counter.number,
    diff: state.counter.diff
  }));

  // useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다.
  const dispatch = useDispatch();
  // 각 액션들을 디스패치하는 함수들을 만드세요
  const onIncrease = () => dispatch(increase());
  const onDecrease = () => dispatch(decrease());
  const onSetDiff = diff => dispatch(setDiff(diff));

  return (
    <Counter
      // 상태와
      number={number}
      diff={diff}
      // 액션을 디스패치 하는 함수들을 props로 넣어줍니다.
      onIncrease={onIncrease}
      onDecrease={onDecrease}
      onSetDiff={onSetDiff}
    />
  );
}

export default CounterContainer;

5) 컴포넌트에서 사용하기 ( useSelector() / useDispatch() )
: 로직에 집중하는 컨테이너 컴포넌트에서 사용한다!

import { useSelector, useDispatch } from 'react-redux';
...
  const { number, diff } = useSelector(state => ({
    number: state.counter.number,
    diff: state.counter.diff
  }));
...
  const dispatch = useDispatch();
  // 각 액션들을 디스패치하는 함수들을 만드세요
  const onIncrease = () => dispatch(increase());
  const onDecrease = () => dispatch(decrease());
  const onSetDiff = diff => dispatch(setDiff(diff));
...

[ 리덕스 개발자도구 적용 ]

1) (크롬 웹 스토어) 확장 프로그램 설치
검색 : Redux Devtools
https://chrome.google.com/webstore/category/extensions?hl=ko&


2) 프로젝트에 'redux-devtools-extension' 설치
$ yarn add redux-devtools-extension


3) index.js 수정

...
import { composeWithDevTools } from 'redux-devtools-extension';
...
const store = createStore(rootReducer, composeWithDevTools());

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

profile
Developer & PhotoGrapher

0개의 댓글