[React] Redux_Basic

youngseo·2022년 8월 10일
0

REACT

목록 보기
42/52
post-thumbnail

redux

개발자들이 리덕스 툴킷을 쓰는 것을 권장하고 있지만, 현업에서 툴킷을 쓰느냐 묻는다면 또 그렇지는 않습니다. 결국 기본 리덕스에 대해 잘 이해하고 있어야합니다.

1. 리덕스의 구조

  • useReducer과 비슷한 구조를 가지고 있습니다.
  • 상태관리 라이브러리가 많습니다.(recoil, Mobx, joty?). 리덕스는 현업에서 사용량이 줄어가는 편이긴 하지만 그래도 여전히 많습니다.
  • action type정의, action생성 함수정의, reducer정의 세가지 타입으로 나뉩니다.

1-1 action type 정의

export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";
  • action type를 미리 정의해놓는 것입니다.
  • 미리 변수형태로 만들어놓는 이유
    • export를 해서 다른 파일에서 편하게 불러오기 위해(자동완성 등)
    • 혹시나 있을 오타 방지

1-2 action 생성 함수

→ 원칙적으로 항상 액션 객체를 리턴해야 합니다

  • 액션객체 : { type: INC_COUNT, payload: ~~ } 와 같은 객체
  • dispatch: action 객체를 reducer 로 보내는 행위
  • useReducer 에서는 dispatch({type:’~~’}) 와 같은 방식으로 dispatch 에 action 객체를 직접 명시해서 전달했습니다.
  • 이렇게 사용을 해도 괜찮지만 dispatch 에 action 객체를 Return 하는 함수를 전달해서 사용하는 방법도 있습니다.
//액션 함수 생성
export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: { diff },
  };
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: { diff },
  };
}
  • 사용: dispatch(incCount(2))

1-3 reducer

→ state는 항상 불변성을 유지해야 합니다(push사용금지, concat등 사용)
→동일한 파라미터가 들어왔다면 동일한 결과를 출력해야합니다.
(data등을 사용할 경우 해당 부분은 액션 생성시 처리를 하고, 리듀서에서 그러한 랜덤 로직을 생성해서는 안됩니다.)

  • reducer는 state와 action 두 객체를 파라미터로 받습니다.
function counter(state = initialState, action) {
  switch (action.type) {
    case INC_COUNT:
      return state + action.payload.diff;
    case DEC_COUNT:
      return state - action.payload.diff;
    default:
      return state;
  }
}

1-4 Store

  • store 란 앞서 만든 리듀서와 상태를 담아놓은 것을 말합니다. - 이 store 를 Provider 라는 것을 통해서 컴포넌트에 공유함으로써, 어떤 컴포넌트에서든 리듀서와 상태를 가져와서 사용할 수 있도록 코드를 작성합니다.

store.js

import { legacy_createStore as createStore } from "redux";
import counter from "./reducers/counter";

const store = createStore(counter);

export default store;

App.js

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

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}
  • createStore 의 경우 deprecate 되었습니다.
  • redux 개발자들이 redux toolkit 사용을 권장하기 위해서 해당 함수를 deprecate 시켰으나, 사용 빈도를 보면 createStore 를 쓰는 경우도 아직 많은 것으로 보입니다.**

1-5 useSelector, useDispatch

useSelector : 관리중인 상태를 가져오기 위한 Hook
useDispatch : dispatch함수를 실행할 수 있도록 해주는 Hook

//useSelector
import { useSelector } from "react-redux";

const { number } = useSelector((state) => state);
import { useDispatch } from "react-redux";

const dispatch = useDispatch();

// 사용 예시
dispatch(incCount(1));

2. Container Presenter 패턴

  • Container Presenter 패턴이란 리액트 디자인 패턴 중의 하나로, 기능과 UI를 컴포넌트 상으로 분리하는 것을 말합니다.
  • 데이터를 처리하고, 받아오는 부분 (기능) 은 Container 컴포넌트에서 담당
  • 데이터를 보여주는 부분 (UI) 은 Presenter 컴포넌트에서 담당

Container 컴포넌트

import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { incCount, decCount } from "../redux/actions/counter";

function CounterContainer() {
  const dispatch = useDispatch();
  const number = useSelector((state) => state.number);

  const onIncrease = () => {
    dispatch(incCount(1));
  };

  const onDecrease = () => {
    dispatch(decCount(1));
  };

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

export default CounterContainer;

Presenter 컴포넌트

import React from "react";

function Counter({ number, onIncrease, onDecrease }) {
  return (
    <div>
      <p>{number}</p>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

3. 설치

$ yarn add redux react-redux
  • react-redux 컴포넌트에 리덕스모듈을 적용하기 위해 사용.

4. 폴더구조

constants > counter.js : action type정의

export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";

actions > counter.js : action함수 정의

import { INC_COUNT, DEC_COUNT } from "../constants/counter";

export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: { diff },
  };
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: { diff },
  };
}

reducer > counter.js : 리듀서 함수 정의

import { INC_COUNT, DEC_COUNT } from "../constants/counter";

const initialState = { number: 0 };

export default function counter(state = initialState, action) {
  switch (action.type) {
    case INC_COUNT:
      return { number: state.number + action.payload.diff };
    case DEC_COUNT:
      return { number: state.number - action.payload.diff };
    default:
      return state;
  }
}

store정의

import { legacy_createStore as createStore } from "redux";
import counter from "../reducers/counter";

const store = createStore(counter);

export default store;

App.js적용

import Counter from "./components/Counter";
import { Provider } from "react-redux";
import store from "./store";

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

5. 사용

src 하위에 components폴더> Counter.jsx와 containers>CounterContainer파일을 만들어줍니다.

CounterContainer

import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { incCount, decCount } from "../redux/actions/counter";

function CounterContainer() {
  const dispatch = useDispatch();
  const number = useSelector((state) => state.number);

  const onIncrease = () => {
    dispatch(incCount(1));
  };

  const onDecrease = () => {
    dispatch(decCount(1));
  };

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

export default CounterContainer;

Counter.jsx

function Counter({ number, onIncrease, onDecrease }) {
  return (
    <div>
      <p>{number}</p>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

App.js

import { Provider } from "react-redux";
import store from "./redux/store/store";
import CounterContainer from "./containers/CounterContainer";

function App() {
  return (
    <Provider store={store}>
      <CounterContainer />
    </Provider>
  );
}

export default App;

6. Ducks패턴

  • ducks 패턴이란 하나의 파일에다가 모든 코드를 작성하는 것을 말합니다.
  • 코드 작성이 조금 더 용이해지고, 폴더 구조 상의 가독성이 더 좋아진다는 장점이 있습니다.

[규칙]

  1. 반드시 리듀서 함수를 default export 해야 한다.
  2. 반드시 액션 생성 함수를 export 해야 한다.
  3. 반드시 접두사를 붙인 형태로 액션타입을 정의해야 한다.
  4. (선택) 액션 타입은 UPPER_SNAKE_CASE 형태로 이름을 짓고 export 할 수 있다.

6-1 Ducks패턴 실습

modules > counter.js

//액션타입 정의
const INC_COUNT = "counter/INC_COUNT";
const DEC_COUNT = "counter/DEC_COUNT";

//액션 생성함수
export function incCount(diff) {
  return {
    type: INC_COUNT,
    payload: { diff },
  };
}

export function decCount(diff) {
  return {
    type: DEC_COUNT,
    payload: { diff },
  };
}

const initailState = { number: 0 };

export default function counter(state = initailState, action) {
  switch (action.type) {
    case INC_COUNT:
      return {
        ...state,
        number: state.number + action.payload.diff,
      };
    case DEC_COUNT:
      return {
        ...state,
        number: state.number - action.payload.diff,
      };
    default:
      return state;
  }
}

modules>index.js

import { legacy_createStore as createStore } from "redux";
import counter from "./counter";

const store = createStore(counter);

export default store;

6-2 App.js

app.js

import { Provider } from "react-redux";
import store from "./modules";
import CounterContainer from "./containers/CounterContainer";

function App() {
  return (
    <Provider store={store}>
      <CounterContainer />
    </Provider>
  );
}

6-3 Container Presenter

container > CounterContainer

import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { incCount, decCount } from "../modules/counter";

function CounterContainer() {
  const dispatch = useDispatch();
  const number = useSelector((state) => state.number);

  const onIncrease = () => {
    dispatch(incCount(1));
  };

  const onDecrease = () => {
    dispatch(decCount(1));
  };

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

export default CounterContainer;

components>Counter.jsx

import React from 'react'

function Counter({ number, onIncrease, onDecrease }) {
  return (
    <div>
      {number}
      <button onClick={onIncrease}>증가</button>
      <button onClick={onDecrease}>감소</button>
    </div>
  )
}

export default Counter

7. combineReducers

만약 counter외에도 관리하는 store가 많은 경우 combineReducers를 통해 rootReducers을 만들어 관리를 할 수도 있습니다.

import { legacy_createStore as createStore } from "redux";
import { combineReducers } from "redux";
import counter from "./counter";
import user from './user';

const rootReducer = combineReducers({
    counter,
    user
})

const store = createStore(rootReducer);

export default store;

단, combinereducer 를 사용하고 난 뒤에는, useSelector 를 쓰는 방식이 조금 달라집니다.

state.number → state.counter.number

8. Redux Logger 및 Dev-tools

redux logger

redux logger 는 redux 로 실행되는 로직에 대해서 logging, 즉 콘솔창에 기록을 남겨주는 역할을 담당하는 리덕스 미들웨어 (미들웨어라고 해서 특별한 것이 아니고, 그냥 리덕스가 작동하는 과정에서 store, action 객체를 다루면서 특정한 기능을 수행하는 함수를 말합니다) 입니다.

 $ yarn add redux-logger --dev

기본적으로 사용하는 코드는 아래와 같습니다.

applyMiddleware 를 통해서 해당 미들웨어를 적용할 수 있습니다.

import { applyMiddleware, legacy_createStore as createStore } from "redux";
import logger from 'redux-logger';

const store = createStore(rootReducer, applyMiddleware(logger));

어떤 action 이 실행되었고, state 가 어떻게 변화했는지를 개발자 도구 콘솔에서 확인할 수 있습니다!

redux devtools

redux-devtools 는 크롬에서 redux 전용 개발자 도구를 활용할 수 있도록 해줍니다

확장 프로그램만 설치하면 되고, 따로 모듈을 추가로 설치할 것은 없습니다. redux logger 와 동시에 사용하기 위한 코드를 작성한다면 아래와 같습니다.

import { applyMiddleware, compose, legacy_createStore as createStore } from "redux";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(logger)));

이후 실행해보면, 아래처럼 실행된 action 과 관련한 세부사항을 확인할 수 있고, 되돌리기까지도 가능합니다!

0개의 댓글