Typescript
란Javascript
의 단점을 보완하기 위해 만들어진 정적 타입 언어이다.
React-query
라는 이름으로 운영하던 패키지가 다른 프레임워크들을 지원하면서 Tanstack-Query
로 이름이 변경되었다.Tanstack-Query
는 서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 쉽게 도와주는 라이브러리이다.onSuccess
, invalidatequeries
등을 통해 데이터를 받은 후 바로 적용을 시킬 수 있다.Typescript
로 이번 청첩장 템플릿 만들기를 진행하면서 오류로 많은 시간을 보내게 되었다.
무한스크롤을 구현하며 Tanstackquery
의 useInfinitequery
를 사용했는데 이때 겪었던 오류들을 중심으로 이야기하려고 한다.
useInfiniteQuery()
를 사용하여 데이터를 불러오는 로직을 작성하였다.☝️ 처음 작성한 로직
const {
data: reviewsData,
isLoading,
error,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery<Review[]>({
queryKey: ['reviews'],
queryFn: async ({ pageParam }) => await getReview({ pageParam: pageParam as number, row }),
getNextPageParam: (lastPage, allPages) => {
return lastPage.length === row ? allPages.length * row : undefined;
},
});
💥 발생한 오류
: initialPageParam
관련 오류가 발생하였고, initialPageParam
을 필수로 설정해야 한다는 메시지가 출력되었다.
tanstackquery
는 2023년 10월경 v5로 업데이트가 되었다.Tanstack-query
docs를 보고 처음 useinfinityquery
를 사용하게 되었는데 이 부분에서 버전이 v4가 체크가 되어있었고 v4로 작업을 한 결과 예상 하지 못한 여러가지 오류들이 계속 발생했다. 이 오류들을 해결하여 google에 검색했을 때에도 대부분 참고한 글들이 v4를 사용하고 있어 이번 오류를 해결하는데 상당히 많은 시간을 소요하였다. (당연한 이야기겠지만 개발에서 개발언어와 프레임워크, 라이브러리들은 변화가 생긴다. 개발자들은 이 변화들을 빠르게 익히고 적용해야 하며 적응해야한다.
어제까지 자주 쓰이던 것들이 오늘부터 새롭게 변화해야할 수도 있다...!
이 점을 머리로는 익히고 있었지만 이번에 몸소 느끼게 된 것 같다.)
✨ 오류를 해결한 방법
initialPageParam
에 관한 이 호출과 일치하는 오버로드가 없다는 문구가 나왔다. => 어떤 것이 추가되지 않았음을 의미해결로직
const {
data: reviewsData,
isLoading,
error,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery<Review[]>({
queryKey: ['reviews'],
queryFn: async ({ pageParam }) => await getReview({ pageParam: pageParam as number, row }),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
return lastPage.length === row ? allPages.length * row : undefined;
},
});
TanstackQuery Docs 에서도 알 수 있듯이 v4에서는 initialPageParam: 0,
이 존재하지 않았지만 v5에서는 필수로 사용해야하는 로직으로 추가되었다.
💥 발생한 오류
supabase
에 저장되어 있는 후기 정보들을 가져오는 fetch로직에서 props의 type과 반환받는 데이터의 타입을 지정해주었음에도 any[] | null 형식은 'Review[] 형식에 할당할 수 없다는 오류가 계속 발생하였다. 이 오류는 실제 supabase에서 반환받는 값과 내가 커스텀해준 Review[] type의 형태가 달라서 나타나는 오류이다.✨ 오류를 해결한 방법
: 이 오류를 해결하기 위해 return되는 데이터를 type 단언을 통하여 해결해주었다.
export const getReview = async ({ pageParam = 0, row }: ReviewProps): Promise<Review[]> => {
const { data, error } = await browserClient
.from('reviews')
.select('*')
.range(pageParam, pageParam + row - 1);
if (error) {
console.error(error);
}
return data as unknown as Review[];
};
: 타입 단언을 통해 컴파일러에게 특정 타입 정보의 사용을 강제할 수 있다.
타입스크립트 컴파일러는 타입 표기, 타입 추론 등을 이용해 type을 판단한다.
타입 단언을 하게 되면 타입 체크를 할 수 없게 된다.
따라서 타입스크립트 컴파일러에게
이 값은 무조건 이런 타입이야! 그러니까 에러를 그만띄워!
하는 것이다. 타입에 대한 확신이 있을 때 사용하는 것이 타입 단언 이기 때문에
따라서. 평소 사용할 때는 지양하는 것이 좋다.
- stringify JSON값을 parsing해서 사용하는 경우이다.
나는 supabase
에 저장되어있는 후기를 가져왔고 browserClient와 supabase에서 반환되는 데이터가 기본적으로 any
타입이거나 예상되는 타입을 보장하지 않기 때문이다.
type단언이 이렇게 보면 모든 타입오류들을 해결해주고 원할하게 작동을 시켜줄 것 같지만 여전히 type단언은 조심해서 써야한다.
type단언은 런타임 오류를 낼 수 있기 때문이다.
참고 : 인프런 타입스크립트는 왜 그럴까?
🤫 언제 사용되나?
타입스크립트
에서 unknown
타입은 매우 중요한 역할을 한다. unknown
타입은 어떤 값이든 올 수 있지만, 그 값에 대해 명시적으로 타입을 확인하고 처리하기 전까지는 그 값을 사용할 수 없도록 제한하는 안전한 타입이다. 타입 안전성을 강화할 수 있으며, 특정 타입으로 변환할 때 타입 단언을 사용해야 한다는 점에서 unknown
타입은 any
타입보다 더 안전한 방식으로 사용된다.
unknown
타입은 JavaScript
의 any
타입과 비슷한 성질을 가지고 있지만, any
는 타입 검사를 완전히 비활성화하는 반면, unknown
은 타입 체크를 요구하여 더 안전하게 사용할 수 있다. 예를 들어, unknown
타입의 변수는 사용하기 전에 타입을 검사하거나 단언해야한다.
사용예시
function processData(data: unknown) {
if (typeof data === 'string') {
console.log(data.toUpperCase()); // string으로 타입이 단언되어 있음
} else if (typeof data === 'number') {
console.log(data.toFixed(2)); // number로 타입이 단언되어 있음
} else {
console.error('알 수 없는 타입입니다.');
}
}
processData('Hello'); // 'HELLO'
processData(42); // '42.00'
processData({}); // 알 수 없는 타입
다운 캐스팅(Downcasting)은 자식 타입을 부모 타입으로 변환하는 것이다. unknown 타입을 다른 특정 타입으로 변환하기 위해서는 타입 단언(as)을 사용한다. 이때 타입 안전성을 확보하려면, 가능한 unknown 타입을 신중하게 처리하고, 적절한 타입 체크 후에 단언하는 것이 중요하다.
예시
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'name' in data) {
const person = data as { name: string }; // 다운 캐스팅 (타입 단언)
console.log(person.name); // 정상적으로 사용 가능
} else {
console.error('유효한 데이터가 아닙니다.');
}
}
processData({ name: 'Alice' }); // 'Alice'
processData({}); // 유효한 데이터가 아닙니다.
-----
// 호출시 오류가 발생하는 함수
function error(v:never){
throw new Error();
error(); // 아무것도 안 넣어서 문제
error(1);
error(null);
error(undefined);
// 완전성이 보장되지 않은 switch 문 (blue type빠져있음)
function error(v:never){
throw new Error();
}
type Color = "RED" | "GREEN" | "BLUE"
function getColorName(c:Color){
switch(c){
case "RED":
return "rgb(255,0,0)";
case "GREEN":
return "rgb(0,255,0)";
default:{
return error(c);
}
}
: TypeScript는 런타임 오버로딩을 지원하지 않는다. 즉, 함수의 구현이 실제로 하나만 존재해야 하며, 타입 시그니처만 여러 개 정의할 수 있다. TypeScript는 컴파일 타임에 각 호출이 어느 시그니처와 맞는지 검사하여 적합한 시그니처를 선택할 뿐, 함수를 여러 개로 구분해서 오버로딩을 구현하지는 않는다.
오버라이딩은 부모 클래스에 정의된 메서드를 자식 클래스에서 새로 구현하는 것이다.
내 코드에서는 클래스 대신 React 컴포넌트의 특성을 사용하여 오버라이딩과 유사한 역할을 하는 구조를 구현하고 있다. 후기 작성 페이지에서 리뷰페이지 안의 ReviewCard.tsx 컴포넌트에서 reviews데이터를 받아와 내부에서 어떻게 표시할 지 직접 정의하고 있다.
오버로딩은 함수나 메서드가 같은 이름을 가지지만 다른 매개변수를 사용하는 것이다.
오버로딩과 달리, 오버라이딩(Overriding)은 상속 관계에서 주로 발생하며, 부모 클래스의 메서드를 자식 클래스가 재정의하는 것을 의미한다. 오버로딩은 같은 이름의 함수가 여러 버전으로 존재하는 것이고, 오버라이딩은 상속된 함수의 내용을 자식 클래스에서 새롭게 정의하는 것이라는 점에서 차이가 있다.
TypeScript의 타입 단언, 오버로딩 및 오버라이딩의 개념을 깊이 이해하게 되었고, 라이브러리의 버전 업데이트에 따른 차이점을 빠르게 습득하는 것이 얼마나 중요한지 깨달았다. 타입 단언은 때때로 유용하지만, 안정성을 해칠 수 있기 때문에 신중하게 사용해야 하며, 오버로딩과 오버라이딩은 코드의 유연성을 높이고 가독성을 개선하는 데 중요한 역할을 한다. 이 경험을 바탕으로 앞으로의 프로젝트에서 TypeScript의 강점을 더욱 효과적으로 활용할 것 같다.