React Redux

저는 주로 전역 상태관리를 recoil 로
작업하고 있었습니다.
그러다 문득 redux 기능에 대해서 궁금해졌고
정리해 보았습니다.

사용하는 이유

state 종속성 탈피

우리는 useState 를 사용 할 경우 컴포넌트 내부에 state 를 만들고, 함수로 state 를 바꿉니다.그렇기 때문에 state 는 컴포넌트에 종속되는 것은 당연한 결과 입니다. redux 는 컴포넌트에 종속되지 않고, 상태관리를 컴포넌트 바깥에서 합니다.프로젝트 루트레벨에서 store 라는 곳에 state 를 저장하고, 모든 컴포넌트는 store 에 구독을 하면서 state 와 그 state 를 바꾸는 함수를 전달 받게 되죠.함수를 바꿈으로 state 가 변경되면 해당 state 를 바라보고 있는 컴포넌트는 모두 리렌더링 됩니다.

 

기본 용어

액션

상태에 어떠한 변화가 필요하면 액션이란 것이 발생합니다.액션 객체는 type 필드를 반드시 가지고 있어야 합니다. 이 값을 액션의 이름이라고 생각하면 됩니다.

 

액션 생성 함수

액션 객체를 만들어 주는 함수입니다.

리듀서

변화를 일으키는 함수입니다.액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아옵니다.그리고 두 값을 참고하여 새로운 상태를 만들어 변환해 줍니다.

스토어

프로젝트에 리덕스를 적용하기 위해 스토어를 만듭니다.스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 중요한 내장 함수를 지닙니다.한 개의 프로젝트는 단 하나의 스토어만 가질 수 있다는 것을 명심하세요.

디스패치

디스패치는 액션을 발생시키는 것이라고 이해하면 됩니다.스토어의 내장 함수 중 하나로써, 이 함수가 호출되면 스토어는 우리가 만들어둔 리듀서 함수를 실행시켜서 새로운 상태를 만들어 줍니다.

사용해보기

들어가기에 앞서 react-redux 는 redux 에 기반하여 만들어진 기능이기 때문에 redux 를 모르신다면 공부하시고 오세요!!

 

설치

npm i redux react-redux

 

처음에는 actions 를 정의해 주어야 합니다. 

// src/redux/actions.js

export const INCREASE = 'INCRESE';
export const DECREASE = 'DECRESE';
export const SET_USER = 'SET_USER';

export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
export const setUser = (user) => ({ type: SET_USER, payload: user });

 

다음으로 리듀서 함수를 만들어 주어야 합니다. 

// src/redux/countReducer.js

import { INCREASE, DECREASE } from './actions';

const initialState = {
  count: 0,
};

export function countReducer(state = initialState, action) {
  switch (action.type) {
    case INCREASE:
      return {
        ...state,
        count: state.count + 1,
      };
    case DECREASE:
      return {
        ...state,
        count: state.count - 1,
      };
    default:
      return state;
  }
}
// src/redux/userReducer.js

import { SET_USER } from './actions';

const initialstate = {
  user: null,
};

export function userReducer(state = initialstate, action) {
  switch (action.type) {
    case SET_USER:
      return {
        ...state,
        user: action.payload,
      };
    default:
      return state;
  }
}

 

이렇게 리듀서까지 만들어 주었다면 스토어에 넣어주어야 합니다. 여러개의 리듀서를 만들어서 스토어에 넣어주고 싶다면

redux-toolkit 에서 지원해 주는 combineReducers 함수를 사용하시면 됩니다. 

설치는 npm i @reduxjs/toolkit 로 설치해 주시고 사용하시면 됩니다.

// src/redux/rootReducer.js

import { combineReducers } from '@reduxjs/toolkit';
import { countReducer } from './countReducer';
import { userReducer } from './userReducer';

const rootReducer = combineReducers({
  count: countReducer,
  user: userReducer,
});

export default rootReducer;

 

이렇게 만들어준 묶은 리듀서를 store 에 넣어주어야 겠죠??

// src/redux/store.js

import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';

export const store = configureStore({
  reducer: rootReducer,
});
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './redux/store';
import './App.css';
import Button from './component/Button';

function App() {
  return (
    <Provider store={store}>
      <Button>Click me</Button>
    </Provider>
  );
}

export default App;

 

여기까지가 기본 설정입니다. 

최상단 부모 컴포넌트에 연결을 해 주어야 그 밑에 자식 컴포넌트들이 사용할 수 있습니다. 

이제 자식 컴포넌트에서 props 로 넘겨받지 않고 저장소인 store 에서 꺼내서 사용해 보겠습니다.

// src/component/Button.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increase, decrease, setUser } from '../redux/actions';

const Button = ({ children }) => {
  const count = useSelector((state) => state.count.count);
  const user = useSelector((state) => state.user.user);
  const dispatch = useDispatch();

  console.log(user);

  return (
    <div>
      <p>{children}</p>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increase())}>Increase</button>
      <button onClick={() => dispatch(decrease())}>Decrease</button>
      <button onClick={() => dispatch(setUser('Chan'))}>
        change User Name
      </button>
      {user && <p>{user}</p>}
    </div>
  );
};

export default Button;

 

특정 값을 꺼내고 싶다면 useSelector 을 사용하시고, 특정 값을 변경하고 싶다면 위에 용어 설명에서 보았듯이

우리의 reducer 함수를 사용할 수 있는 dispatch 를 꺼내면 되는데 꺼낼 때는 useDispatch 함수를 이용하시면 됩니다.

변수로 만든 dispatch 함수 안에 저희가 정의한 액션 생성 함수를 dispatch 함수 안에 넣어서 실행시키면 됩니다.

 

 

여기까지 기본적인 react-redux 사용법 입니다.

 

useSelector 최적화

const { number, diff } = useSelector(state => ({
  number: state.counter.number,
  diff: state.counter.diff
}));

 

만약 이렇게 두개의 값을 동시에 사용하는 컴포넌트가 있다고 가정해 봅시다.

그리고 이 두개의 값을 변경하는 특정 함수도 있다고 가정해 보면 number 값만 변경되도 diff 값이 포함된 컴포넌트도 리렌더링 되는 것을

보실 수 있을 것입니다. 

사실상 useSelector Hook을 통해 매번 렌더링 될 때마다 새로운 객체 { number, diff }를 만드는 것이기 때문에 상태가 바뀌었는지 바뀌지 않았는지 확인을 할 수 없어서 낭비 렌더링이 이루어지고 있는 것이죠.

 

이를 최적화 하기 위해선 두가지 방법이 있습니다.

 

첫번째 방법은 간단하게 useSelector 을 두번 하는 것입니다.

const number = useSelector(state => state.counter.number);
const diff = useSelector(state => state.counter.diff);

 

이렇게 하면 해당 값들 하나라도 바뀌었을 때에만 컴포넌트가 리렌더링 됩니다.

 

두번째는, react-redux 의 shallowEqual 함수를 useSelector 의 두번째 인자로 전달해주는 것입니다.

  const { number, diff } = useSelector(
    state => ({
      number: state.counter.number,
      diff: state.counter.diff
    }),
    shallowEqual
  );

 

React-Redux는 상태 관리를 체계적으로 할 수 있게 도와주는 강력한 도구라고 생각합니다.
특히 대규모 애플리케이션에서 상태 추적과 관리를 용이하다고도 생각합니다.

그러나 recoil 을 사용하는 입장으로써 작은 프로젝트에서는 오히려 복잡성을 증가시킬 수 있으며,
러닝 커브가있을 수 있다고 생각했습니다.
프로젝트의 규모와 필요에 따라 적절히 선택하여 사용하는 것이 중요하다고 느껴봅니다.

'React' 카테고리의 다른 글

React 에서 files 다루기  (0) 2024.07.29
Axios  (0) 2024.07.28
useCallback  (0) 2024.05.03
useMemo  (0) 2024.05.03
useEffect  (0) 2024.05.01