본문 바로가기

온라인 강의(유데미, 인프런 등)/React 완벽 가이드(유데미)

[react & Redux] Redux 개념 및 예시 간단하게 알아보기

반응형

Redux

Redux를 이용하면 전역상태 관리를 할 수 있다. 모든 JavaScript 프로젝트에서 Redux를 사용할 수 있으며 React에서 Redux를 연결하기 위해선 react-redux를 추가로 설치해야 한다.

npm install redux react-redux

크게 전역 상태를 보관하는 하나의 중앙 데이터 저장소 Store, 상태 저장소에 접근하기 위한 reducer, reducer에 행동을 지시하는 action, 저장소에 보관된 상태를 가져오는 subscription으로 나뉘어 있다.

전역 상태 관리 외에도 로컬스토리지 상태저장, 버그리포트 첨부 기능 등의 기능들을 사용할 수 있어 상태 및 변경되는 데이터를 관리하는데 도움이 된다.

 

Store

리액트에서는 Store를 주로 index.js에 정의한다

createStore : 저장소 생성
const store = createStore(리듀서 함수)

react-redux 라이브러리에서 제공하는 Provider를 이용하여 Store(전역 상태)사용을 원하는 컴포넌트를 감싸주고 store를 전달한다.

<Provider store={store}>

Reducer

reducer 함수의 아규먼트로 state 초기값과, action 객체를(type 검사를 통해 데이터를 수정) 미리 정의해 놓고 사용해야 한다.

단, 절대로 기존 상태를 직접 조작하면 안된다. 상태가 추가되는 것이 아닌 덮어씌워지게 되므로 전체 상태를 "복사"하여 상태를 갱신한 후에 반환해야 한다.

정의된 수정방법을 실행하려면 dispatch() 함수를 이용한다. dispatch 할 때 데이터(페이로드)를 실어 보낼 수도 있다.

reducer가 여러개일땐 redux 라이브러리에서 제공하는 combineReducers를 이용할 수 있다.

createStore(combineReducers({ reducer1, reducer2 }))

const data1 = useSelector((state) => state.reducer1);
const data2 = useSelector((state) => state.reducer2);

Subscription

react-redux 라이브러리에서 제공하는 Hooks가 있다.

  • useStore
    • 컴포넌트 안에서 store를 사용할 수 있다.
    • 이미 변경이 완료된 값을 가져오는 것이기에 상태변화를 감지 못해서 useEffect가 인식을 못한다.
    • const store = useStore()
    • store.getState()
  • useSelector
    • useSelector(selector : Function, deps : any [])
    • const counter = useSelector(state => state.counter)
    • 해당 redux의 상태가 바뀌면 그 state를 사용하는 컴포넌트가 리렌더링 된다.
    • 성능 최적화를 위해 store에 저장된 state를 쪼개서 필요한 것만 가져올 수 있다.
    • deps 배열은 어떤 값이 바뀌었을 때 selector를 재정의 할지 설정해 준다.
    • deps 값을 생략하면 매번 렌더링 될 때마다 selector 함수도 새로 정의된다.
  • useDispatch
    • redux store에 설정된 action에 대한 dispatch를 연결하는 hook
    • const dispatch = useDispatch()

 

[예시]

// store/index.js 정의
import { createStore } from 'redux';

const initialState = { counter: 0, showCounter: true };

const counterReducer = (state = initialState, action) => {
  if (action.type === 'increment') {
    return {
      counter: state.counter + 1,
      showCounter: state.showCounter,
      // 주의! 절때 state 직접 수정은 금물!!!
      // 데이터가 추가되는것이 아닌 덮어씌어지기때문에 값의 변화가 없더라도 명시해야한다.
    };
  }
  if (action.type === 'increase') {
    return {
      counter: state.counter + action.amount,
      showCounter: state.showCounter,
    };
  }
  if (action.type === 'decrement') {
    return {
      counter: state.counter - 1,
      showCounter: state.showCounter,
    };
  }
  if (action.type === 'toggle') {
    return {
      counter: state.counter,
      showCounter: !state.showCounter,
    };
  }

  return state;
};

const store = createStore(counterReducer);

export default store;


// src/index.js 정의
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import App from './App';
import store from './store/index';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);


// 최종전달 컴포넌트
import { useSelector, useDispatch } from 'react-redux';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector(state => state.counter);
  const show = useSelector(state => state.showCounter);

  const incrementHandler = () => {
    dispatch({ type: 'increment' });
  };

  const increaseHandler = () => {
    dispatch({ type: 'increase', amount: 5 });
  };
  
  const decrementHandler = () => {
    dispatch({ type: 'decrement' });
  };
  
  const toggleCounterHandler = () => {
    dispatch({ type: 'toggle' });
  };

  return (
    <main>
      <h1>Redux Counter</h1>
      {show && <div className={classes.value}>{counter}</div>}
      <div>
        <button onClick={incrementHandler}>Increment</button>
        <button onClick={increaseHandler}>Increment by 5</button>
        <button onClick={decrementHandler}>Decrement</button>
      </div>
      <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
  );
};
반응형