redux-toolkit의 구조
예제
const counterSlice = createSlice({
name: 'counter',
initialState: {value: 0},
reducers: {
increase: (state, action) => {
state.value = state.value + action.payload
}
}
})
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
})
const counterSlice = createSlice({
name: 'counter',
initialState: {value: 0},
reducers: {
increase: (state, action) => {
state.value = state.value + action.payload
}
}
})
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
})
export default function App() {
return (
<div id='container'>
<h1>Root</h1>
<Provider store={store}>
<A />
</Provider>
</div>
);
}
function A() {
return (
<div>
<h1>
A :
</h1>
<B />
</div>
)
}
function B() {
// 😀 여기서 state인 큰 store의 state 즉, 모든 slice의 state를 의미하므로
// 😀 우리는 counter라는 slice의 state의 값을 사용할 것이고, 초기값을 value
// 😀 라고 지정을 했기 때문에 state.counter.value로 해당 값을 가져 오면 된다.
const number = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<h1>
B : {number}
</h1>
<button onClick={() => {
// 😀 dispatch를 할때는 toolkit에서 제공하는 actions를 사용하면 따로
// 😀 타입을 지정하지 않고 아래 처럼 counterSlice의 actions중 increase타입
// 😀 의 괄호 안에 값을 전달하면 해당 값이 action의 payload로 전달이 된다.
dispatch(counterSlice.actions.increase(2))
}}>숫자 증가</button>
</div>
)
}
공식 사이트에서 제공하는 예제
npx create-react-app my-app --template redux-typescript
app 폴더 안에 store파일이 있다. features 폴더안에 counterSlice와 ui를 보여주는 컴포넌트 등이 있다.
store 생성 할 때 reducer함수를 넣어 줘야 하기 때문에 먼저 reducer함수를 살펴보자.
toolkit에서는 slice라는 작은 store가 있는데 여기에 reducer를 만들어 줘야 한다. 따라서 CounterSlice파일의 코드를 살펴 보자.
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../app/store';
import { fetchCount } from './counterAPI';
// 😀 초기 state의 타입 지정
export interface CounterState {
value: number;
status: 'idle' | 'loading' | 'failed';
}
// 😀 slice에 전달할 초기 state 지정
// 🔥 여기서 초기값은 반드시 initialState라는 이름으로 만들어야 한다. 그렇지 않으면 에러 발생
const initialState: CounterState = {
value: 0,
status: 'idle',
};
// 😀 createAsyncThunk를 통해 비동기 작업을 만들어 주는 action을 생성한다.
// 먼저 action의 타입을 적어주고, action이 실행되었을 때 처리될 함수를 입력한다.
export const incrementAsync = createAsyncThunk(
'counter/fetchCount',
async (amount: number) => {
const response = await fetchCount(amount);
return response.data;
}
);
// 😀 createSlice를 통해 slice를 만들어 주고 여기에 name, 초기 state, reducer를 입력한다.
// 😀 reducers에는 reducer함수에 전달 할 action의 타입을 지정해 둔다.
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
// 😀 action의 타입이 increment일때 state를 + 1
increment: (state) => {
state.value += 1;
},
// 😀 action의 타입이 decrement일때 state를 - 1
decrement: (state) => {
state.value -= 1;
},
// 😀 action의 타입이 incrementByAmount일때 payload에 전달된 값을 +
// 😀 여기서 PayloadAction은 payload의 타입을 지정할 수 있는 제네릭 이다.
// 😀 incrementByAmount의 경우만 전달 받은 payload값을 이용하여 state를 변경시키기 때문에
// 😀 여기서는 payload값을 사용하기 위해 action을 인자로 받는 것
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
// 😀 extraReducers는 다른 곳에서 정의된 액션생성함수를 사용하고 싶을 때 사용한다,
// 가장 흔한 케이스는 비동기를 위해 createAsyncThunk 를 사용하여 정의된 액션함수를 사용하거나,
// 다른 slice 에서 정의된 액션함수를 사용하는 경우이다.
// 아래의 extraReducers안에 있는 incrementAsync는 counterSlice 외부에 있는 액션 함수다.
// 비동기 함수가 pending일때, fulfilled일때, rejected일때 각각의 reducer를 정의
extraReducers: (builder) => {
builder
// 😀 비동기 함수가 pending상태 일때는 status를 loading으로 바꿔주고
.addCase(incrementAsync.pending, (state) => {
state.status = 'loading';
})
// 😀 비동기 작업이 완료가 되면 status를 idle로 바꾸고, 비동기 함수에서 리턴하는
// 값이 action.payload로 들어 가고, 그 다음value에 payload값을 +
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = 'idle';
state.value += action.payload;
})
// 😀 비동기 작업이 정상적으로 수행되지 못하면 status를 failed로 바꿔 준다.
.addCase(incrementAsync.rejected, (state) => {
state.status = 'failed';
});
},
});
// 😀 counterSlice의 reducers에 정의한 action의 타입들을 export
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 😀 useSelector를 이용하여 state를 가져올때 인자로 넣어줄 함수
export const selectCount = (state: RootState) => state.counter.value;
// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState());
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount));
}
};
export default counterSlice.reducer;
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
// 😀 스토어를 만들고 counterSlice에서 만든 reducer를 전달
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;