[Typescript pair study] my AgoraStates Typescript 리팩토링 코드 공유 세션 ③

이민선(Jasmine)·2023년 5월 28일
0
post-thumbnail

5번째 세션!

이번주도 저번주에 이어서 my Agora States 과제의 React/Typescript 리팩토링을 계속 하고, 각자 과제를 하면서 어려웠던 부분과 극복했던 방법들 + 미해결 난제들을 공유하는 시간을 가졌다.
원래 이번주에 제네릭 기초 세션을 하려고 했었지만, 다음주에 코드스테이츠에서 타입스크립트를 배우기 때문에 유어클래스에서 제네릭까지 공부하고 서로 어려웠던 부분 위주로 공유하는 방식으로 진행하기로 계획을 수정하였다. 이번주는 계속 해서 my Agora State 리팩토링 하며 type 적용에 집중!

💡 나의 과제

이번 주 진행 상황 요약



이번주에 코드스테이츠에서 배웠던 json server를 사용하여 CRUD 구현을 연습하면 좋겠다는 생각이 들었다. json server라는 좋은 도구가 있었다니..! 서버에게 axios 요청을 던지며 CRUD를 연습하고 싶었던 나에게 아주 꿀 같은 라이브러리다 🍯

data.json

  • discussion 데이터를 담는 임시 database 파일 생성

useFetch.tsx

  • GET 요청 구현을 위한 custom component 생성

useDelete.tsx

  • DELETE 요청 구현을 위한 custom component 생성

pagination.tsx

  • custom hook 사용하여 데이터 fetch, delete 하는 부분 리팩토링

Type 발표

useFetch, useDelete에 alias type 지정하여 타입 추론 오류 해결

type Fetch = [
  data: Discussion[],
  fetchData: (author?: string) => Promise<void>
];

// data GET 요청 처리를 위한 custom component 생성
export const useFetch = (url: string): Fetch => {
  const [data, setData] = useState<Discussion[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async () => {
    try {
      setIsLoading(true);
      // axios 사용하여 GET 요청 보냄
      const response = await axios.get(url);
      console.log(response.data);
      setData(response.data);
    } catch (error) {
      setError(error as Error);
    } finally {
      setIsLoading(false);
    }
  };

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

  return [data, fetchData];
};
type Delete = (id: number) => Promise<void>;
// data DELETE 요청 처리를 위한 custom component 생성
export const useDelete = (url: string): Delete => {
  const [data, setData] = useState<Discussion[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null | unknown>(null);

  const deleteData = async (id: number) => {
    try {
      setIsLoading(true);
      await axios.delete(`${url}/${id}`);
      setData((prevData) => prevData.filter((item) => item.id !== id));
    } catch (error) {
      setError(error);
    } finally {
      setIsLoading(false);
    }
    window.location.reload();
  };

  return deleteData;
};

지난 시간까지는 redux action을 통해 local storage에 있는 data를 관리했지만, 이번 시간부터는 서버와의 상호작용을 연습하며 type 지정하는 연습을 하고자 했다. 서버와 상호작용 하며 CRUD를 구현하는 로직을 custom component로 분리하여 보관하는 것을 시도해보았다.

그런데 이번 시간에는 적절한 type을 지정하는 데 꽤 오랜 시간이 소요되었다.


원래 pagination을 구현할 때 redux store에서 받아온 state 배열에 slice 메서드를 사용했었는데, 이번에 json server로 CRUD를 연습하면서 state을 useFetch에서 return 해준 data로 모조리 바꾸려고 시도하자 type error가 나는 것이었다.

Property 'slice' does not exist on type 'Discussion[] | ((author?: string | undefined) => Promise<void>)

처음에 type error 났을 때 내 표정

웨땜시???
서버에서 Discussion[] type의 배열을 return 해왔는데?

type error를 자세히 보면 (author?: string | undefined) => Promise<void>) 이 부분은 useFetch가 return 하는 fetchData 함수이다.
구조분해할당을 해서 data와 fetchData 함수를 배열에 담아 받아왔는데, typescript 컴파일러가 순서까지 정확히 추론하지는 못할 수도 있기 때문이다.

따라서 2가지 해결 방법을 찾았다.

1. 위의 코드처럼 [data, fetchData]를 return 하되 useFetch의 type을 명시.

type Fetch = [
  data: Discussion[],
  fetchData: (author?: string) => Promise<void>
];

// data GET 요청 처리를 위한 custom component 생성
export const useFetch = (url: string): Fetch => {
.
.
(생략)

data와 fetchData를 return해주는 useData의 type을 명시하면 구조분해할당 하더라도 더 이상 타입 오류는 나지 않는다.

2. fetchData를 return 하지 않고 data만 return함.

fetchData 함수는 useFetch 외부에서 사용하지 않는 방법이다. axios를 사용하는 로직은 useFetch 내부에 숨겨두고, data만 반환하면 typescript가 타입을 제대로 추론할 수 있다.

export const useFetch = (url: string) => {
  const [data, setData] = useState<Discussion[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = async (author?: string) => {
    try {
      setIsLoading(true);
      const response = await axios.get(url);
      console.log(response.data);
      setData(response.data);
    } catch (error: any) {
      setError(error);
    } finally {
      setIsLoading(false);
    }
  };

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

  // fetchData를 더 이상 return 하지 않음.
  return data;
};

수민님과 스터디 할 때는 1번 방법만 공유를 했었지만, 다시 보니 2번 방법이 더 나을 듯 하다. fetchData 함수를 사용하려면 굳이 return하지 않아도 url을 인자로 넘겨 useFetch를 호출하면 되기 때문이다.

이번 세션 회고

이번 세션 과제는 원래 목표했던 만큼 구현을 하지 못해 아쉬웠다. 원래 막연하게 검색 기능을 구현해보고 싶다는 생각이 있었고 수민님도 좋은 아이디어라고 하셔서 해보고 싶었지만.. json server로는 일부 키워드로 검색 기능을 구현하는 데에는 한계가 있고, 내가 원하는 기능을 구현하려면 서버에서 로직을 건드려야 한다고 한다.

그래도 구조 분해 할당을 할 때 type 추론이 의도된 대로 되지 않을 수 있다는 점, custom component에 type을 명확히 줄 경우 이러한 type 오류를 해결할 수 있다는 점, useFetch를 사용하는 나만의 방법 정립은 이번 세션과 과제를 통해 확실히 얻을 수 있었다.

다음주에는 my Agora State 과제는 마무리를 하고, 다다음주에는 github README와 repo 정리를 하기로 했다. 다음주는 벌써 마지막 세션이다..! 수민님과 함께 타입스크립트를 공부하는 마지막 세션이 된다 😱 시간 왜이렇게 빠르지?!? 끝나갈 무렵이 되고 돌아보니 스터디 하면서 나도 수민님도 많이 성장한 스터디였다! 이런 얘기는 다음주에 자세히 써봐야겠다 ㅎㅎㅎ

🔈 수민님 다음주 마지막 세션까지 또 같이 열심히 달려봐욧!! 내일은 휴일이니까 같이 야무지게 쉬고 야무지게 공부해봐요 !! ㅋㅋㅋㅋ 😂💪🏻👍

profile
기록에 진심인 개발자 🌿

0개의 댓글