내가 지금까지 해오던 react-query의 사용방식이다.
const { isLoading, error, data } = useQuery(...);
if(isLoading){
<div> loading... </div>
}
if(error){
<div> error </div>
}
return(
<div> {data} </div>
)
이렇게 내부에서 처리하거나 ErrorBoundary, Suspense
를 이용하여 선언적으로 로딩, 에러를 처리한 뒤, 데이터가 무조건 존재하는 상황에서만 쿼리된 데이터를 사용했다.
=> Type narrowing을 했다.
하지만 v5에서 이렇게 사용하면...
const data = DataType | undefined
이렇게 데이터가 없을수도 있다는 결과를 받게된다.
깃허브 디스커션 내용에서 완벽하게 설명해주지만, 요약해보자면 아래와 같다.
일단 useQuery
가 리턴하는 값 중 status
와 fetchStatus
에 대해 알 필요가 있다.
data
(쿼리 결과값) 에 대한 상태. pending, error, success
로 분류된다.queryFn
에 대한 정보를 나타냄. fetching, paused, idle
세가지 상태로 구분된다.왜 둘을 나눠서 다루는걸까?
=> fetchStatus는 네트워크 연결 상태와 관련된 데이터다. 즉, queryFn
의 요청이 진행중인지 아닌지에 대한 상태다.
=> status는 쿼리 결과값(data)에 대한 상태다.
아하! 그러니까, 함수의 요청 상태와 결과에 대한 상태구나.
그러면 isLoaidng
과 isPending
은 무슨 차이일까?
아하! 내가 원하는 건 결국 쿼리를 호출했는데, 결과값이 아직 없는 상태(promise의 pending 상태) 였다.
그건 v5에서 isPending이었다.
해결!
게임 모드를 나타내는 상태가 있다. 상태에 기반하여 함수를 실행하고싶다.
const [gameMode, setGameMode] = useState<'Start' | 'Finish'>('Start');
if(gameMode === 'Start'){
startgame()
} else {
finishgame()
}
이렇게 상태가 두가지라면 if-else
를 사용해도 가독성을 해치진 않는다. 하지만 상태가 늘어날수록 if, switch
문으로 도배가 될 것이다.
이를 해결하기위해서 매핑을 하면 좋아보인다.
const [gameMode, setGameMode] = useState<'Start' | 'Finish'>('Start');
const mappedGameFn = {
Start: () => {...},
Finish: () => {...},
};
mappedGameFn[gameMode]();
다만...TS컴파일러는 이 방법을 모른다. 현재 상태가 Start
인지 Finish
인지 모르고 유니온타입이기에 StartFn, FinishFn의 형식이 완전히 같아야한다. 이는 Ts컴파일러의 문제점이라고 볼 수 도있다.
https://github.com/microsoft/TypeScript/pull/47109
https://stackoverflow.com/questions/75723250/type-narrowing-of-a-discriminated-union-via-mapping-instead-of-switch
해결법의 핵심은 유니온타입을 제네릭타입으로 바꾸는 것이다. 즉, 함수와 인수가 호환 가능하다는 걸 TS 컴파일러에게 알리는 것이다.
=> 해결하기 위한 타입 작성이 생각보다 엄청 길어진다...
이럴거면 상태가 몇 개 안될땐 그냥 if-else나 switch쓰는게 나을 수도 있을듯?
일단 보류...!!!