스터디 관리 프로젝트 - api axios instance

Seuling·2023년 2월 26일
1

api axios instance 생성

index.ts 파일 생성

axios 인스턴스 생성 -> 백엔드 접속정보로 create -> instance intercept (if(error){ retrun response.data } throw error) -> export

import axios from "axios";

const api = axios.create({
  withCredentials: true,
  headers: {
    "Content-Type": "application/json; charset=UTF-8",
    Accept: "application/json",
  },
  baseURL: "http://3.36.113.120:1337/api",
});

api.interceptors.response.use(
  function (response) {
    return response;
  },
  function (error) {
    return Promise.reject(error);
  }
);

export default api;

get해오는 fetch 함수 생성

fetch ? 를 하는것은 아니고, get요청으로 data를 fetch한다하여 fetch함수를 만들었다.

  • fetchMember라는 함수를 생성, get 요청, 파라미터는 원래는 주소만 있으면 되지만, strapi 특성상 관계형db에 해당하는 것은 ?populate=* 을 붙여줘야한다.
export const fetchMember = async () => {
  const res: AxiosResponse<Entity<MemberEntity>> = await api.get(
    "/members?populate=*"
  );
  return res.data;
};
  • return 으로 res.data를 해준 이유는 ?

만약 res만 할경우, 위와같이 response 전체에해당하는 정보가 나오는데,
res.data를 리턴할 경우 아래 fetchMemberData와 같은 형태로 data만 빼낼수 있다!

  • 그렇다면 왜 data만이 아닌 data, meta 이렇게 두개나 있지?

strapi 에서 data 부분에 data와 meta 정보를 같이 제공해주기 때문에!

api 호출

지금 api를 호출할 db table은 총 4개이다. (나중에 수정될 수 있지만!)
Fine, Member, Room, Todo
먼저 Member만 보았을때, 위에서 생성한 fetchMember() 를 호출한다면?

  const [member, setMember] = useState<MemberEntity[]>([]);

  const fetchData = async () => {
    const res = await fetchMember();
    setMember(
      res.data.map((d) => {
        return { id: d.id, ...d.attributes };
      })
    );
  };
  
  console.log(member);

  useEffect(() => {
    fetchData();
  }, []);

각각의 api를 호출해줄때마다 위와같이 state를 set해줘야하고, 각각의 data를 fetch해주는 함수를 만들어서, 비동기로 get요청에따른 response값을 setState를 해줘야한다.
여기서 map을 돌려서 내가 원하는 타입의 값으로 반환해주는 작없을 한다.

TypeScript - Generic

제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다. 한번의 선언으로 다양한 타입에 재사용이 가능하다는 장점이 있다.

즉, 미래에 사용할 타입을 미리 지정해준다는 느낌이다.
우린 Member에 관련한 db table에서 필요한 값은 memberId, email, name, profileImg, createAt, updatedAt, publichedAt이다.
그런데 strapi에서 data에 담겨있는값의 형태는 {id : ..., attributes: {...} } 이다.

  • MemberEnTity
interface MemberEntity {
  data: {
    id: number;
    attributes: {
      id?: number;
      memberId: string;
      email: string;
      name: string;
      profileImg: string;
      createdAt?: Date;
      updatedAt?: Date;
      publishedAt?: Date;
    };
  };
  meta: any;
}

한단계 더 나아가서 생각해보자면, Member 말고 만약 Room과 관련된 table의 api를 확인해보자면,

attributes 내의 값만 다르고 나머지는 동일하다,
그렇다면 전체 Entity에 해당하는 type을 먼저만들자!

interface Entity<T> {
  data: {
    id: number;
    attributes: T;
  }[];
  meta: any;
}

그리고, MemberEntity를 빼주자!

interface MemberEntity {
  id?: number;
  memberId: string;
  email: string;
  name: string;
  profileImg: string;
  createdAt?: Date;
  updatedAt?: Date;
  publishedAt?: Date;
}

이렇게 하면, 각각의 db table별로 entity를 만들면 전체 Entity 는 공유할 수 있다.

useAPI

그런데, 이걸 모든 api를 요청할 때마다 반복되는 코드이니, useAPI라는 hook으로 빼보자!

useAPI.tsx 작업 전!

import { useState, useEffect } from 'react';

interface Options {
    isFetch: boolean;
}

export function useAPI<T>(fetchFn: /* fetch fn에 대한 타입 */any ,options: Options) {
    const [data, setData] = useState<T[]>();

    // fetchFn을 호출해서 데이터 처리
    const fetchData = () => {

    }

    useEffect(() => {
        // isFetch가 true이면 fetchData
    }, [fetchFn])

    return {
        data,
    }
}
  • options를 준 이유는 ?
    지금이야 처음에 렌더링될 때 바로 fetch를 해오지만, 나중엔 버튼 클릭시, 뭐 이런 기능이 필요할 수 있기에 isFetch라는 boolean값으로 원할 때 사용할 수 있도록 하기 위해!

  • return을 generic 타입으로 해줌으로 재사용할 수 있음.

  • fetchFn 이 들어올때마다 useEffect으로 fetchData() 호출!

useAPI.tsx 작업 후!

import { AxiosResponse } from "axios";
import { useState, useEffect } from "react";
import api from "../services/api";

interface Options {
  isFetch: boolean;
}

export function useAPI<T>(fetchFn: () => Promise<Entity<T>>, options: Options) {
  const [data, setData] = useState<T[]>([]);

  // fetchFn을 호출해서 데이터 처리
  const fetchData = async () => {
    const res = await fetchFn();

    setData(
      res.data.map((d) => {
        return { id: d.id, ...d.attributes };
      })
    );
  };

  useEffect(() => {
    // isFetch가 true이면 fetchData
    if (options.isFetch) {
      fetchData();
    }
  }, [fetchFn]);

  return {
    data,
    fetchData,
  };
}

useAPI 호출

    const { data: member } = useAPI<MemberEntity>(fetchMember, { isFetch: true });
  

먼저 member와 관련된 fetchMember를 호출할 것이다.
그런데, 데이터타입은 MemberEntity 를 반환할 것이고, data라는 변수의 이름을 디스트럭쳐링을 이용해 member라는 새로운 변수의 이름으로 할당해주었다.


<Entity<MemberEntity>>의 형태로 console이 잘찍혔다!

구조분해할당 (디스트럭쳐링)

  • 객체 구조( structure )를 제거( de ) 한다는 의미가 있다.
  • 디스트럭처링은 객체의 구조를 분해 후 할당이나 확장과 같은 연산을 수행한다.
    자료형에 따라 다음과 같은 방식으로 나뉜다.
  1. 객체 디스트럭처링
  2. 배열 디스트럭처링
  • 기본 할당
    var o = {p: 42, q: true};
    var {p, q} = o;
    console.log(p); // 42
    console.log(q); // true
  • 선언 없는 할당
    구조 분해를 통해 변수의 선언과 분리하여 변수에 값을 할당할 수 있다.
    var a, b;
    ({a, b} = {a: 1, b: 2});
  • 새로운 변수 이름으로 할당하기
    객체로부터 속성을 해체하여 객체의 원래 속성명과는 다른 이름의 변수에 할당할 수 있습니다.
    var o = {p: 42, q: true};
    var {p: foo, q: bar} = o;
    console.log(foo); // 42
    console.log(bar); // true
profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글