📎 패키지 설치
npm install @reduxjs/toolkit
📎 store폴더 생성
import {...} from '@reduxjs/toolkit'
createSlice({name: '', initialState: '', reducers: {}})
configureStore({reducer: ...})
reducer가 한개일 경우
: ex) configureStore({reducer: counterSlice.reducer})reducer가 여러개일 경우
: reducer map을 생성하여 리듀서마다 key값을 설정한다. 나중에 하나의 주요 리듀서로 자동 합쳐지고, 주요 리듀서를 스토어에 노출한다. ex) configureStore({reducer :{ counter: counterSlice.reducer, auth: authSlice.reducer }})리듀서Slice.actions
👾 store/index.js - toolkit사용 전
const counterReducer = (state = initialState, action) => {
if (action.type === "increment") {
return {
counter: state.counter + action.amount,
showCounter: state.showCounter,
};
}
if (action.type === "decrement") {
return {
counter: state.counter - action.amount,
showCounter: state.showCounter,
};
}
if (action.type === "toggle") {
return {
showCounter: !state.showCounter,
counter: state.counter,
};
}
return state;
};
const store = createStore(counterReducer);
export default store;
👾 store/index.js - toolkit 사용
import { createSlice } from '@reduxjs/toolkit'
const initialState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name: 'counter', // 식별자 설정
initialState: initialState, // 초기상태 설정
reduders: { // 리듀서 설정
increment(state, action) {
state.counter = state.counter + action.payload;
},
decrement(state, action) {
state.counter = state.counter - action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
}
}
});
// action에 따라 자동으로 메서드가 리덕스에 의해 호출 됨.
// 스토어는 루트 리듀서 하나만 가지고 있음!
const store = configureStore({reducer: counterSlice.reducer});
// 액션 식별자 값을 얻으려면 counterSlice.actions 사용
// 액션이 필요한 컴포넌트에서 사용 가능하게 export 함!
export const counterActions = counterSlice.actions;
// counterSlice.actions가 reduders객체를 반환 함!
export default store;
🔥 increment(state, action) { state.counter = state.counter + action.amount; },
👉🏻 여전히 원래 있는 상태를 변경할 수 없지만, React Toolkit에서 제공하는 createSlice를 사용하면 기존상태를 직접적으로 변경할 수 있음.
👉🏻 왜냐하면 React Toolkit 내부적으로 immer라는 다른 패키지를 사용하는데 이런 코드를 감지하고 자동으로 원래 있는 상태를 복제하고 새로운 상태 객체를 생성하여 오버라이드를 하기 때문이다.
👉🏻 createSlice()를 사용하면 불변성을 고려하지 않고 직접 상태 업데이트할 수 있다. -> 내부적으로 알아서 변경할 수 없는 코드로 변경함.
👾 Counter.js
import { useSelector, useDispatch } from "react-redux";
import { counterActions } from '../store/index';
const Counter = () => {
const dispatch = useDispatch();
const counter = useSelector((state) => state.counter);
const show = useSelector((state) => state.showCounter);
const incrementHandler = () => {
// action객체가 자동 생성 후 increment메서드 호출
// payload가 있을 경우 인자로 전달
dispatch(counterActions.increment(10));
// Toolkit이 { type: SOME_UNIQUE_IDNETIFIER, payload: 10}을 생성하여 자동으로 전달!!
// 이때 payload는 기본값으로 사용하는 필드임!!
};
const decrementHandler = () => {
dispatch(counterActions.decrement(10));
};
const toggleCounterHandler = () => {
dispatch(counterActions.toggleCounter());
};
return (
<main>
<h1>Redux Counter</h1>
{show && <div>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={increaseHandler}>Increase by 10</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
next(action)
: action을 받기 때문에 다른 action객체를 보낼 수 있음.import { applyMiddleware } from 'redux';
const loggerMiddleware = (store) => (next) => (action) => {
// 액션을 다음 미들웨어로 넘김
// 더 이상 미들웨어가 없으면 리듀서에 전송
const result = next(action);
// 스토어 상태 기록
console.log(store.getState());
return result;
// 여기서 반환하는 값은 store.dispatch(ACTION_TYPE) 했을때의 결과로 설정됨
// next(action) = {type: "AGE_UP", value:1}
}
// 미들웨어가 여러개인경우에는 파라미터로 여러개를 전달해주면 된다. 예: applyMiddleware(a,b,c)
const store = configureStore(
{reducer: counterSlice.reducer},
applyMiddleware(loggerMiddleware)
);
createAsyncThunk('type', async ()=>{})
: 버튼 클릭 시 api호출하여 data를 store에 저장
🔴 thunk사용 전
<button onClick={async ()=>{
const response = await fetch('https://...');
const data = response.json();
dispatch(set(data.value));
// dispatch({type:'counterSlice/set', payload: data.value});
}}>async fetch</button>
👉🏻 api호출이 중복 될 경우, 코드양이 길어지고 가독성이 떨어짐
🔵 thunk사용 후
👾 App.js
import {asyncUpFetch} from './counterSlice';
...
<button onClick={()=>{
dispatch(asyncUpFetch());
}}>async thunk</button>
👉🏻 return data.value
반환된 값이 자동으로 value에 저장됨!!
👾 CounterSlice.js
const asyncUpFetch = createAsyncThunk(
'counterSlice/asyncUpFetch', // action 타입 지정
async () => {
const resp = await fetch('htttps://...');
const data = resp.json();
return data.value;
}
)
const counterSlice = createSlice({
name: 'counterSlice',
initialState: { value:0, status: 'welcome'},
// 동기적인 작업은 reducers에서 작업
reducers: {
up: (state, action) => {
state.value = state.value + action.payload;
}
},
// 비동기적인 작업은 extraReducers에서 작업
extraReducers: (builder) => {
// pending일때 실행할 reduser를 두번째 파라미터에 작성
builder.addCase(asyncUpfetch.pending, (state,action)=>{
state.status = 'Loading';
})
builder.addCase(asyncUpfetch.fulfilled, (state,action)=>{
state.status = 'Complete';
})
builder.addCase(asyncUpfetch.rejected, (state,action)=>{
state.status = 'Fail';
})
}
})
👉🏻 동기적인 작업은 reducers에서 작업하며, 자동으로 type이 생성됨!
👉🏻 비동기적인 작업은 extraReducers에서 작업하며, 자동으로 type이 생성되지 않음!
: 리덕스와 리덕스 상태를 좀 더 쉽게 디버깅할 수 있음.
: 많은 리덕스 상태가 여러 다른 슬라이스로 처리되고 다양한 작업이 진행되는 복잡한 애플리케이션에서는 작업 등의 디버그 상태에서 오류를 찾기 어려울 수 있음.
: 전체 리덕스 스토어의 현재 상태를 살펴볼 수 있음.
: 브라우저 확장으로 사용 및 설치가 가능함.
: Redux toolkit 사용 시 즉시 사용가능.
: Redux toolkit없이 사용하려면 추가코드를 설정해야함.