왜 queryClient 설정에 useState를 사용할까?
기존에 진행한 프로젝트들에서는 react-query의 쿼리와 쿼리 상태를 관리하는 메소드들을 포함한 객체인 queryClient를 설정해서 option 등을 정의하기 위해서 queryClient를 컴포넌트 외부에서 선언하고 사용했다. 이는 라이프 사이클에서 한 번만 초기화되어 사용됨으로써 참조 동일성을 유지하게 된다.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
useErrorBoundary: true,
},
},
});
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<QueryClientProvider client={queryClient}>
<app/>
</QueryClientProvider>
)
next.js에서 react-query를 사용하기 위한 환경을 봤을때 다음과 같은 useState를 활용한 queryClient 설정을 보았고, 의문점이 들어서 조금 공부하게 되어서 기록을 남긴다.
function MyApp({ Component, pageProps }) {
const [queryClient] = React.useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
useState요???
상태관리를 위해 useState를 사용했던 나로서는 왜 여기서 useState를 쓰지? 라는 의문이 들었다. useState는 [state,setter함수] 의 조합으로 구성되어있는데, state는 setter를 호출하는 경우에만 업데이트되는 것이 보장된다. 그렇다면, 우리는 setter 함수를 사용하지 않거나, 선언하지 않음으로써 참조 동일성을 유지할 수 있다.
공식문서의 예제
var fruitStateVariable = useState('banana'); // 두 개의 아이템이 있는 쌍을 반환
var fruit = fruitStateVariable[0]; // 첫 번째 아이템
var setFruit = fruitStateVariable[1]; // 두 번째 아이템
*배열의 인덱스를 활용해 [0]으로 state [1]로 setter함수에 접근할 수도 있지만, 배열의 구조분해를 통해
const [fruit, setFruit] = useState('banana') 와 같은 방식으로 접근할 수 있기 때문에 전자의 방식은 좋지않을수 있다고 공식문서는 말한다.
게으른 초기 상태(Lazy initial state)를 활용하여 initalState에 함수를 전달하면 queryClient는 초기 한번만 생성되고, 이후에는 참조 동일성을 유지하게 된다.
initialState인수는 초기 렌더링 중에 사용되는 상태입니다 . 후속 렌더링에서는 무시됩니다. 초기 상태가 비용이 많이 드는 계산의 결과인 경우 초기 렌더링에서만 실행되는 함수를 대신 제공할 수 있습니다.
그럼 useMemo는 안될까?
function MyApp({ Component, pageProps }) {
const [queryClient] = React.useMemo(() => new QueryClient(),[]);
return (
<QueryClientProvider client={queryClient}>
"만들기" 함수와 종속성 배열을 전달합니다. useMemo종속성 중 하나가 변경된 경우에만 메모된 값을 다시 계산합니다. 이 최적화는 모든 렌더링에서 비용이 많이 드는 계산을 피하는 데 도움이 됩니다.
useMemo 의미론적 보증이 아닌 성능 최적화 에 의존할 수 있습니다 . 미래에 React는 이전에 메모한 일부 값을 "잊고" 다음 렌더링에서 다시 계산하도록 선택할 수 있습니다(예: 오프스크린 구성 요소를 위한 메모리 확보). 없이도 계속 작동하도록 코드를 작성한 useMemo다음 추가하여 성능을 최적화하십시오
즉 useMemo는 참조동일성(의미론적 보증)보다는 성능개선을 위한 목적이 크다. 기존에 useMemo 없이도 동작하는 코드를 useMemo를 이용하여, 성능최적화를 하는 것이다. (자세한 내용은 이전의 포스트 https://jong6598.tistory.com/33 를 참조하자!) 그러므로 참조동일성을 위한 useState를 사용한다.
Refs도 있잖아?
function MyApp({ Component, pageProps }) {
const queryClient = React.useRef(null);
if (!queryClient.current) {
queryClient.current = new QueryClient()
}
return (
<QueryClientProvider client={queryClient.current}>
다음과 같이 ref를 사용해서도 같은 결과를 얻고, 참조 동일성을 확보할 수 있다. 하지만 길어지는 코드는 모두 비용이고, current는 null을 포함할 수 있다. 이왕이면 useState를 사용하자.
자칫하면 그냥 이렇게 설정해야되구나 하고 넘어갈 수 있는 코드 한 줄을 이유를 찾고 사용하니 즐거운 경험이었다. 코딩 너무 재밌다.