Redux๋ "action"์ด๋ผ๋ ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํด ์ดํ๋ฆฌ์ผ์ด์ ์ state๋ฅผ ๊ด๋ฆฌํ๊ณ ์ ๋ฐ์ดํธํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ํจํด์ด๋ค. Redux๋ state๋ค์ ์ค์ ์ ์ฅ์(centralized store)์ ์ญํ ์ ํ๋ฉฐ state๊ฐ ์์ธก ๊ฐ๋ฅํ ๋ฐฉ์(predictable)์ผ๋ก๋ง ์ ๋ฐ์ดํธ ๋๋๋ก ํ๋ ๊ท์น์ ์ ๊ณตํ๋ค.
Redux์์ ์ ๊ณต๋๋ ํจํด๋ค๊ณผ ๋๊ตฌ๋ค์ ์ฌ์ฉํ๋ฉด ์ธ์ /์ด๋์/์/์ด๋ป๊ฒ state๊ฐ ์ ๋ฐ์ดํธ๋๊ณ , state๊ฐ ์ ๋ฐ์ดํธ ๋ ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ก์ง์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ์ฝ๊ฒ ์ดํดํ ์ ์๋ค.
Redux๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ๋ช ๊ฐ์ง ๊ฐ๋ ๊ณผ ์ฉ์ด์ ๋ํด ์์์ผํ๋ค.
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0);
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter((prevCounter) => prevCounter + 1);
};
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
);
}
์ ์์ ๋ ๋ฒํผ์ ํด๋ฆญํ๋ฉด counter state๊ฐ ์ฆ๊ฐํ๋ Counter
์ปดํฌ๋ํธ์ด๋ค. ์ด ์ปดํฌ๋ํธ๋ ์ธ ๊ฐ์ง ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋ ๋
๋ฆฝํ ์ดํ๋ฆฌ์ผ์ด์
(self-contained app)์ด๋ค.
- state : ์ดํ๋ฆฌ์ผ์ด์ ๊ตฌ๋์ ๋ฐํ์ด ๋๋ ์ํ(the source of truth that drives our app)
- view : UI ์ ์
- actions : ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ฐํ์ผ๋ก ๋ฐ์ํ๋ ์ด๋ฒคํธ์ด๋ฉฐ state๊ฐ ์ ๋ฐ์ดํธ ๋๋ ํธ๋ฆฌ๊ฑฐ
์ด ์ดํ๋ฆฌ์ผ์ด์ ์ state์ ๋ฐ๋ผ view๊ฐ ๋ ๋๋ง๋๋ฉฐ action์ ์ํด state๊ฐ ์ ๋ฐ์ดํธ๋๋ฉด ๋ค์ view๊ฐ ๋ ๋๋ง๋๋ ํ๋ฆ์ธ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ก์ฐ(one-way data flow)์ ์์ด๋ค.
๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ก์ฐ๋ ๋์ผํ state๋ฅผ ๊ณต์ ํ๊ณ ์ฌ์ฉํ๋ ๋ค์์ ์ปดํฌ๋ํธ๊ฐ ์์ ๊ฒฝ์ฐ ๋นํจ์จ์ ์ด๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ ์ปดํฌ๋ํธ์์ ๊ณต์ ๋๋ state๋ฅผ ์ถ์ถํด ์ปดํฌ๋ํธ ํธ๋ฆฌ ์ธ๋ถ ์ค์์ ์์น์์ผ์ ์ปดํฌ๋ํธ๊ฐ ํธ๋ฆฌ์ ์ด๋ ๋ถ๋ถ์ ์์นํ๋ state๋ฅผ ์ก์ธ์คํ ์ ์๊ฒ ํ๋ ๊ฒ์ด๋ค.
์ด๊ฒ์ด Redux์ ๊ธฐ๋ณธ ์์ด๋์ด์ด๋ค.
Redux์์ state์ ์ ๋ฐ์ดํธ์, ๋ถ๋ณ์ฑ์ ์ ์งํด์ผํ๋ ์์น์ด ์๋ค. ๋ฐ๋ผ์ ๊ธฐ์กด์ state๋ฅผ ์ง์ ์์ ํ๋ ๊ฒ์ด ์๋ ๊ธฐ์กด state๋ฅผ ํฌํจํ ์๋ก์ด state๋ฅผ ์์ฑํ๋ ๋ฐฉ์์ผ๋ก state๋ฅผ ์ ๋ฐ์ดํธํ๋ค.
action์ type
ํ๋๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฐ์ฒด์ด๋ฉฐ ์ดํ๋ฆฌ์ผ์ด์
์์ ๋ฐ์ํ ์ด๋ค ๋์์ ์ค๋ช
ํ๋ ์ด๋ฒคํธ์ด๋ค.
action์ ๋ฐ์ํ ๋์์ ๋ํ ์ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ payload
ํ๋๋ฅผ ๊ฐ์ง ์ ์๋ค.
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk',
};
reducer๋ ํ์ฌ์ state์ action์ ์ ๋ฌ ๋ฐ์ผ๋ฉฐ state๋ฅผ ์ด๋ป๊ฒ ์
๋ฐ์ดํธ ํ ๊ฒ์ธ๊ฐ๋ฅผ ๊ฒฐ์ ํ๊ณ ์๋ก์ด state๋ฅผ ๋ฐํํ๋ค. (state,action) => newState
์ ๋ฌ๋ฐ์ action(์ด๋ฒคํธ) type์ ๊ธฐ๋ฐ์ผ๋ก ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๊ฐ๋ ์ผ๋ก ์๊ฐํ๋ฉด ๋๋ค.
reducer๋ ํญ์ ์๋์ ๊ฐ์ ๊ท์น์ ์ง์ผ์ผํ๋ค.
state
์action
์ธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ก์ด state ๊ฐ ๋ง ๊ณ์ฐํด์ผํ๋ค.- ๊ธฐ์กด state๋ฅผ ์์ ํ ์ ์๋ค. ๊ธฐ์กด state๋ฅผ ๋ณต์ฌํ๊ณ ๋ณต์ฌ๋ ๊ฐ์ ๋ณ๊ฒฝํด ๋ถ๋ณ์ฑ์ ์ง์ผ์ผํ๋ค.
- ๋น๋๊ธฐ ๋ก์ง, ๋๋ค ๊ฐ ๊ณ์ฐ ๋ฑ side effect๋ฅผ ๋ฐ์์์ผ์ ์๋๋ค.
reducer๋ ์ฒ๋ฆฌํ action์ธ์ง ํ์ธ ํ, ์ฒ๋ฆฌํ action์ด ๋ง์ ๊ฒฝ์ฐ state์ ๋ณต์ฌ๋ณธ์ ๋ง๋ค์ด ์๋ก์ด ๊ฐ์ผ๋ก ๋ณต์ฌ๋ณธ์ ์ ๋ฐ์ดํธํ๊ณ ๋ฐํํ๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ์๋ ๊ธฐ์กด state๋ฅผ ๋ฐํํ๋ค.
const initialState = { value: 0 };
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/incremented') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1,
};
}
// otherwise return the existing state unchanged
return state;
}
store๋ reducer๋ฅผ ์ ๋ฌ๋ฐ์ ์์ฑ๋๋ฉฐ ํ์ฌ state ๊ฐ์ ๋ฐํํ๋ getState
๋ฉ์๋๊ฐ ์๋ค.
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({ reducer: counterReducer });
console.log(store.getState());
// {value: 0}
Redux store๋ dispatch
๋ผ๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค. state๋ฅผ ์
๋ฐ์ดํธํ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ store.dispatch()
๋ฅผ ํธ์ถํ๊ณ action ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด๋ค. store๋ reducer ํจ์๋ฅผ ์คํํด ์๋ก์ด state ๊ฐ์ ๋ด๋ถ์ ์ ์ฅํ๋ค. ๋ํ getState()
๋ฅผ ํตํด ์
๋ฐ์ดํธ๋ ๊ฐ์ ํ์ธํ ์ ์๋ค.
store.dispatch({ type: 'counter/incremented' });
console.log(store.getState());
// {value: 1}
dispatch๋ action์ ๋ฐ์์ํค๋ ์ด๋ฒคํธ ํธ๋ฆฌ๊ฑฐ ๊ฐ๋ ์ผ๋ก ์๊ฐํ๋ฉด ๋๋ค.
action creator๋ action์ ๋ฐ์์ํค๋ ํจ์์ด๋ค. store.dispatch({ type: '...'})
์ ํตํด action์ ๋ฐ์์ํฌ ์ ์์ง๋ง ๋์ผํ ์์
์ด ๋ฐ๋ณต๋ ๊ฒฝ์ฐ ๋นํจ์จ์ ์ด๋ฏ๋ก action creator๋ฅผ ์ฌ์ฉํด store์ action์ ๋ณด๋ผ ์ ์๋ค.
const doAddToDoItem = (payload) => ({ type: 'TODO_ADDED', payload });
store.dispatch(doAddToDoItem('be awesome today'));
store.dispatch()
๋ถ๋ถ์ ์ํํ๋ ์๋ก์ด "wrapper" ํจ์๋ฅผ ์์ฑํจ์ผ๋ก์จ dispatch ๊ณผ์ ์ ์์ฝ๊ฒ ํ ์ ์๋ค.
// assume we've created a store here
const store = createStore(someReducer);
// and we have our plain action creator function from above
const doAddToDoItem = (payload) => ({ type: 'TODO_ADDED', payload });
// we'd simply need to make a new function that did both things:
const boundActionCreator = (text) => store.dispatch(doAddToDoItem(text));
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ด์ฅ๋ bindActionCreators()
ํจ์๋ฅผ ์ฌ์ฉํด ์ "wrapper" ํจ์๋ฅผ ๋์ฒดํ ์ ์๋ค. bindActionCreators()
ํจ์๋ action creator๋ฅผ ๊ฐ์ ธ์ ์๋์ผ๋ก ๋ฐ์ธ๋ฉ ํด์ฃผ๊ธฐ ๋๋ฌธ์ action์ ์๊ฐ ๋ง์๋ ์ ์ฉํ๋ค.
selelctor๋ store state ๊ฐ์์ ํน์ ๋ถ๋ถ์ ์ ๋ณด๋ฅผ ์ถ์ถํ๋ ํจ์์ด๋ค.
const selectCounterValue = (state) => state.value;
const currentValue = selectCounterValue(store.getState());
console.log(currentValue);
// 2
-
- Action Creator โ Action โ Dispatch โ Reducer โ State
-
- root reducer ํจ์๋ฅผ ์ฌ์ฉํด Redux store ์์ฑ
- store๋ root reducer๋ฅผ ํ๋ฒ ํธ์ถํ๊ณ ๋ฐํ ๊ฐ์ ์ด๊ธฐ state๋ก ์ ์ฅ
- UI๊ฐ ์ฒ์ ๋ ๋๋ง ๋ ๋, UI ์ปดํฌ๋ํธ๋ Redux store์ ํ์ฌ state์ ์ก์ธ์คํ๊ณ ๋ ๋๋ง ํ๊ธฐ ์ํ ๋ฐ์ดํฐ ๊ฒฐ์
-
- ๋ฒํผ ํด๋ฆญ ๋ฑ์ ์ด๋ฒคํธ ๋ฐ์
- store์
dispatch({type: 'counter/incremented'})
์์ ์ ๋ฌ - store๋ ์ด์
state
์ ํ์ฌaction
์ผ๋ก reducer ํจ์๋ฅผ ๋ค์ ์คํํ๊ณ ๋ฐํ ๊ฐ์ ์๋ก์ดstate
๋ก ์ ์ฅ - store์ ๋ฐ์ดํฐ๊ฐ ํ์ํ UI ์ปดํฌ๋ํธ๋ค์ ํ์ํ state๊ฐ ๋ณ๊ฒฝ๋์๋์ง ํ์ธ
- ์๋ก์ด ๋ฐ์ดํฐ๋ก ๋ฆฌ๋ ๋๋ง๋ UI ์ปดํฌ๋ํธ๋ค์ด ํ๋ฉด์ ์ถ๋ ฅ
๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋ฉด action์ด dispatch๋ ๋ค์ reducer์ ์ ๋ฌ๋๊ธฐ ์ ์ ์ถ๊ฐ์ ์ธ ์์ ์ ์ํํ ์ ์๋ค. ์ฃผ๋ก ๋น๋๊ธฐ ์์ ์ ์ํด ์์ฃผ ์ฌ์ฉ๋๋ค.
๋ฏธ๋ค์จ์ด๋ Redux์์ ์ ๊ณต๋๋ createStore
์ applyMiddleware
๋ผ๋ ํน๋ณํ ์คํ ์ด ์ธํธ์(store inhancer)๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋ค.
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducer';
import { print1, print2, print3 } from './exampleAddons/middleware';
const middlewareEnhancer = applyMiddleware(print1, print2, print3);
// Pass enhancer as the second arg, since there's no preloadedState
const store = createStore(rootReducer, middlewareEnhancer);
export default store;
๊ฐ ๋ฏธ๋ค์จ์ด๋ action์ด dispatch ๋ ๋ ๋ฒํธ๋ฅผ ์ถ๋ ฅํ๋ค.
import store from './store';
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' });
// log: '1'
// log: '2'
// log: '3'
๋ฏธ๋ค์จ์ด๋ store์ dispatch
๋ฉ์๋๋ฅผ ์ค์ฌ์ผ๋ก ํ์ดํ๋ผ์ธ์ ํ์ฑํ๋ค. store.dispatch(action)
์ ํธ์ถํ๋ฉด ํ์ดํ๋ผ์ธ์ ์ฒซ๋ฒ์งธ ๋ฏธ๋ค์จ์ด๊ฐ ํธ์ถ๋๊ณ ์์
์ ์ํํ ์ ์๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋ฏธ๋ค์จ์ด๋ reducer์ ๋ง์ฐฌ๊ฐ์ง๋ก ์คํํด์ผํ action์ธ๊ฐ๋ฅผ ํ์ธํ ํ ์ฌ๋ฐ๋ฅธ action์ผ ๊ฒฝ์ฐ ์ฌ์ฉ์๊ฐ ์ง์ ํ ์์
์ ์ํํ๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ ๋ค์ ๋ฏธ๋ค์จ์ด๋ก ์์
์ ์ ๋ฌํ๋ค.
๋ฏธ๋ค์จ์ด๋ ์ธ ๊ฐ์ง์ ์ค์ฒฉ๋ ํจ์๋ก ์์ฑ๋๋ค.
// Middleware written as ES5 functions
// Outer function:
function exampleMiddleware(storeAPI) {
return function wrapDispatch(next) {
return function handleAction(action) {
// Do anything here: pass the action onwards with next(action),
// or restart the pipeline with storeAPI.dispatch(action)
// Can also use storeAPI.getState() here
return next(action);
};
};
}
// ES6 arrow function
const anotherExampleMiddleware = (storeAPI) => (next) => (action) => {
// Do something in here, when each action is dispatched
return next(action);
};
exampleMiddleware
:applyMiddleware
์ ์ํด ํธ์ถ๋์ด์ง๋ "๋ฏธ๋ค์จ์ด" ํจ์์ด๋ฉฐ ์คํ ์ด์{dispatch, getState}
๋ฅผ ํฌํจํstoreAPI
๊ฐ์ฒด๋ฅผ ์ ๋ฌ ๋ฐ๋๋ค.dispatch
ํจ์๋ฅผ ํธ์ถํ๋ฉด ๋ฏธ๋ค์จ์ด ํ์ดํ๋ผ์ธ์ ์์์ผ๋ก action์ ๋ณด๋ธ๋ค.wrapDispatch
:next
์ธ์๋ก ํธ์ถ๋ ํจ์๋ฅผ ๋ฐ๋๋ค. ์ด ํจ์๊ฐ ์ค์ ํ์ดํ๋ผ์ธ์ ์์์ด๋๋ ๋ฏธ๋ค์จ์ด์ด๋ค.next(action)
์ ํธ์ถํด ๋ค์ ๋ฏธ๋ค์จ์ด๋ก ์ ๋ฌํ๋ค.handleAction
:action
์ ์ธ์๋ก ์ ๋ฌ๋ฐ์ action์ด ์ ๋ฌ ๋ ๋๋ง๋ค ํธ์ถ๋๋ค.
Redux Toolkit์ ๋ณต์กํ Redux ์คํ ์ด ๊ตฌ์ฑ, Redux ์ฌ์ฉ์ ์ํ ๋ค์์ ํจํค์ง ํ์์ฑ๊ณผ ๋ง์ ์ฝ๋๋์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ง๋ค์ด์ง ๊ฐ๋ฐ ๋๊ตฌ์ด๋ค.
Redux Toolkit์ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋ค.
- simple : ์คํ ์ด ์ค์ , ๋ฆฌ๋์, ๋ถ๋ณ์ฑ ์ ๋ฐ์ดํธ ๋ก์ง ๋ฑ์ ๊ธฐ์กด Redux์ ์ฌ์ฉ๋ฒ์ ๊ฐํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ ํธ๋ฆฌํฐ ๊ธฐ๋ฅ์ด ์๋ค.
- opinionated : ์คํ ์ด ์ค์ ์ ์ฌ์ฉ๋๋ ๊ธฐ๋ณธ ์ค์ ์ด ์ ๊ณต๋๋ฉฐ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ Redux addon์ด ๋ด์ฅ๋์ด ์๋ค.
- powerful : ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๋ก์ง์ผ๋ก ๋ถ๋ณ์ฑ ๋ก์ง์ ์์ฑํ ์ ์๋ค. state์ ์ ์ฒด slice๋ฅผ ์๋์ผ๋ก ์์ฑํ ์ ์๋ค.
- effective : ์ ์ ์ฝ๋๋ก ๋ง์ ์์ ์ ํ ์ ์๋ค.
Redux Toolkit ๊ณต์ ๋ฌธ์์์ ์๋ก์ด ์ฑ ์์์ ๊ถ์ฅํ๋ ๋ฐฉ๋ฒ์ CRA์ฉ Redux + TypeScript ํ ํ๋ฆฟ์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
// Redux + JS
npx create-react-app app-name --template redux
// Redux + TS
npx create-react-app app-name --template redux-typescript
๋ชจ๋ ๋ฒ๋ค๋ฌ ๋๋ ๋ ธ๋ ์ดํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ npm ํจํค์ง๋ฅผ ์ค์นํด์ผํ๋ค.
npm install @reduxjs/toolkit
// or
yarn add @reduxjs/toolkit
Redux Toolkit์ ํฌํจ๋ api๋ ๋ค์๊ณผ ๊ฐ๋ค.
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: {
ex1: ex1Reducer,
ex2: ex2Reducer,
},
});
configureStore()
๋ Redux์ createStore๋ฅผ ์ฌ์ฉํด ์๋์ผ๋ก ์ฌ๋ผ์ด์ค ๋ฆฌ๋์๋ฅผ ๊ฒฐํฉํ๋ค. Redux ๋ฏธ๋ค์จ์ด์ Redux DevTools์ ์ฌ์ฉํ ์ ์๋ค.
const exReducer = createReducer(0, {
increment: (state, action) => state + action.payload,
});
createReducer()
๋ switch๋ฌธ ์์ฑ์์ด reducer ํจ์๋ฅผ ์ฌ์ฉํ๋ ๋ฉ์๋์ด๋ค. Immer ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ฌ์ฉํด ๋ถ๋ณ์ฑ์ ์ ์งํ๋ฉด์ state๋ฅผ ์
๋ฐ์ดํธํ ์ ์๋ค.
์ฒซ ๋ฒ์งธ ์ธ์๋ ์ด๊ธฐ ๊ฐ, ๋ ๋ฒ์งธ ์ธ์๋ reducer๋ฅผ ๊ฐ์ง๋ค.
const increment = createAction('increment');
createAction()
์ action type์ ๋ฐ๋ผ action create function์ ๋ฐํํ๋ค.
const exSlice = createSlice({
name: 'ex',
initialState: [],
reducers: {
action1(state, action) {
state.ex = action.payload;
},
},
});
createSlice()
๋ action๊ณผ reducer๋ฅผ ๋ชจ๋ ๊ฐ์ง ๋ฉ์๋์ด๋ค. action์ ์ด๋ฆ์ ๋ํ๋ด๋ name
ํ๋กํผํฐ์ ์ด๊ธฐ ๊ฐ์ ๋ฐ์ slice reducer์ action creator, action type์ ์์ฑํ๋ค.
๋ค์์ React, TypeScript, Redux Toolkit์ ์ฌ์ฉํ ์นด์ดํฐ ์์ ์ด๋ค.
configureStore()
๋ฅผ ํตํด ๋ฉ์๋๋ฅผ ์์ฑํ๊ณ index.ts
์์ Provider
๋ฅผ ์ฌ์ฉํด Redux ์คํ ์ด๋ฅผ ์ ๊ณตํ๋ ์ฝ๋์ด๋ค.
// app/store.ts
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: {},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// index.ts
import ReactDOM from 'react-dom';
import App from './App';
import { store } from './app/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
๋ค์์ slice๋ฅผ ์์ฑํ๋ ์ฝ๋์ด๋ค. slice ์์ฑ์ ์ํด์๋ slice ์ด๋ฆ, ์ด๊ธฐ ๊ฐ, state ์ ๋ฐ์ดํธ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ํ๋ ์ด์์ reducer ํจ์๊ฐ ํ์ํ๋ค. ์์ฑ๋ ์ฌ๋ผ์ด์ค๋ action creator์ ์ ์ฒด slice์ ๋ํ reducer ๊ธฐ๋ฅ์ export ํ ์ ์๋ค.
Redux Toolkit์ createSlice
์ createReducer
๋ย Immer ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ ์ฉํด ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๋ก์ง์ผ๋ก ๋ถ๋ณ์ฑ์ ์ ์งํ๋ฉฐ ์
๋ฐ์ดํธํ ์ ์๋ค.
// app/features/counter/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface State {
value: number;
}
const initialState: State = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
๋ค์์ slice reducer๋ฅผ ์คํ ์ด์ ์ถ๊ฐํ๋ ๋จ๊ณ์ด๋ค.
configureStore()
๋ฉ์๋์ reducer
ํ๋กํผํฐ์ ์ด์ ๋จ๊ณ์์ ์ ์ํ๊ณ exportํ reducer importํด ์ถ๊ฐํ๋ค.
// app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export default configureStore({
reducer: {
counter: counterReducer,
},
});
์ด์ ย hooks๋ฅผ ์ฌ์ฉํด ๋ฆฌ์กํธ ๊ตฌ์ฑ์์์ Redux ์คํ ์ด์ ์ธํฐ๋์
ํ ์ ์๋ค. useSelector
์ย useDispatch
๋ฅผย ์ฌ์ฉํด Redux ์คํ ์ด์ state๋ฅผ ๊ฐ์ ธ์ค๊ณ ์
๋ฐ์ดํธํ ์ ์๋ค.
import React from 'react';
import { RootState } from '../../app/store';
import { useSelector, useDispatch } from 'react-redux';
import { decrement, increment } from './counterSlice';
export function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>
<button
aria-label='Increment value'
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label='Decrement value'
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}
Reference