본문 바로가기
  • 삽질하는 자의 블로그
메인-프로젝트/React - Do-Health 프로젝트

10. *** [ 기능 ] FireBase DB 에서 데이터 가져오기, with. Thunk 액션생성자 ( 리덕스 툴킷에서 타입스크립트와 함께 Thunk 액션생성자를 사용하는 방법)

by 이게뭐당가 2023. 1. 4.

들어가기 전에 필수적인 요소

 

1. 리덕스 툴킷에서, 비동기 함수를 사용하기 위한  Thunk 액션 생성자의 생성

 (Slice 의 reducer 는 반드시 순수함수 이므로)

 

2. Thunk 액션 생성자 를 생성할때, 필요한 Type 지정과 useDispatch 를 사용하는 방법

 ( 리덕스 툴킷에서 타입스크립트와 함께 Thunk 액션생성자를 사용하는 방법)

 

3. Thunk 액션 생성자 를 사용할때,  미리 정의한 AppDispatch 를 이용하여, dispatch 를 진행하는 방법

 

** KeyPoint
    [1. 리덕스 툴킷에서, 비동기 함수를 사용하기 위한, Thunk Action 생성자의 생성 ( Slice 의 Reducer action 은 반드시 순수함수 이므로) ]
    [2. Thunk Action 생성자를 생성할때, "타입지정" 과 useDispatch 의 생성 방법] ***
    [3. Thunk Action 생성자를 사용할때, 미리 정의한 AppDispatch 를 사용하는 것]

 

 

 

0. 파이어베이스에서 온 Data 재가공

<helper / fetch-get-exercise.tsx >
    : 사용 할 코드는 아니다. 전반적인 기능과 가공후 데이터만 보았다.

        export const getExercise = async () => {
            const response = await fetch(
            "https://do-health-project-default-rtdb.firebaseio.com/exercise.json"
            );

            const responseData = await response.json();

            const refineData = [];                  // 데이터 재가공

            for (const key in responseData) {       // 데이터 재가공
                refineData.push({
                    id: responseData[key].id,
                    img: responseData[key].img,
                    name: responseData[key].name,
                    part: responseData[key].part,
                    des: responseData[key].des,
                    warn: responseData[key].warn,
                });
            }

            console.log(refineData);

            return responseData;
        };
        
  ** "일단, 값만 뽑아서, fetch 가 잘 진행되었는지, 가공 후 데이터가 생각한 모양이 맞는지 확인한다."
  ** "이 코드는 단순히, 확인용이다. 사용하진 않을것이다."


< 가공 후 데이터 형태 >
    id: "e1"
    img: "/2.parts/do-parts/chest/pexels-andrea-piacquadio-3837781.jpg"
    name: "벤치 프레스"
    part: "chest"
    des : (5) [{…}, {…}, {…}, {…}, {…}]
            0 : {text: ...}, 1: {text:...}
    warn: (3) [{…}, {…}, {…}]
            0 : {text: ...}, 1: {text:...}

 

 

 

1. 가공 데이터를 토대로, 데이터를 담을 "리덕스툴킷" 의 "Slice" 를 만든다.

<store / exercise-slice.tsx >

//	[가공 후데이터 형태]
//      id: "e1"
//      img: "/2.parts/do-parts/chest/pexels-andrea-piacquadio-3837781.jpg"
//      name: "벤치 프레스"
//      part: "chest"
//      des : (5) [{…}, {…}, {…}, {…}, {…}]
//              0 : {text: ...}, 1: {text:...}
//      warn: (3) [{…}, {…}, {…}]
//              0 : {text: ...}, 1: {text:...}

    import { createSlice, PayloadAction } from "@reduxjs/toolkit";

    export interface ExerciseType {
        id: string;
        img: string;
        name: string;
        part: string;
        des: { text: string }[];
        warn: { text: string }[];
    }

    const initialState: { exerciseData: ExerciseType[] } = {     // exerciseData 안에, Data 들을 전부 넣을 것이다.
        exerciseData: [],
    };

    const exerciseSlice = createSlice({
        name: "exercise",
        initialState,
        reducers: {
            updateAllExercise(state, action: PayloadAction<ExerciseType[]>) {	// payload 에는, playloadAction 을 이용한다.
                state.exerciseData = action.payload;                // payload 에서 오는 값을, exerciseData 로 교체할 것이다.
            },
        },
    });

    export const exerciseActions = exerciseSlice.actions;

    export default exerciseSlice.reducer;

 

 

   2. 타입스크립트에서 Thunk 를 이용해,  "비동기 함수(fetch)" 의 결과값을 Store 에 담아본다.   

** Key Point

1. Thunk action 생성자 를 사용하기 위한 useDispatch 특수한 타입 지정(AppDispatch)
2. Thunk action 생성자 사용시 특정한 dispatch 타입을 import 해서 사용
 

TypeScript Quick Start | Redux

- How to set up and use Redux Toolkit and React-Redux with TypeScript

redux.js.org

While it's possible to import the RootState and AppDispatch types into each component, it's better to create typed versions of the useDispatch and useSelector hooks for usage in your application. This is important for a couple reasons:

  • For useSelector, it saves you the need to type (state: RootState) every time
  • For useDispatch, the default Dispatch type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized AppDispatch type from the store that includes the thunk middleware types, and use that with useDispatch. Adding a pre-typed useDispatch hook keeps you from forgetting to import AppDispatch where it's needed.

** 기본적인 useDispatch 는 Thunk 에 대하여 알지 못한다.

** Thunk 를 정확하게 사용하기 위해서는,

      "Thunk 가 지정된", "특수하게 정의된 AppDispatch 타입" 을 이용하여, 사용해야한다.

 

전반적으로 전과 같지만, "타입을 지정해 주는 것만 다르다"

 

 

Thunk (action) 생성 [ useAppDispatch 타입 정의 ]

< store / exercise-action.tsx >

    import { Dispatch } from "@reduxjs/toolkit";
    import { exerciseActions } from "./exercise-slice";
    import { useDispatch } from "react-redux";              // useDispath 의 사전 생성
    import type { AppDispatch } from "../store/index";      //  action 생성자용 Dispatch


    export const sendRequest = () => {
        return async (dispatch: Dispatch) => {      // 1. 타입은 Dispatch 이다.

            const fetchData = async () => {         // 비동기 함수(fetch) 만들어서

                const response = await fetch(      
                    "https://do-health-project-default-rtdb.firebaseio.com/exercise.json"
                );
                const responseData = await response.json();

                const refineData = [];              // 파이어 베이스에서 데이터 것 refine

                for (const key in responseData) {
                    refineData.push({
                    id: responseData[key].id,
                    img: responseData[key].img,
                    name: responseData[key].name,
                    part: responseData[key].part,
                    des: responseData[key].des,
                    warn: responseData[key].warn,
                    });
                }

                return refineData;
            };

            const allExercise = await fetchData();

            dispatch(exerciseActions.updateAllExercise(allExercise));   // inputData를 집어넣는 action 을 한다.
        };
    };

    export const useAppDispatch: () => AppDispatch = useDispatch;       // 2. Thunk action 생성자 사용시에 필요한 Dispath 를 export

 

Thunk 사용 [ 정의한 useAppDispatch 사용]

: useDispatch 가 아닌,  "exercise-action.tsx" 에서 정의한 "useAppDispatch" 를 사용

    < App.tsx >

        import { sendRequest, useAppDispatch } from "./store/exercise-action";
        import { useEffect } from "react";
        import { useSelector } from "react-redux";
        import { RootState } from "./store";

        function App() {
            const exerciseState = useSelector(      // 확인용, State 뽑아보았다.
                (state: RootState) => state.exercise.exerciseData
            );

            const dispatch = useAppDispatch();      // useDispatch 가 아닌, 정의한 useAppDispatch 사용

            useEffect(() => {
                dispatch(sendRequest());            // useDispatch 가 아닌, 정의한 useAppDispatch 사용2
            }, [dispatch]);

            console.log(exerciseState);

            return (
                <div className="App">
                    <Switch>
                    <Route path="/" exact>
                        <MainPage />
                        ...



    ** console.log(exerciseState)       // 데이터 확인 완료
        (20)[{…}, {…}, ...,  {…}, {…}, {…}, {…}, {…}, {…}]

 

 

이후에는, fetch 한 데이터로 교체해주었다.

<components / 2.how-to-workout / 1.chest-part-description.tsx >
    import DesCard from "../UI/descriptionCard/DesCard";
    import styles from "./0.all-parts-description.module.css";
    import { useSelector } from "react-redux";
    import { RootState } from "../../store";

    const ChestPartDes = () => {
        const exerciseData = useSelector(
            (state: RootState) => state.exercise.exerciseData           // 데이터 뽑아서 사용
        );
        const filterdData = exerciseData.filter((item) => item.part === "chest");       // 필터

        return (
            <div className={styles.main_div}>
                {filterdData.map((item) => (
                    <DesCard
                        key={item.id}
                        name={item.name}
                        img={item.img}
                        des={item.des}
                        warn={item.warn}
                    ></DesCard>
                ))}
            </div>
        );
    };

    export default ChestPartDes;

 

댓글