September 7, 2024 • ☕️☕️☕️ 13 min read
useQuery
와 useMutation
useQuery
는 데이터를 읽고 관리하는 데 특화되어 있고, 자동화된 캐싱, 리페칭, 상태 관리 등을 제공한다.
useMutation
은 데이터를 변경하는 작업에 특화되어 있으며, 명령형 호출, optimistic updates, 오류 처리, 쿼리 무효화 등을 가능하게 한다.
useQuery
와 달리, useMutation
은 요청이 수동적으로 트리거된다. 즉, 사용자가 버튼을 클릭하거나 폼을 제출할 때 호출된다.
🤔 그러면 GET 요청을 할 때도 컴포넌트 업데이트 시 데이터를 바로 불러오는 게 아니라, 수동적으로 트리거 시 불러오고 싶을 때는 useQuery 대신 useMutation을 써도 되나?
useQuery
는 기본적으로 컴포넌트가 마운트될 때 자동으로 데이터를 불러온다. 하지만 데이터를 수동적으로 트리거하고 싶을 경우에도 useQuery
를 사용할 수 있다. 이를 위해 useQuery
의 enabled
옵션을 false
로 설정하고, refetch
함수를 사용하여 원하는 시점에 데이터를 불러올 수 있다.
왜 useMutation
을 사용하지 말아야 할까?
비록 useMutation
을 사용하여 GET 요청을 수행할 수 있지만, 이는 권장되지 않는다. 그 이유는 다음과 같다:
useMutation
은 데이터 변경 작업에 최적화되어 있으며, GET 요청은 데이터 조회에 해당한다. 의미적으로 맞지 않기 때문에 코드의 가독성과 유지보수성이 떨어질 수 있다.useMutation
은 useQuery
가 제공하는 캐싱, 자동 리페칭, 데이터 동기화 등의 기능을 제공하지 않는다. 따라서 GET 요청을 useMutation
으로 처리하면 이러한 이점을 놓치게 된다.useQuery
는 데이터 페칭에 최적화된 다양한 최적화 기법을 사용한다. useMutation
은 이러한 최적화가 적용되지 않는다.☝️ 추가 팁
useQuery
의refetch
기능 활용: 데이터가 변경될 때마다refetch
를 호출하여 최신 데이터를 가져올 수 있다.useQuery
와useMutation
의 조합: 예를 들어, 데이터를 업데이트한 후useMutation
의onSuccess
콜백에서 관련useQuery
를 무효화(invalidate)하여 최신 데이터를 가져오도록 할 수 있다.
useMutation
vs mutation.mutate()
useMutation
useMutation
은 데이터를 생성(Create), 수정(Update), 삭제(Delete)하는 비동기 작업을 수행하기 위해 사용되는 훅이다. 이 훅은 비동기 작업의 상태를 관리하고, 성공 또는 실패 시 특정 로직을 처리할 수 있도록 도와준다.useMutation
을 호출하면 반환되는 객체에는 mutate
와 mutateAsync
라는 두 가지 메서드가 포함되어 있다.mutate
메서드
mutate
는 가장 기본적인 방식으로 mutation을 트리거하는 메서드이다. mutate
를 사용하면 비동기 작업을 수행하고, 해당 작업이 완료되면 콜백 함수(예: onSuccess
, onError
, onSettled
)가 호출된다.const mutation = useMutation(addTodo, {
onSuccess: () => {
// 성공 시 처리
},
onError: () => {
// 에러 발생 시 처리
},
});
mutation.mutate(newTodo);
콜백 중심: mutate
는 Promise를 반환하지 않고, 성공 또는 실패 시 전달된 콜백을 통해 작업 결과를 처리한다.
비동기 함수의 내부에서 사용 시 불편: mutate
를 비동기 함수 안에서 사용하면 작업 완료 시점에 대한 처리가 어려울 수 있다.
mutateAsync
메서드
mutateAsync
는 mutate
와 동일한 비동기 작업을 수행하지만, Promise
를 반환한다는 점에서 차이가 있다. 이 Promise
는 mutation 작업의 성공 또는 실패 여부를 처리할 수 있게 해준다.const mutation = useMutation(addTodo);
const handleAddTodo = async () => {
try {
const response = await mutation.mutateAsync(newTodo);
// 성공 시 처리
} catch (error) {
// 에러 발생 시 처리
}
};
mutateAsync
는 비동기 함수 안에서 await
키워드를 사용하여 작업이 완료될 때까지 기다릴 수 있다.mutateAsync
를 사용하면 명령형 코드 스타일로 비동기 작업을 처리할 수 있다. 특히, 작업이 완료된 후에 특정한 추가 로직을 처리해야 하는 경우 유용하다.🤔 그럼 MutateAsync는 언제 쓰나요?
- Promise가 꼭 필요할 경우
- 여러
mutations
를 동시에 시작하며, 모든mutations
이 끝나기를 기다리는 작업- 콜백 지옥에 빠질 수 있는 의존적
mutations
이 있는 경우
Ref
invalidateQueries
react-query에서 invalidateQueries
를 호출해야 하는 경우는 언제일까?
invalidateQueries
를 사용할 수 있다. 예를 들어, 일정 시간이 지나면 데이터를 자동으로 갱신하고 싶을 때이다.invalidateQueries
를 사용해 해당 쿼리를 무효화할 수 있다.
invalidateQueries
vsremoveQueries
vsresetQueries
invalidateQueries
- 특정 쿼리를 “무효화”하여, 해당 쿼리가 다음에 사용될 때 서버로부터 데이터를 다시 가져오도록 한다. 기존의 캐시된 데이터는 그대로 남아 있으며, 무효화된 쿼리가 다시 호출될 때 캐시된 데이터가 아닌 새 데이터를 가져오게 된다.
removeQueries
- 특정 쿼리를 캐시에서 완전히 제거한다. 이 함수가 호출되면 해당 쿼리의 모든 캐시 데이터와 메타데이터(상태, 타이머 등)가 삭제된다. 이후 해당 쿼리가 다시 호출되면, 처음부터 데이터를 가져오게 된다.
resetQueries
- 특정 쿼리를 초기 상태로 리셋한다. 즉, 쿼리를 처음 호출했을 때와 같은 상태로 되돌린다. 이 과정에서 캐시된 데이터는 삭제되지만,
removeQueries
와 달리 쿼리 자체는 남아 있으며 다음에 사용할 때 초기 상태로 다시 시작하게 된다. 이때, 쿼리가enabled
상태라면 즉시 데이터를 다시 가져온다.
Ref https://velog.io/@eamon3481/React-Query-Query-invalidate
React Query의 Structural Sharing은 쿼리 데이터를 업데이트할 때 효율성을 높이기 위한 최적화 기술이다. 이 개념은 객체나 배열의 변경이 최소화될 수 있도록, 동일한 구조를 공유하면서 변경된 부분만 업데이트하는 방식으로 작동한다. 이로 인해 성능이 향상되고, React 컴포넌트의 불필요한 리렌더링을 줄일 수 있다.
Structural Sharing은 데이터의 특정 부분만 변경되었을 때, 변경되지 않은 부분은 기존의 구조를 유지하면서, 변경된 부분만 새로운 구조로 만들어주는 방식으로 작동한다. 이를 통해 동일한 구조를 최대한 공유하게 하여 불필요한 메모리 사용과 객체 생성, 그리고 성능 저하를 방지한다.
예시)
다음과 같이 데이터가 있을 때
[
{ "id": 1, "name": "Learn React", "status": "active" },
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]
첫 번째 todo의 status만 변경되었다면
[
- { "id": 1, "name": "Learn React", "status": "active" },
+ { "id": 1, "name": "Learn React", "status": "done" },
{ "id": 2, "name": "Learn React Query", "status": "todo" }
]
다음과 같이 구조적 공유를 활용할 수 있다.
// ✅ will only re-render if _something_ within todo with id:2 changes
// thanks to structural sharing
const { data } = useTodo(2)
- 구조적 공유는 json-serializable 데이터에만 동작한다.
- 아주 큰 데이터셋의 경우 구조적 공유가 병목이 될 수 있으므로
structuralSharing: false
옵션을 켜두는 것이 좋다.
Ref https://tkdodo.eu/blog/react-query-render-optimizations
.d.ts
파일을 정의하는 것의 차이.d.ts
파일의 주요 특징과 사용 용도:
.d.ts
파일은 주로 JavaScript로 작성된 외부 모듈의 타입을 정의하는 데 사용된다. 예를 들어, npm에서 다운로드한 JavaScript 라이브러리에 대한 타입 정보를 제공할 때 사용된다. 이 경우, 라이브러리는 실제 구현을 포함하지 않고 타입만 선언한다.@types/lodash
와 같은 패키지가 .d.ts
파일을 제공하여 Lodash 라이브러리의 타입 정보를 TypeScript 프로젝트에서 사용할 수 있게 한다..d.ts
파일은 실제 코드 구현이 없고 오직 타입 선언만 포함된다. 이 파일은 타입스크립트 컴파일러가 타입 정보를 이해하도록 도와주며, 컴파일된 JavaScript 코드에는 포함되지 않는다..d.ts
파일로 제공함으로써 점진적인 마이그레이션이 가능하다..ts
파일에서 타입 정의의 주요 특징과 사용 용도:
.ts
파일은 TypeScript 코드와 타입 정의를 모두 포함할 수 있다. 이는 함수의 구현과 타입 정보를 모두 포함할 수 있어, 코드와 타입 선언이 함께 유지된다.type.ts
파일에서 타입과 함수 구현을 모두 정의할 수 있다..ts
파일에는 실제 코드 구현이 포함될 수 있다. 따라서, 타입과 함께 실제 코드 로직을 작성할 수 있다.type.ts
파일로 관리하여, 해당 모듈 내에서만 사용되는 타입을 정의할 수 있다.flow
주어진 함수에 대해 this
바인딩을 추가한 결과를 리턴하여 다음 함수를 연속적으로 실행한다.
function square(n) {
return n * n;
}
var addSquare = _.flow([_.add, square]);
addSquare(1, 2);
// => 9
구현 코드가 이것밖에 안 된다!
function flow(...funcs: Function[]) {
const length = funcs.length;
let i = length;
while (i--) {
if (typeof funcs[i] !== 'function') {
throw new TypeError('Expected a function');
}
}
return function (this: any, ...args: any[]) {
let j = 0;
let result = length ? funcs[j].apply(this, args) : args[0];
while (++j < length) {
result = funcs[j].call(this, result);
}
return result;
};
}
export default flow;
그리고 UI는 항상 빠르고 반응적이다.
데이터는 데이터가 필요한 컴포넌트로 범위가 제한되었으며 모든 컴포넌트는 서로에게 독립적이다.
동일한 SWR 키를 사용하며 그 요청이 자동으로 중복 제거, 캐시, 공유되므로, 단 한 번의 요청만 API로 전송된다.
SWR은 캐시된 데이터를 빠르게 반환하면서, 백그라운드에서 데이터를 새로 가져와 최신 상태로 유지하는 전략을 기반으로 한다. Stale-While-Revalidate
는 SWR의 핵심 개념으로, 캐시된 데이터를 먼저 제공하고, 그 후에 새로운 데이터를 가져와 업데이트한다.
swr의 뮤테이션은 react-query의 mutation과 다르다. useSWR()
을 통해 받아온 데이터를 클라이언트 사이드에서 변형시켜 업데이트해 주는 개념이다.
cf) SWR은 데이터를 받아오는 것에 중점을 두고 있었기 때문에 원래 mutation hook이 존재하지 않았었다.
bound mutate
useSWR
hook과 함께 사용되는 함수로, 변경한 데이터를 갱신하고 필요에 따라 options
값을 전달하여 재검증(revalidate) 여부를 설정할 수 있다.global mutate
useSWR
hook과는 별개로 독립적으로 사용할 수 있으며, 특정 데이터를 갱신하고 싶을 때 직접 사용이 가능하다. useSWRConfig
hook을 사용하거나 전역으로 가져와서 사용한다.useSWR
hook에 영향을 미치지 않는다.Ref
useSWRSubscription
useSWRSubscription
은 SWR을 사용하여 실시간 데이터를 구독할 수 있는 훅이다.
useSWRSubscription<Data, Error>(key: Key, subscribe: (key: Key, options: { next: (error?: Error | null, data: Data) => void }) => () => void): { data?: Data, error?: Error }
이 훅은 실시간 데이터를 구독하고, 가장 최근에 수신된 데이터와 발생한 에러를 반환한다. 훅은 새 이벤트가 수신되면 반환된 데이터를 자동으로 업데이트한다.
useSWRSubscription
hook을 이용하여 real-time data에 지속적인 연결을 유지할 수 있다. ex) Firestore 및 WebSocket 데이터 구독
Ref https://swr.vercel.app/ko/docs/subscription
SWR은 먼저 캐시에서 데이터를 반환한 다음, 서버에 데이터를 가져오는 요청을 보내고, 마지막으로 최신 데이터를 제공하는 전략이다.
React Query는 React 어플리케이션에 서버 상태를 가져오고, 캐싱하고, 동기화하고, 업데이트하는 것을 쉽게 해 준다.
→ React Query는 보다 전반적인 서버 상태 관리에 중점을 두고 있고, SWR은 사용자 경험을 위해 빠르고 최신 상태의 데이터를 제공하는 데 중점을 둔다.
스크립트의 첫줄에 존재하는 !# 로 시작하는 문자 시퀀스로, 텍스트 기반 파일을 실행 파일처럼 실행했을 때 어떤 인터프리터를 토대로 이 스크립트를 실행할 것인지를 지시하는 ‘인터프리터 지시자 (interpreter directive)’ 역할을 한다.
즉 이 파일을 package.json의 bin 속성의 값으로 넣어준다면, 이 파일을 binary executable로 취급할 것이고, 해당 명령어를 실행했을 때 이 파일의 첫 줄을 보고 어떤 인터프리터로 실행할지 결정하게 된다.
이때 첫 줄에 hashbang 뒤에 지정하는 인터프리터는 실제 해당 인터프리터의 executable이 존재하는 위치를 지정하게 된다.
ex)
#!/bin/bash
(bash로 실행)#!/usr/bin/env node
(usr/bin/env에서 실행가능한 node로 실행, 이 파일의 스크립트는 js로 분석)Object.hasOwn()
- 객체에 자체 속성으로 지정된 속성이 있는지 여부 (Ref)<hgroup>
- h1~h6 태그 + 1개 이상의 p요소로 이루어져 있다. (Ref)Rust로 쓰인 JS 번들러로, Vite에 적용하기 위해서 개발중이다.
현재는 dev에서는 esbuild, prod에서는 rollup을 사용한다.
esbuild와 rollup의 미묘한 차이로 인해서 개발과 운영 결과물 사이의 차이가 존재한다.
그래서 네이티브 언어 수준의 성능을 가지면서 rollup의 인터페이스와 호환되는, 큰 스케일의 앱에 적합한 번들러를 만들고자 하는 것이 목표
<div style="align-content: center;">
<code>align-content</code> just works!
</div>
댓글은 흥분의 도가니…
Ref https://news.hada.io/topic?id=16586
9월까지 폭염 실화냐…
그래도 이른 저녁 바람 살랑살랑 야외공연까지 잘 마치고, 열심히 돌아댕긴 9월 첫주 주말 💨