react redux toolkit으로 백엔드 데이터 가져오기 with typescript

본 포스트 예제는 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;

Leave a Reply