Today I Learned
- Redux
Redux를 사용하면 Redux가 전역 상태를 관리할 수 있는 Store 제공하기 때문에 이 문제들을 해결할 수 있다.
Action 객체
가 생성된다.Action 객체
는 Dispatch 함수
의 인자로 전달된다.Dispatch 함수
는 Action 객체
를 Reducer 함수
로 전달한다.Reducer 함수
는 Action 객체
의 값을 확인하고, 그 값에 따라 전역 상태 저장소 Store
의 상태를 변경한다.즉, Redux에서는 Action
-> Dispatch
-> Reducer
-> 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>
);
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,
...
});
어떤 액션을 취할 것인지 정의해 놓은 객체로, 보통 객체 생성 함수(액션 생성자)를 만들어 사용한다.
액션은 type
과 payload
로 구성된다.
type
: 필수 지정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>
);
Reducer
로 Action 객체
를 전달해주는 함수이다.
// Action 객체를 직접 작성하는 경우
dispatch({ type: 'INCREASE', number: 5 });
dispatch({ type: 'RESET_COUNTER' });
// 액션 생성자를 사용하는 경우
dispatch(increase(5));
dispatch(reset());
위와 같이 dispatch 함수
를 사용하려면 Redux Hooks
의 useDispatch()
불러와야 한다. Redux Hooks
를 알아보면서 나머지 코드를 완성해보자.
React-Redux
에서는 Redux를 사용할 때 활용할 수 있는 Hooks 메서드를 제공한다. 그 중 useSelector()
, 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()
는 컴포넌트와 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에는 데이터를 저장하는 단 하나의 Store를 의미한다.
상태는 읽기 전용이라는 뜻으로, React에서 상태갱신함수로만 상태를 변경할 수 있었던 것처럼, Redux의 상태도 직접 변경할 수 없음을 의미한다. 즉, Action 객체가 있어야만 상태를 변경할 수 있다는 뜻이다.
변경은 순수함수로만 가능하다는 뜻으로, Reducer는 순수함수이어야 한다는 것을 의미한다.