Redux 사용해보기

hoo00nn·2020년 12월 20일
0
post-thumbnail

이번 글에서는 react에서 redux를 사용하는 방법에 대해서 알아보려고 한다.

먼저 CRA를 통해 redux와 react-redux 패키지가 포함된 채로 프로젝트를 시작할 수도 있고, 이미 진행 중인 프로젝트에 redux를 추가할 수도 있다.

CRA를 통한 설치 방법

npx create-react-app client --template redux-typescript

이미 진행 중인 프로젝트에서 redux 설치하기

npm i redux react-redux
# 타입스크립트라면 아래의 패키지를 추가로 설치
npm i -D @tpyes/react-redux

그리고 리덕스를 디버깅하기 쉽게 해주는 크롬 확장프로그램이 있는데 확장프로그램과 연동하기 위해 패키지를 하나 추가로 설치해줘야 한다.

npm i -D redux-devtools

설치가 모두 완료됐으면 이제 redux를 사용할 준비가 되었다!

Redux 폴더 구조

Redux를 사용할 때 강제되는 폴더구조는 없지만 권장하는 폴더 구조는 존재한다. 우리는 아래의 그림과 같은 폴더 구조를 사용할 것이다.

  • components : 화면에 실제로 그려지는 컴포넌트를 담는 폴더
  • modules : 리덕스의 State, Reducer를 정의한 파일들을 담는 폴더

Redux로 Counter 만들어보기

그럼 Redux를 사용하여 간단한 Counter를 만들어 보겠다.

src/components/Counter.tsx

import React, { FC } from "react";

const Counter: FC = () => {
  return (
    <>
      <div>
        <button>-</button>
        <span>0</span>
        <button>+</button>
      </div>
    </>
  );
};

export default Counter;

src/App.tsx

import React from "react";
import Counter from "./components/Counter";

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

export default App;

간단하게 Counter 컴포넌트를 만들었다.

여기서 redux를 사용하기 위한 절차를 살펴보자.

  1. Action Type 정의하기
  2. Action Creator 함수 만들기
  3. Store 초기 상태 설정하기
  4. Reducer 함수 만들기

Action Type 정의하기

src/modules/counter.ts

const INCREMENT_COUNTER: string = "INCREMENT_COUNTER";
const DECREMENT_COUNTER: string = "DECREMENT_COUNTER";

Action Creator 함수 만들기

src/modules/counter.ts

interface IncrementAction {
  type: typeof INCREMENT_COUNTER;
}

interface DecrementAction {
  type: typeof DECREMENT_COUNTER;
}

const INCREMENT_COUNTER: string = "INCREMENT_COUNTER";
const DECREMENT_COUNTER: string = "DECREMENT_COUNTER";

export const increase = (): IncrementAction => ({ type: INCREMENT_COUNTER });
export const decrease = (): DecrementAction => ({ type: DECREMENT_COUNTER });

Store 초기 상태 설정하기

src/modules/counter.ts

interface InitialState {
  number: number;
}

const initialState = {
    number : 0
}

Reducer 만들기

Reducer를 만들기 전에 주의해야할 것이 하나 있다. 바로 Reducer는 순수함수여야 한다 이다. 그 이유는 이전 포스팅인 Redux란?을 읽어보길 바란다.

  • Reducer 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
  • 파라미터 외의 값에는 의존하면 안된다.
  • 이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환한다.
  • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 한다.

src/modules/counter.ts

type Action = IncrementAction | DecrementAction;

export const counter = (prevState = initialState, action: Action) => {
  switch (action.type) {
    case INCREMENT_COUNTER:
      return { number: prevState.number + 1 };
    case DECREMENT_COUNTER:
      return { number: prevState.number - 1 };
		default:
      return prevState;
  }
};

Reducer 한 곳에 합치기

현재는 reducer가 counter에 하나밖에 없지만 프로젝트가 커질수록 reducer의 개수는 계속 늘어날 것이다. 하지만 redux store에는 하나의 reducer만 연결되야 하므로 이를 통합 해야한다.

우리는 이를 위해 redux의 combineReducers를 이용할 것이다.

사용법은 간단하다. combineReducers 메소드 안에 객체 형태로 우리가 만든 Reducer를 넣어주면 된다.

src/modules/index.ts

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

const rootReducer = combineReducers({
  counter,
});

export default rootReducer;

React와 Redux 연결하기

store도 만들었으니 React 프로젝트에 Redux를 적용시켜보자.

src/App.tsx

import React from "react";
import { createStore } from "redux"; // 추가
import { Provider } from "react-redux"; // 추가

import rootReducer from "./modules/index"; // 추가
import Counter from "./components/Counter";

const store = createStore(rootReducer); // 추가

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

export default App;

Redux에 상태를 저장하고 사용해보기

Hooks를 사용해서 간단하게 Redux를 이용할 것이다.

useSelector를 이용하여 상태를 조회하고, useDispatch를 이용하여 Action를 디스패치할 수 있다.

src/components/Counter.tsx

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

const Counter: FC = () => {
const {
    counter: { number },
  } = useSelector((state) => state as any);
  const dispatch = useDispatch();
  const onClickMinus = useCallback(() => dispatch(decrease()), [dispatch]);
  const onClickPlus = useCallback(() => dispatch(increase()), [dispatch]);

  return (
    <>
      <div>
        <button onClick={onClickMinus}>-</button>
        <span>{number}</span>
        <button onClick={onClickPlus}>+</button>
      </div>
    </>
  );
};

export default Counter;

Redux의 렌더링 최적화

Hooks를 사용하든 connect 함수를 사용하든 개인의 자유지만 useSelector를 사용하여 Redux의 상태를 조회하는 경우 최적화 작업이 자동으로 이루어지지 않는다.

해결방법

  1. 독립 선언

    // 최적화 전
    // number, diff 중 하나라도 변경되면 리렌더링
    const { number, diff } = useSelector(state => ({
      number: state.counter.number,
      diff: state.counter.diff
    }));
    
    // 최적화 후
    const number = useSelector(state => state.counter.number);
    const diff = useSelector(state => state.counter.diff);
  2. equalityFn

    equalityFn?: (prev: any, next: any) => boolean
    • equalityFn 는 이전 값(prev)과 다음 값(next)을 비교하여 true가 나오면 다시 렌더링을 하지 않고 false가 나오면 렌더링을 진행 합니다.
    const { count, prevCount } = useSelector((state: RootState) => ({
        count : state.countReducer.count,
        prevCount: state.countReducer.prevCount,
      }),(prev, next) => {
        return prev.count === next.count && prev.prevCount === next.prevCount;
      });
  3. shallowEqual

    • shallowEqual 는 selector로 선언한 값의 최상위 값들의 비교여부를 대신 작업해 줍니다.
    • 주의할 사항은 shallowEqual 은 최상위 값만 비교한다. 객체의 최상위 값만 비교하지 depth가 깊어지면 깊어진 부분은 비교하지 않는다.
profile
😀 신기술에 관심이 많고, 함께 성장하고 함께 개발하고 싶은 개발자가 되고 싶습니다. 😀

0개의 댓글