React에서 Redux를 사용해서 상태 관리하기
// npx
npx create-react-app .
// npm
npm init react-app .
// yarn
yarn create react-app .
// npm
npm install redux react-redux
// yarn
yarn add redux react-redux
카운터 값과 더하기 빼기 버튼이 있는 컴포넌트를 만들자. 당연히 지금은 아무 동작도 하지 않는다.
component/Counter.js
const Counter = () => {
const counter = 0;
return (
<div>
<h2>{counter}</h2>
<div>
<button>더하기</button>
<button>빼기</button>
</div>
</div>
);
};
const store = createStore(리듀서);
const reducer = (기존상태, 액션) => {
// 액션에 따라 새로운 상태를 반환
}
store는 위와 같이 createStore
를 사용해서 만들 수 있다. createStore
의 인자로 넘기는 리듀서는 기존 상태를 복사하여 새로운 상태를 반환한다. 여기서 주의할 점은 리듀서 함수는 반드시 기존 state
값을 직접적으로 변경해서는 안된다는 것이다. (state.counter++;
금지)
또한 리액트 앱 하나에 딱 하나의 store만 존재해야 한다. 여기서는 상태가 counter
하나 뿐이지만 복잡한 상태를 다루는 앱의 경우에도 store는 하나만 있어야 한다.
store/index.js
import { createStore } from 'redux';
const initialState = { counter: 0 };
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD':
return { counter: state.counter + action.value };
case 'SUB':
return { counter: state.counter - action.value };
}
default:
return { ...state };
}
const store = createStore(counterReducer);
export default store
initialState
로 초기값을 정해주지 않으면 초기 state가 undefined
타입이 되기 때문에 에러가 발생한다.counterReducer
는 counter 상태를 변화시키는 함수다. store
를 외부 Provider의 prop으로 넘겨야 하므로 export 한다. const state = useSelector(state => state);
react-redux 의 useSelector
를 사용해서 store의 특정 state에 접근할 수 있다. 콜백 함수의 반환값에 접근한다.
component/Counter.js
import { useSelector } from 'react-redux';
const Counter = () => {
const counter = useSelector(state => state.counter);
return (
<div>
<h2>{counter}</h2>
<div>
<button>더하기</button>
<button>빼기</button>
</div>
</div>
);
};
useSelector
의 콜백함수를 통해 state.counter
값에 접근하여 컴포넌트에 counter를 가져온다.const dispatch = useDispatch();
dispatch( /* { 액션 객체 } */ );
react-redux 의 useDispatch
를 사용해서 store의 특정 action에 접근할 수 있다. dispatch
인자로 액션 객체를 넘기면, 리듀서에서 액션 객체의 값에 맞게 새로운 state 값을 업데이트하게 된다.
component/Counter.js
import { useSelector, useDispatch } from 'react-redux';
const Counter = () => {
const counter = useSelector(state => state.counter);
const dispatch = useDispatch();
const addHandler = () => {
dispatch({ type: 'ADD', value: 1 });
};
const subHandler = () => {
dispatch({ type: 'SUB', value: 1 });
};
return (
<div>
<h2>{counter}</h2>
<div>
<button onClick={addHandler}>더하기</button>
<button onClick={subHandler}>빼기</button>
</div>
</div>
);
};
dispatch
의 action.type
과 action.value
를 다르게 설정하면 리듀서를 통해 상태를 다르게 업데이트 할 수 있다. onClick
에 해당하는 함수를 dispatch
를 통해 정의할 수 있다.<Provider store={store}>
<App />
</Provider>
최상위 컴포넌트를 Provider 로 감싸서 store를 제공하면, 내부 컴포넌트에서 리덕스의 store를 전역적으로 사용할 수 있다.
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);