Lv4
- Lv4의 과제에서 데이터베이스의 비동기 처리 로직을 추가합니다.
- Lv3의 코드에서, createAsyncThunk를 추가하여 json-server 상태 관리 로직을 다루도록 합니다.
Keyword
Thunk
1. redux -thunk 설치
yarn add redux-thunk
1. 조회
[App.tsx]
import { __getTodos } from './redux/modules/todoSlice';
import { useAppDispatch } from './redux/config/configStore';
function App() {
const dispatch = useAppDispatch();
//데이터 가져오기
useEffect(() => {
dispatch(__getTodos());
}, [dispatch]);
return (
1) useEffect로 mount 될때 조회되도록 설정
2) dispatch 사용해서 __getTodos (thunk 사용)
[todoSlice.tsx]
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import api from '../../axios/api';
export interface Todo {
id: string;
title: string;
content: string;
isDone: boolean;
}
interface TodoState {
todos: Todo[];
isLoading: boolean;
isError: boolean;
Error: string | null;
}
const initialState: TodoState = {
todos: [],
isLoading: false,
isError: false,
Error: null,
};
//조회
export const __getTodos = createAsyncThunk('getTodos', async (payload, thunkAPI) => {
try {
const response = await api.get('/todos');
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
console.log(error);
}
});
const todoSlice = createSlice({
name: 'todos',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(__getTodos.pending, (state, action) => {
state.isLoading = true;
state.isError = false;
})
.addCase(__getTodos.fulfilled, (state, action) => {
state.isLoading = false;
state.todos = action.payload;
})
.addCase(__getTodos.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.Error = action.error.message || 'error';
});
},
});
export default todoSlice.reducer;
1) 비동기 통신하면 시간의 차이가 발생할 수 있음=> isLoading, isError, Error 항목 추가
2) initialState의 타입 설정 : TodoState
* __getTodos : 비동기 thunk 액션
* createAsyncThunk : 비동기 작업을 처리하는 액션 생성 부분
* 매개변수 payload : thunk 액션에 전달되는 데이터
* 매개변수 thunkAPI : 여기서 비동기함수 수행함
3) extraReducers 사용해서 각각 pending, fulfilled, rejected 상황에 맞게 넣어줌
2. 추가 ★
[todoSlice.tsx]
//추가
export const __addTodos = createAsyncThunk<Todo[], {}>('addTodos', async (payload, thunkAPI) => {
try {
const response = await api.post(`/todos/`, payload);
thunkAPI.dispatch(__getTodos());
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
console.log('todoSlice [add] error', error);
throw error;
}
});
1) createAsyncThunk 다음에 2가지의 제네릭타입 작성
* 첫번째 제니릭 타입 : Todo[ ] : thunk 액션의 비동기 작업 성공하면 들어가는 결과
* 두번째 제니릭 타입 : { } : 추가할 newData 타입 (payload로 전달되는 타입 )
[InputForm.tsx]
function InputForm() {
const dispatch = useAppDispatch();
const formSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const newData = {
id: uuid(),
title,
content,
isDone: false,
};
dispatch(__addTodos(newData));
1) dispatch 타입 : store 설정하는 부분에서 설정해줌
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;
2) newData 타입 : todoSlice.tsx 파일에서 두번째 제니릭 타입 (객체])
즉, 추가할 newData 타입 (payload로 전달되는 타입 ) ==> 배열 안에 객체여서서 객로 들어가야함
3. 삭제
[todoSlice.tsx]
export interface Todo {
id: string;
title: string;
content: string;
isDone: boolean;
}
interface TodoState {
todos: Todo[];
isLoading: boolean;
isError: boolean;
Error: string | null;
}
const initialState: TodoState = {
todos: [],
isLoading: false,
isError: false,
Error: null,
};
export const __deleteTodos = createAsyncThunk<Todo[], string>('deleteTodos', async (payload, thunkAPI) => {
try {
const response = await api.delete(`/todos/${payload}`);
thunkAPI.dispatch(__getTodos());
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
console.log('todoSlice [delete] error', error);
throw error;
}
});
extraReducers: (builder) => {
builder .addCase(__deleteTodos.pending, (state, action) => {
state.isLoading = true;
state.isError = false;
})
1) 알아두기 : extraReducers에서 state값은 슬라이스리듀서를 생성할 떄 정의한 "initialState'에 정의한 타입
=> state 타입 = TodoState임
=> 나는 state의 타입은 TodoState의 todos를 사용하고싶음
==> useSelector함수를 사용할때 todos를 선택하면 됨
const todos = useSelector((state: RootState) => state.todos.todos);
[Content.tsx]
const removeHandler = async (e: React.MouseEvent<HTMLButtonElement>, id: string) => {
try {
dispatch(__deleteTodos(id));
} catch (error) {
console.log('삭제 오류', error);
}
};
1) 이벤트의 타입과 아이디의 타입 지정해주기
2) dispatch로 삭제할 아이디 전달
4. 변경
[todoSlice.tsx]
export const __changeTodos = createAsyncThunk<Todo, { id: string; isDone: boolean }>(
'changeTodos',
async (payload, thunkAPI) => {
try {
const response = await api.patch(`/todos/${payload.id}`, { isDone: !payload.isDone });
thunkAPI.dispatch(__getTodos());
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
console.log('todoSlice [delete] error', error);
throw error;
}
}
);
1) 전달 받은 아이디 & isDone 상태값 변
[Content.tsx]
const changeHandler = async (e: React.MouseEvent<HTMLButtonElement>, id: string, isDone: boolean) => {
try {
dispatch(__changeTodos({ id, isDone }));
} catch (error) {
console.log('상태 업데이트 오류', error);
}
};
1) 이벤트의 타입과 아이디 & isDone 상태 타입 지정해주기
2) dispatch로 삭제할 아이디 & isDone 전달
끝.