본 포스트 예제는 Redux Toolkit에서 제공하는 비동기 함수 createAsyncThunk를 이용해서 https://jsonplaceholder.typicode.com 사이트에서 데이터를 가져오는 예제로 typescript를 기반으로 작성되었습니다.
프로젝트 생성
vite를 이용해서 프로젝트를 생성합니다.
yarn create vite test_app --template react-ts
패키지 추가
yarn add @reduxjs/toolkit axios react-redux
App.js
import './App.css' import { Provider } from "react-redux"; import Content from "./Content"; import { store } from './store'; function App() { return ( <div className="App"> <Provider store={store}> <h1>Hello, React</h1> <Content></Content> </Provider> </div> ); } export default App
store.ts
import { configureStore } from "@reduxjs/toolkit"; import contentSlice from "./contentSlice"; export const store = configureStore({ reducer: { content: contentSlice }, }); // Infer the `RootState` and `AppDispatch` types from the store itself export type RootState = ReturnType<typeof store.getState>; // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} export type AppDispatch = typeof store.dispatch;
Content.tsx
import React, { useEffect } from "react"; import { useAppSelector, useAppDispatch } from "./hooks"; import { fetchContent } from "./contentSlice"; function Content() { const dispatch = useAppDispatch(); useEffect(() => { dispatch(fetchContent()); }, [dispatch]); const contents = useAppSelector((state) => state.content.contents); const isLoading = useAppSelector((state) => state.content.isLoading); const error = useAppSelector((state) => state.content.error); if (isLoading) { return "loading..."; } if (error) { return error; } return ( <div> {contents.map((content) => ( <div key={content.id}> <img src={`${content.thumbnailUrl}`} alt={`${content.title}`} className="w-full h-full rounded" /> </div> ))} </div> ); } export default Content;
hooks.ts
import { useDispatch, useSelector } from "react-redux"; import type { TypedUseSelectorHook } from "react-redux"; import type { RootState, AppDispatch } from "./store"; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
contentSlice.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; interface ContentState { contents: string[]; isLoading: boolean; error: null | string; } const initialState: ContentState = { contents: [], isLoading: false, error: null, }; export const fetchContent = createAsyncThunk( "content/fetchContent", async () => { const res = await axios("https://jsonplaceholder.typicode.com/photos"); const data = await res.data; return data; } ); export const contentSlice = createSlice({ name: "content", initialState, reducers: {}, extraReducers: (builder) => { builder.addCase(fetchContent.pending, (state) => { state.isLoading = true; }); builder.addCase(fetchContent.fulfilled, (state, action) => { state.isLoading = false; state.contents = action.payload; }); builder.addCase(fetchContent.rejected, (state, action) => { state.isLoading = false; state.error = action.error.message as string; }); }, }); export default contentSlice.reducer;