
학습목표 : Redux, Redux-toolkit을 공부해보자 😎
상태관리 라이브러리 중 하나이고 현재 많은 곳에서 사용되고 있다.
상태관리는 UI에 맞게 데이터를 관리하거나 서버와 주고 받는 데이터를 관리하는 것을 말한다.
복잡하고 크기가 커질수록 상태관리의 난이도가 어려워지기 때문에, Props driling등의 문제를 해결하기 위해 사용된다.
아래와 같은 경우가 있을 때 사용된다.
리덕스는 리액트에 종속된 라이브러리가 아니기 때문에 바닐라 자바스크립트 환경, 리액트환경 등 다양한 환경에서 사용이 가능하다.
데이터를 스토어 > 컴포넌트 > 액션 > 리듀서 > 스토어의 과정으로 변경한다.
아래 사용 방법은 리액트에서 사용하는 기준이다.
import {Provider} from 'react-redux';
import rootReducer from './reducers';
import {createStore} from 'redux';
const store = createStore(rootReducer);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store = {store}>
<App/>
</Provider>
);
createStore는 리듀서를 객체로 반환하여 전달한다. Provider로 감싼다.import {createStore} from 'redux';
const ADD = 'ADD';
const DELETE = 'DELETE';
export const addToDo = text => {
return {
type : ADD,
text
}
};
export const deleteToDo = id =>{
return {
type : DELETE,
id
}
}
const reducer = (state = [], action) => {
switch(action.type){
case ADD:
return [{text:action.text, id:Date.now()},...state];
case DELETE:
return state.filter(toDo => toDo !== action.id);
default:
return state;
}
}
const store = createStore(reducer);
mutate할 수 없다. 새로운 배열을 리턴하여준다. (불변성을 유지해야하기 때문에) Spread Operator혹은 Object.assign()등 기존 state를 복사하여 사용한다. button.onclick = () => {
store.dispatch(ADD(1));
}
function mapStateToProps(state){
return { toDos : state };
}
function mapDispatchToProps(dispatch){
return {
addToDo : text => dispatch(actionCreators.addToDo(text));
};
}
export default connect(mapStateToProps)(Home);
connect는 인자로 state와 dispatch를 가진다. connect대신 사용할 수 있는 문법이 새로 생겼다. useSelector()를 사용하면 리덕스의 상를 조회할 수 있다. useDispatch()를 사용하면 생성한 액션을 발생시키며, 액션생성 함수를 가져온다. import {useDispatch, useSelector} from 'react-redux';
import {plus, minus} from './counter';
export default function App() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick = {() => dispatch(minus())}>-</button>
value : {count}
<button onClick = {() => dispatch(plus())}>+</button>
</div>
);
}
다소 복잡한 리덕스의 사용을 편하게 사용할 수 있게 해주는 라이브러리이다.
npm install @reduxjs/toolkit
configureStore() : 기존 createStore를 단순화하고, redux-thunk를 기본적으로 포함한다. 그외에도 자동으로 슬라이스 리듀서를 결합하고, Redux DevTools Extension을 사용할 수 있다. createReducer() : 기존 리듀서를 작성할 때 스위치문을 사용하고 불변성을 유지해야했지만, 해당 api는 immer를 지원하여 mutate된 값을 넣어도 자동으로 이를 인식하고 사용할 수 있게 해준다. createAction() : 주어진 액션 유형 문자열에 대한 액션 생성기 함수를 생성한다. createSlice() : 리듀서 함수의 객체, 슬라이스 이름, 초기 상태 값을 받고 해당 액션 생성자와 액션 유형을 사용하여 슬라이스 리듀서를 자동으로 생성한다. createAsyncThunk : 작업 유형 문자열과 프로미스를 반환하는 함수를 받고 해당 프로미스를 기반으로 작업 유형을 전달하는 thunk를 생성한다. (pending/fulfilled/rejected) createEntityAdapter : 저장소에서 정규화된 데이터를 관리하기 위해 재사용 가능한 리듀서 및 어댑터 세트를 생성 // 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 {store} from './store'
import {Provider} from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,document.getElementById('root')
)
// counterSlice.ts
import {createSlice} from '@reduxjs/toolkit`
import type {PayloadAction} from '@reduxjs/toolkit'
export interface CounterState {
value : number
}
const initialState : CounterState = {
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
//store.ts
import {configureStore} from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
export const store = configureStore({
reducer:{
counter:counterReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
/Counter.ts
import type{RootState} from './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>
)
}
createSlice로 리듀서 함수 내부에 있는 케이스 명을 쉽게 대체할 수 있음
const initialState = {
value : 0,
}
export const counterSlice = createSlice({
name : 'counter', // action 이름을 만드는데 사용되는 키
initialState, // 리듀서에서 사용될 state값을 정의
reducers : { // 객체 타입. switch등으로 만들어 사용하던 케이스들을 아래와 같이 설정하면 됨
increment : (state, action) => {
state.value += action.payload.data
}
}
})
export const counterActions = counterSlice.actions // dispatch를 위한 설정
export default counterSlice.reducer;
configureStore를 사용하여 store를 수정할 수 있다.import {configureStore} from '@reduxjs/toolkit'
const store = configureStore({
reducer:{
}
})
export default store;
기존의 dispatch는 아래와 같이 사용했음
dispatch({type : 'ADD', payload : {data});
//createSlice 내 액션값을 가져온다
dispatch(counterActions.increment({data}));