
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './readucer/userSlice'
export default configureStore({
reducer: {
user: userReducer
}
})
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
)
createAction, createReducer, createSlice 함수의 reducer는 동기 함수를 실행하기때문에 redux toolkit에서 제공하는 비동기 처리 API함수를 사용하면 된다.
function createAsyncThunk( type, payloadCreator, options )
첫번째 매개변수 type은 비동기 요청의 생명주기를 나타내는 추가 action type을 생성하는 데 사용되는 문자열이다. 예를 들어 type이 user/getUserInfo라면 이 문자열을 기반으로 비동기 작업의 각 단계에 대한 액션 타입이 자동으로 생성된다. 일반적으로 '상태명/상수명'으로 명명한다.
두번째 매개변수 payloadCreator는 promise를 반환하는 콜백함수로 createAsyncThunk가 호출 될 때 자동으로 실행된다. payloadCreator가 비동기 작업을 수행하고 그 결과로 액션의 payload를 생성하여 비동기 작업의 결과에 따라 액션의 payload가 반환된다.
async (userId) => {
const res = await fetch(`/api/user/${userId}`) // 요청 payload: pending
if (!res.ok) { // 에러 payload: rejected
throw new Error(`failed to get user info`)
}
const data = await res.json() // 응답 payload: fulfilled
returen data.result
}
세번째 매개변수 options는 선택적 설정 객채로 비동기 작업의 동작을 커스터마이징하는 데 사용된다. 여러 종류 중 하나인 condition은 액션이 디스패치되기 전에 특정 조건을 확인하는 함수이고, getState 메서드는 현재 상태를 가져오는 함수로 비동기 작업을 수행하기 전에 현재 상태를 참조해야 할 때 유용하다.
// option + 정리
const getUserInfo = createAsyncThunk(
'user/getUserInfo',
async ({ userId }) => {
const res = await fetch(`/api/user/${userId}`)
if (!res.ok) {
throw new Error('failed to get user info')
}
const data = await res.json()
return data.result
},
{
condition: ({ userId }, { getState }) => { // 이미 로딩 중인지 상태 확인
const { user } = getState()
return !user.loading
},
dispatchConditionRejection: true
}
);
extraReducers는 비동기 작업이 끝난 이후 새로 생성된 타입을 createSlice 내부의 reducers로 처리할 수 없기 때문에, 비동기 작업의 결과를 처리하기 위해 사용된다. createAsyncThunk로 생성된 비동기 작업의 결과를 처리하는 것이 주요 목적이다. extraReducers는 빌더 콜백 표기법을 사용하여 createAsyncThunk의 비동기 작업의 결과를 인자로 받아 각 경우에 맞는 상태 변화를 쉽게 정의할 수 있다.
세가지 case는 다음과 같다.
const userSlice = createSlice({
name: 'user',
initialState: {
status: 'idle',
data: null,
error: null,
},
reducers: {
// 동기 액션 처리
},
extraReducers: (builder) => {
builder
.addCase(getUserInfo.pending, (state) => {
state.status = 'loading'
})
.addCase(getUserInfo.fulfilled, (state, action) => {
state.status = 'idle'
state.data = action.payload
})
.addCase(getUserInfo.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
},
})
addCaes는 첫번째 인자로 액션타입을 받아 해당 액션 타입과 일치하는 경우에 실행될 리듀서를 정의한다. 특정 액션이 디스패치되었을 때 상태를 어떻게 변경할지를 명확하게 지정 할 수 있다.
useDispatch를 사용해서 리액트 컴포넌트에서 데이터를 받아 올 수 있다.
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getUserInfo } from './readucer/userSlice'
const UserInfo = () => {
const dispatch = useDispatch()
const { data, status, error } = useSelector((state) => state.user)
const handleClick = () => {
dispatch(getUserInfo({ userId: 1 }))
}
return (
<div>
<button onClick={ handleClick }>Get Name</button>
{ status === 'loading' && <p>Loading...</p> }
{ status === 'idle' && data && (
<div>
<p>이름: { data.name }</p>
</div>
)}
{ status === 'failed' && <p>정보를 불러오지 못했습니다.</p> }
</div>
);
};
export default UserInfo;