프론트/Vue

[Nuxt3] useFetch 를 커스텀화 하기 (interceptor, type 적용)

ZestLee 2023. 1. 17. 22:13

보통 프론트 프로젝트에서 데이터 페칭을 할 때 axios 를 많이 사용하는데,
Nuxt3 에서는 useFetch 를 사용하는 것을 권장하고 있다.

https://nuxt.com/docs/getting-started/data-fetching#usefetch

 

Data Fetching · Get Started with Nuxt

Nuxt provides useFetch, useLazyFetch, useAsyncData and useLazyAsyncData to handle data fetching within your application. useFetch, useLazyFetch, useAsyncData and useLazyAsyncData only work during setup or Lifecycle Hooks Within your pages, components and p

nuxt.com

그렇다면 useFetch 에 interceptor 를 추가하고자 하면 어떻게 할 수 있을까?https://github.com/nuxt/framework/discussions/5370

 

How to add interceptor to useFetch() ? · Discussion #5370 · nuxt/framework

Hello I need a solution to globally add interceptor to useFetch() composable. any ideas ?

github.com

이미 많은 사람들이 해당 내용에 대해서 질문을 남겼다.

 

nuxt3 에서는 composables 하위의 파일들을 자동으로 가져오기 때문에,
composables/useCustomFetch.ts 를 만들어서 interceptor 를 만들어 보겠다.

import { UseFetchOptions } from "nuxt/dist/app/composables";

export default <T>(url: string, options?: UseFetchOptions<any, any, any>) => {
  return useFetch<T>(url, {
    ...options,
    onRequest({ request, options }) {
      // 뭔가의 헤더 작업
      // const headers = new Headers(options.headers);
      // headers.set("Authorization", "Bearer Token");
      // options.headers = headers;
    },
    onRequestError({ request, options, error }) {},
    onResponse({ request, response, options }) {},
    onResponseError({ request, response, options }) {},
  });
};

일단 타입만 빼고 보면 간단하다.

 

useCustomFetch 는 useFetch 함수를 리턴하는데,

인자로 url, options 를 받아서 useFetch 에 그대로 넘겨 준다.

(여기서 useFetch 는 nuxt3 에서 auto import 를 해 오기 때문에 import 를 따로 안 해 주는 것이다.)

 

타입은, options 에는 UseFetchOptions 타입을 넣어줬는데,

요건 useFetch 를 따라서 들어가면 타입 설정된 것을 볼 수 있다.

나머지 제네릭 타입들도 넣어줄 수 있겠지만, 일단 any 로 해 두겠다.

 

<T> 제네릭을 추가해 준 이유는

useFetch 는 AsyncData 를 리턴하는데,

제네릭 안에 리턴 타입을 넣어 주기 위해서이다.

이런 식으로 데이터가 리턴되게 해 주기 위함이다.

사용하는 쪽에서

type User = {
  name: string;
  age: Number;
};
const data = useCustomFetch<User>("/test");

이런 식으로 호출하면 AsyncData 타입의 제네릭에 User 타입이 들어오게 되는데

이렇게 하면 무엇이 좋으냐...

 

AsyncData 타입을 타고 들어가면 아래처럼 정의 되어있다

export type AsyncData<Data, Error> = _AsyncData<Data, Error> & Promise<_AsyncData<Data, Error>>;

그렇다면 _AsyncData 는 무엇일까?

export interface _AsyncData<DataT, ErrorT> {
    data: Ref<DataT | null>;
    pending: Ref<boolean>;
    refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>;
    execute: (opts?: AsyncDataExecuteOptions) => Promise<void>;
    error: Ref<ErrorT | null>;
}

data 가 Ref 데이터로 정의되어있다!!!!

Ref... 어디서 많이 보지 않았는가...!!!!!

그렇다. vue 의 ref 타입이다

https://vuejs.org/guide/typescript/composition-api.html#typing-ref

 

TypeScript with Composition API | Vue.js

 

vuejs.org

Promise 타입이기 때문에

await 호출 후 반환 값을 디스트럭처링 해서 data 를 가지고 온다면

_AsyncData 타입에서 data 의 타입을 Ref<T> 로 지정해 줬기 때문에,

data 타입이 Ref<T> 로 지정되는 것이다.

이것이 무엇에 좋느냐? typeError 를 체크할 수 있다는 것이다.

type User = {
  name: string;
  age: Number;
};

const user = ref("");
const { data } = await useCustomFetch<User>("/test");
user.value = data.value;

위처럼 기본값이 ""인 user 반응형 변수를 만들어 data 값을 넣으면 어떻게 될까. (두근두근)

거 개발자 양반 data의 타입은 User 인데 user 는 타입이 string 이지 않소 ("" 이 기본값이라 타입 추론으로 string 이 자동으로 들어감)

User 도 null 도 string 타입이 아니니 에러를 내뿜겠소

 

위처럼 typescript 의 장점인 typeError 를 내뿜는다.

const user: Ref<User | null> = ref(null);
const { data } = await useCustomFetch<User>("/test");
user.value = data.value;

해당 부분을 위처럼 Ref 타입으로 지정해 주면... 에러가 사라진다