Closed hyeyoonS closed 2 months ago
Next.js는 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG) 지원을 통해 검색 엔진 최적화(SEO)에 유리한 환경과 초기 페이지 로딩 성능을 향상시켜준다. 이를 통해 우수한 사용자 경험을 제공할 수 있고, 앱 라우터 방식에서는 내장 fetch 함수를 페이지에서 비동기로 사용해 서버 사이드 렌더링을 구현하고 서버 액션을 가능하게 한다.
그렇다면 서버 상태 관리를 위한 React-query를 굳이 사용할 필요가 있을까? Next.js의 기본 제공 기능들이 매우 강력하기는 하지만, 모든 경우에 최적의 해결책을 제공하는 것은 아니다. 특히, 서버 상태 관리와 관련하여, React-query는 다음과 같은 몇 가지 이점을 제공한다.
이러한 이점을 고려할 때, React-query는 서버 상태를 관리하는 데 있어서 뛰어난 선택이 될 수 있다. 특히 대규모 프로젝트나 복잡한 데이터 흐름이 있는 애플리케이션에서 그 가치가 더욱 빛을 발한다. 결국, 사용하는 기술은 프로젝트의 요구사항과 팀의 선호도에 따라 달라질 수 있으며, Next.js와 React-query를 함께 사용함으로써 얻을 수 있는 시너지를 고려하는 것이 중요하다.
Q. ReactQuery를 왜 썼나요? 이점이 뭔가요? 서버에서 받아오는 데이터를 쉽게 관리하기 위해 사용했다. 리액트 쿼리를 이용하면 데이터의 fetching, caching, 요청 중복 제거, 서버데이터 동기화, 상태 등을 쉽게 파악하고 이용할 수 있었다. 리액트 쿼리의 이점으로는 효율적인 캐싱, 오래된 데이터 백그라운드 자동 업데이트, 페이징 처리와 로딩 지연 처리, 가비지컬렉션을 이용한 서버데이터 메모리관리, 중복요청 제거 가 있다.
Q. next.js를 채택했다면 사용하지 않을 수도 있지 않나요? Next.js 역시 SSR과 서버 데이터를 가져오는 기능을 내장하고 있지만, React Query를 이용하면 더 간단한 코드로 데이터를 관리할 수 있다. 데이터 fetching 뿐만 아니라 캐싱, 에러처리, 중복요청 제거, 무한스크롤 구현 등을 Next.js만 사용했을때보다 훨씬 짧은 코드로 별도의 설정없이 즉시 사용가능하다.
(next.js도 fetch함수로 캐싱 자체는 가능함 주의)
fetch('https://...', { cache: 'force-cache' })
next.js는 내장 fetch함수를 제공하고 fetch로 캐시를 관리할 수 있습니다. 하지만 next.js에서 제공하는 fetch 만으로는 아직 next의 cache를 관리하는 게 어렵다는 의견을 보았습니다.
공식적으로 Next14는 캐시된 값을 버리는(invalidate) 기술을 두 가지 지원해 주는데,
server cache invalidate
client cache invalidate
이 두 가지 방식을 조합해서 써야 하는데, client cache와 server cache를 생각하면서 잘 써야 하기 때문에 휴먼 에러가 다수 발생할 수 있습니다.
예시로,
사용자가 게시글을 수정하면 → 검색 페이지의 검색 결과에서도 해당 게시글 내용이 수정되어야 하는 경우에,
/search
에서 fetch('/getPost', { tags: [search] })
로 데이터를 받고 (서버)
/post/edit
경로에서 revalidateTag('search')
로 서버의 캐시를 invalidate (서버)
하지만 클라이언트 컴포넌트에서 router.refresh()
를 사용해도 수정 사항이 반영되지 않습니다. 서버의 캐시만을 지웠을 뿐이기 때문입니다.….. 서버에서 최신 데이터를 다시 가져온 후 router.refresh()로 화면을 새로 그리거나 or state를 사용하여 데이터를 mutate하여야 합니다.
//state를 사용하여 데이터를 mutate하는 예시코드
import React, { useState } from 'react';
function PostComponent() {
const [postData, setPostData] = useState(null);
// 게시글 데이터를 업데이트하는 함수
const updatePostData = (newData) => {
setPostData(newData);
};
return (
<div>
<p>{postData}</p>
</div>
);
}
따라서 next.js에서 제공하는 fetch함수를 사용할 경우 서버와 클라이언트의 캐시를 각각 고려해야 하고, 코드가 매우 복잡해지기에 Client Component에서 데이터 Fetching 을 하는 경우 RQ를 쓰는 게 더 편하다는 의견을 따랐습니다.
++ 또한 관련하여 학습을 할 때에 revalidate 함수를 사용한 경로에서 해당 경로의 client cache가 모두 날아가서, 화면 전체 렌더링
이 되는 이슈가 있었습니다. 임시적인 것으로 차기 버전에서는 수정 될 것이라고 합니다.
📎 질문
ReactQuery를 왜 썼나요? 이점이 뭔가요? next.js를 채택했다면 사용하지 않을 수도 있지 않나요?
✏ 구술 답변 키워드
✏ 서술 답변
reactQuery란
reactQuery 사용 이유
reactQeury의 이점
reactQuery를 사용하지 않았을 때에는?
추가 ) reactQuery의 사용방법([react-query 개념 및 정리](https://kyounghwan01.github.io/blog/React/react-query/basic/#usequeries))
* useQuery : get API * unique key, api호출함수를 인자로 가짐. ```tsx const Todos = () => { const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, { refetchOnWindowFocus: false, retry: 0, onSuccess: data => { // 성공시 호출 console.log(data); }, onError: e => { // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출.) console.log(e.message); } }); // isLoading, isError를 따로 받지 않고, status 하나만 받아도 처리 가능 // ex) if(status === "loading") if (isLoading) { return Loading...; } if (isError) { return Error: {error.message}; } return ({data.map(todo => (- {todo.title}
))}
); }; ``` * useQuery 동기적 실행 : enabled 옵션으로 ```tsx const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList); const { data: nextTodo, error, isFetching } = useQuery( "nextTodos", fetchNextTodoList, { enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다 } ); ``` * useQueries : 여러 비동기 호출을 하는 경우 ```tsx // 아래 예시는 롤 룬과, 스펠을 받아오는 예시. // queries 안에 배열로 넣어주면 된다. const result = useQueries([ { queryKey: ["getRune", riot.version], queryFn: () => api.getRunInfo(riot.version) }, { queryKey: ["getSpell", riot.version], queryFn: () => api.getSpellInfo(riot.version) } ]); useEffect(() => { console.log(result); // [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}] const loadingFinishAll = result.some(result => result.isLoading); console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료 }, [result]); ``` * QueryCache : 쿼리에 대한 성공, 실패 전처리 ```tsx const queryClient = new QueryClient({ queryCache: new QueryCache({ onError: (error, query) => { console.log(error, query); if (query.state.data !== undefined) { toast.error(`에러 : ${error.message}`); }, }, onSuccess: data => { console.log(data) } }) }); ``` * useMutation : post, update API ```tsx import { useState, useContext, useEffect } from "react"; import loginApi from "api"; import { useMutation } from "react-query"; const Index = () => { const [id, setId] = useState(""); const [password, setPassword] = useState(""); const loginMutation = useMutation(loginApi, { onMutate: variable => { console.log("onMutate", variable); // variable : {loginId: 'xxx', password; 'xxx'} }, onError: (error, variable, context) => { // error }, onSuccess: (data, variables, context) => { console.log("success", data, variables, context); }, onSettled: () => { console.log("end"); } }); const handleSubmit = () => { loginMutation.mutate({ loginId: id, password }); }; return (