[React] 상태 관리 (2) - Redux

선정·2022년 7월 6일
0

Today I Learned

  • Redux

Redux

기존 React 데이터 흐름에서의 문제

  • 해당 상태를 직접 사용하지 않는 컴포넌트도 상태 데이터를 가짐
  • 상태 끌어올리기, props drilling을 여러번 거쳐야 함
  • 애플리케이션이 복잡해질수록 데이터 흐름도 복잡해짐
  • 컴포넌트 구조가 바뀌면, 기존 데이터 흐름을 완전히 바꿔야 할 수도 있음

Redux를 사용하면 Redux가 전역 상태를 관리할 수 있는 Store 제공하기 때문에 이 문제들을 해결할 수 있다.



Redux의 구조

Redux의 작동 방식

  1. 상태 변경 이벤트가 발생하면, 변경될 상태에 대한 정보가 담긴 Action 객체가 생성된다.
  2. Action 객체Dispatch 함수의 인자로 전달된다.
  3. Dispatch 함수Action 객체Reducer 함수로 전달한다.
  4. Reducer 함수Action 객체의 값을 확인하고, 그 값에 따라 전역 상태 저장소 Store의 상태를 변경한다.
  5. 상태가 변경되면, React는 화면을 리렌더링 한다.

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



Store

상태가 관리되는 오직 하나뿐인 저장소로, redux의 createStore 메서드의 인자로 Reducer를 전달해 Store를 생성할 수 있다.


index.js

import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import App from './App';

const reducer = () => {}; // 임의의 reducer
const store = createStore(reducer); // store 생성

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

root.render(
  <StrictMode>
    <Provider store={store}> // Provider 컴포넌트의 props로 store 전달
      <App />
    </Provider>
  </StrictMode>
);


Reducer

Dispatch 함수를 통해 전달받은 Action 객체의 type 값에 따라서 상태를 변경시키는 함수이다. 이 때, Reducer는 순수함수여야 한다.

순수함수
외부의 상태를 변경하지 않으면서 같은 입력에 대해서 같은 결과를 리턴하는 함수


index.js

import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import App from './App';

// counter 관련 reducer 작성
const initialState = 0
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREASE':
      return state + action.number;
    case 'DECREASE':
      return state - action.number;
    case 'RESET_COUNTER':
      return initialState;
    default:
      return state;
  }
};
const store = createStore(reducer);

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

root.render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
);

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

import { combineReducers } from 'redux';

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


Action

어떤 액션을 취할 것인지 정의해 놓은 객체로, 보통 객체 생성 함수(액션 생성자)를 만들어 사용한다.
액션은 typepayload로 구성된다.

  • type : 필수 지정
    • 액션 객체가 어떤 동작을 하는지 명시함
    • 대문자와 Snake Case로 작성
  • payload : 필요에 따라 선택적으로 지정
// payload 없음
const onReset = () => {
  return {
    type: 'RESET_COUNTER'
  }
};

// payload 전달
const onIncrease = (number) => {
  return {
    type: 'INCREASE',
    number
  }
};

index.js

import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import App from './App';

// 액션 생성자 함수 작성 + export
export const increase = (number) => ({ type: 'INCREASE', number });
export const decrease = (number) => ({ type: 'DECREASE', number });
export const reset = () => ({ type: 'RESET_COUNTER' });

const initialState = 0;
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREASE':
      return state + action.number;
    case 'DECREASE':
      return state - action.number;
    case 'RESET_COUNTER':
      return initialState;
    default:
      return state;
  }
};
const store = createStore(reducer);

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

root.render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>
);


Dispatch

ReducerAction 객체를 전달해주는 함수이다.

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

// 액션 생성자를 사용하는 경우
dispatch(increase(5));
dispatch(reset());

위와 같이 dispatch 함수를 사용하려면 Redux HooksuseDispatch() 불러와야 한다. Redux Hooks를 알아보면서 나머지 코드를 완성해보자.



Redux Hooks

React-Redux에서는 Redux를 사용할 때 활용할 수 있는 Hooks 메서드를 제공한다. 그 중 useSelector(), useDispatch()에 대해 알아보자.

useDispatch()

Action 객체를 Reducer로 전달해 주는 Dispatch 함수를 반환하는 메서드이다. 이전의 dispatch 함수useDispatch()를 사용해서 만든 것이다.

import { useDispatch } from 'react-redux';

const dispatch = useDispatch();

dispatch(increase(5));
dispatch(reset());

App.js

import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { increase, decrease, reset } from './index';
import './style.css';

export default function App() {
  const [number, setNumber] = useState(0);
  const dispatch = useDispatch();

  const onChange = (e) => {
    setNumber(parseInt(e.target.value, 10));
  };
  const onIncrease = () => {
    dispatch(increase(number));
  };
  const onDecrease = () => {
    dispatch(decrease(number));
  };
  const onReset = () => {
    dispatch(reset());
  };

  return (
    <div>
      <h1>{`Count: ${0}`}</h1>
      <input type="number" value={number} onChange={onChange} />
      <div>
        <button onClick={onIncrease}>+</button>
        <button onClick={onDecrease}>-</button>
        <button onClick={onReset}>reset</button>
      </div>
    </div>
  );
}


useSelector()

useSelector()는 컴포넌트와 Store를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드이다.

import { useSelector } from 'react-redux';

const counter = useSelector((state) => state);

App.js

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { increase, decrease, reset } from './index';
import './style.css';

export default function App() {
  const [number, setNumber] = useState(0);
  const dispatch = useDispatch();
  const counter = useSelector((state) => state); // useSelector로 state 가져오기

  const onChange = (e) => {
    setNumber(parseInt(e.target.value, 10));
  };
  const onIncrease = () => {
    dispatch(increase(number));
  };
  const onDecrease = () => {
    dispatch(decrease(number));
  };
  const onReset = () => {
    dispatch(reset());
  };

  return (
    <div>
      <h1>{`Count: ${counter}`}</h1>
      <input type="number" value={number} onChange={onChange} />
      <div>
        <button onClick={onIncrease}>+</button>
        <button onClick={onDecrease}>-</button>
        <button onClick={onReset}>reset</button>
      </div>
    </div>
  );
}


Redux의 세 가지 원칙

1. Single source of truth

동일한 데이터는 항상 같은 곳에서 가지고 와야 한다. 즉, Redux에는 데이터를 저장하는 단 하나의 Store를 의미한다.

2. State is read-only

상태는 읽기 전용이라는 뜻으로, React에서 상태갱신함수로만 상태를 변경할 수 있었던 것처럼, Redux의 상태도 직접 변경할 수 없음을 의미한다. 즉, Action 객체가 있어야만 상태를 변경할 수 있다는 뜻이다.

3. Changes are made with pure functions

변경은 순수함수로만 가능하다는 뜻으로, Reducer는 순수함수이어야 한다는 것을 의미한다.

profile
starter

0개의 댓글