React - Redux란? (상태 관리 라이브러리)

uk·2023년 1월 7일
0

React

목록 보기
15/17

Redux란?

Redux란 컴포넌트와 상태를 분리하여 전역에서 상태 관리를 해줄 수 있게 해주는 상태 관리 라이브러리이다. React 애플리케이션을 개발할 때 Redux를 사용하면 React 컴포넌트 간의 복잡한 데이터 흐름을 따라갈 필요가 없어진다.

그렇다고 웹 애플리케이션 개발 시 상태 관리 라이브러리가 꼭 필요한건 아니다. Redux를 사용하지 않아도 충분히 웹 애플리케이션을 만들 수 있다. 하지만 컴포넌트의 구조가 복잡해질수록 상태 관리가 힘들어지고 props drilling이 발생하게 되므로 상태 관리 라이브러리를 사용하는 것이 효율적이다.

*로컬 상태 - 특정 컴포넌트 내에서만 관리되는 상태
*전역 상태 - 다른 컴포넌트와 상태를 공유하고 영향을 끼치는 상태


Props Drilling이란?

Props Drilling은 상위 컴포넌트의 state를 props를 통해 하위 컴포넌트로 전달하는 과정을 의미하는데 상위 컴포넌트와 하위 컴포넌트 사이에 props를 전달하는 용도로만 쓰이는 컴포넌트가 존재한다.

Component A -> Component B -> Component C -> Component D -> Component E

A 컴포넌트에서 E 컴포넌트로 props를 전달할 때 B, C, D 컴포넌트들을 거쳐야한다.
이때 A 컴포넌트의 state를 E 컴포넌트에서 변경했을 때 모든 컴포넌트에서 re-redering이 발생한다.

최상위 컴포넌트인 A컴포넌트에서 state를 관리하고 state가 변경될 때마다 re-rendering 되면서 모든 컴포넌트가 re-rendering된다. 변경되는 상태와 연관없는 컴포넌트 까지 불필요하게 re-rendering되는 것이다.


Props Drilling의 문제점

props의 전달 횟수가 5회 이내라면 Props Drilling은 크게 문제가 되지 않지만 프로젝트 규모가 커지고 구조가 복잡해질수록 props의 전달 과정이 늘어나기 때문에 문제점들이 발생한다.

  1. 코드의 가독성이 나빠진다.
  2. 구조 변경 시 일일이 수정해줘야 하기 때문에 코드의 유지보수가 힘들어진다.
  3. state 변경 시 props 전달 과정에서 거쳐가는 컴포넌트들까지 불필요한 re-rendering이 발생하므로 웹 성능에 좋지않다.

해결 방법은 상태 관리 라이브러리를 사용하는 것이다. 전역으로 관리하는 저장소에서 state를 꺼내 쓸 수 있기 때문에 Props Drilling을 방지할 수 있다.


Redux의 세 가지 원칙

1. Single source of truth
동일한 데이터는 항상 같은 곳에서 가지고 와야 한다는 의미로 Redux에서는 데이터를 저장하는 단 한개의 공간(Store)이 있음과 연결되는 원칙이다. 사실 여러개의 Store를 사용하는 것은 가능하긴 하나 권장되지는 않는다.

2. State is read-only
상태는 읽기 전용이라는 뜻으로 React에서 상태 갱신 함수(setState)로만 상태를 변경하고 배열은 push가 아닌 concat, 객체의 경우 마찬가지로 기존의 객체는 건드리지 않고 Object.assign를 사용하거나 spread 연산자를 사용하는 것처럼 Redux의 상태도 직접 변경할 수 없음을 의미한다. 즉, Action 객체가 있어야만 상태를 변경할 수 있음과 연결되는 원칙이다.

3. Changes are made with pure functions
변경은 순수 함수로만 가능하다는 뜻으로 상태가 예상치 못한 값으로 변경되는 일이 없도록 순수 함수로 작성되어야하는 Reducer와 연결되는 원칙이다. 동일한 매개변수로 호출된 Reducer 함수는 언제나 똑같은 결과값을 반환해야한다.


Redux의 구조

Redux의 상태 관리 순서

Redux에서는 Action → Dispatch → Reducer → Store 순으로 데이터가 단방향으로 흐른다.

  1. 상태가 변경되어야 하는 이벤트가 발생하면 변경될 상태에 대한 정보가 담긴 Action 객체 생성
  2. Action 객체는 Dispatch 함수의 인자로 전달
  3. Dispatch 함수는 Action 객체를 Reducer 함수로 전달
  4. Reducer 함수는 Action 객체의 값을 확인하고 그 값에 따라 Store의 상태를 변경
  5. 상태가 변경되면 React는 화면을 re-rendering

1. Redux 설치

npm i redux react-redux

2. Store

Store는 상태가 관리되는 하나뿐인 저장소이며 state가 저장되는 공간이다.

// 1. store를 손쉽게 사용할 수 있게 하는 컴포넌트인 Provider 불러오기
import { Provider } from 'react-redux';

// 2. redux로 부터 createStore 불러오기
import { createStore } from 'redux';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

const reducer = () => {};

// 3 변수 store에 createStore 메서드를 통해 reducer 함수 전달
const store = createStore(reducer);

root.render(
   // 4. 전역 상태 저장소 store를 사용하기 위해 App 컴포넌트를 Provider로 감싼 후 props를 통해 store 전달
  <Provider store={store}>
    <App />
  </Provider>
);

3. Reducer

Reducer는 Dispatch에게서 전달받은 Action 객체의 type 값에 따라서 상태를 변경시키는 함수이다.

// 초기값 = 1
const count = 1

// Reducer 함수의 첫번째 인자에는 default value, 두번째 인자에는 action 객체가 들어간다.
const counterReducer = (state = count, action) => {

  // Action 객체의 type 값에 따라 분기하는 switch 조건문
  switch (action.type) {

    // action === 'INCREASE'일 경우
    case 'INCREASE':
			return state + 1

    // action === 'DECREASE'일 경우
    case 'DECREASE':
			return state - 1

    // action === 'SET_NUMBER'일 경우
    case 'SET_NUMBER':
			return action.payload

    // 해당 되는 Action이 없을 경우 기존 상태를 그대로 반환
    // default를 분기해주지 않으면 첫 렌더링 시 undefined가 출력된다.
    default:
      return state;
	}
} // Reducer가 리턴하는 값이 새로운 상태가 된다.
  • 첫번째 인자에 default value를 설정하지 않을 경우 undefined가 할당되기 때문에 오류가 발생할 수 있다.
  • Reducer는 순수함수여야 하는데 외부 요인으로 인해 기대한 값이 아닌 예상치 못한 값으로 상태가 변경되는 일이 없어야하기 때문이다.

3-1. combineReducers

여러 개의 Reducer를 사용하는 경우, Redux의 combineReducers 메서드를 사용해서 하나의 Reducer로 합쳐줄 수 있다.

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
  counterReducer,
  diffReducer,
  ...
});

4. Action

어떤 액션을 취할 것인지 정의해 놓은 객체이며 Dispatch를 통해 Reducer의 두번째 인자로 전달된다.

// payload가 필요 없는 경우
{ type: 'INCREASE' }

// payload가 필요한 경우
{ type: 'SET_NUMBER', payload: 5 }
  • Action 객체가 어떤 동작을 하는지 명시해주는 역할이기 때문에 type을 필수로 지정해 주어야 한다.
  • 대문자와 Snake Case로 작성하며 필요에 따라 payload를 작성해 구체적인 값을 전달한다.

보통 Action을 직접 작성하기 보다 Action 객체를 생성하는 함수인 액션 생성자(Action Creator)를 만들어 사용하는 경우가 많다.

// payload가 필요 없는 경우
const increase = () => {
  return {
    type: 'INCREASE'
  }
}

// payload가 필요한 경우
const setNumber = (num) => {
  return {
    type: 'SET_NUMBER',
    payload: num
  }
}

5. Dispatch

Dispatch는 Reducer로 Action을 전달해주는 함수이다. Dispatch의 전달인자로 Action 객체가 전달된다.

// Action 객체를 직접 작성하는 경우
dispatch( { type: 'INCREASE' } );
dispatch( { type: 'SET_NUMBER', payload: 3 } );

// 액션 생성자(Action Creator)를 사용하는 경우
dispatch( increase() );
dispatch( setNumber(3) );

Redux Hooks

앞서 Store, Reducer, Action, Dispatch에 대해 알아보았다. 이제 이 개념들을 연결시켜 주어야 하는데 이때 사용되는 것이 Redux Hooks의 useDispatch()와 useSelector() 메서드이다.

1. useDispatch()

useDispatch() 메서드는 Action 객체를 Reducer로 전달해 주는 Dispatch 함수를 반환하는 메서드이다. 위의 Dispatch 예시에서 useDispatch() 메서드가 사용되었다.

import { useDispatch } from 'react-redux'

const dispatch = useDispatch()

dispatch(increase())
console.log(counter)  // 2

dispatch(setNumber(5))
console.log(counter)  // 5

// 이벤트 핸들러 내에서 사용
const handlePlus = () => {
  dispatch(increase());
};

2. useSelector()

컴포넌트와 state를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드이다. state가 필요한 컴포넌트에서 store에 저장된 state를 불러온다.

import { useSelector } from 'react-redux'

const counter = useSelector((state) => state)
console.log(counter) // 1

return (
  <div>
    {`카운트: ${state}`}
  </div>
);
  • useSelector의 콜백 함수 인자에 store에 저장된 모든 state가 담긴다.

상태관리 라이브러리 종류

  1. Redux
  2. Redux-toolkit
  3. Recoil
  4. MobX
  5. Zustand
profile
주니어 프론트엔드 개발자 uk입니다.

0개의 댓글