TanStack Query๋ฅผ ์ฐ๋ค๊ฐ ๋ฌธ๋ ์ด๋ฐ ์๋ฌธ์ด ๋ค์์ต๋๋ค.
์ด ๊ธ์ ์ด๋ฐ ์๋ฌธ์ ํด์ํ๊ธฐ ์ํด ๊ณต๋ถํ๊ณ ํ ์คํธํ๋ ๊ณผ์ ์ ์ ๋ฆฌํ ๊ธฐ๋ก์ ๋๋ค.
Tanstack query์ ๊ตฌ์กฐ์ ๊ณต์
TanStack Query๋ ๋ด์ฉ์ด ๊ฐ์ผ๋ฉด ์ฐธ์กฐ ๋์ผ์ฑ์ ์ ์งํ๋๋ก ์ค๊ณ๋์ด ์์์ต๋๋ค. ์ฐธ์กฐ ๋์ผ์ฑ์ด๋ ๋ฌด์์ผ๊น์? ์๋ ์์๋ฅผ ํตํด ์ค๋ช ํ๊ฒ ์ต๋๋ค.
JS์์๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ์ง์ ๋ณผ ์ ์์ง๋ง, ์ค๋ช ํธ์๋ฅผ ์ํด ์์๋ก ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ํ๊ธฐํ์ต๋๋ค.
[
{ "id": 1, "title": "์ฑํ
1" }, //@memory_address: 1
{ "id": 2, "title": "์ฑํ
2" }, //@memory_address: 2
]๋ง์ฝ id๊ฐ 1์ธ ์ฑํ
์ title์ด ์ฑํ
1 -> ์ฑํ
123์ผ๋ก ๋ฐ๋๋ ๊ฒฝ์ฐ,
[
- { "id": 1, "title": "์ฑํ
1" }, //@memory_address: 1
+ { "id": 1, "title": "์ฑํ
123" }, //@memory_address: 3
{ "id": 2, "title": "์ฑํ
2" }, //@memory_address: 2
]์ ์์์์ id:2 ํญ๋ชฉ์ ๋ด์ฉ์ด ๋ฐ๋์ง ์์ ์ด์ ์ฐธ์กฐ๊ฐ์ด ์ ์ง๋ฉ๋๋ค.
๋ฐ๋ฉด id:1 ํญ๋ชฉ์ ๋ด์ฉ์ด ๋ฐ๋์ด ์ ์ฐธ์กฐ๋ก ๊ต์ฒด๋ฉ๋๋ค.
TanStack Query๋ ๊ฐ์ด ๋์ผํ ๋ ๊ธฐ์กด ๊ฐ์ฒด์ ์ฐธ์กฐ๋ฅผ ์ฌ์ฌ์ฉํ๋ ๊ตฌ์กฐ์ ๊ณต์ ๋ก ๋ด๋ถ ์บ์๊ฐ ์ต์ ํ๋์ด ์์ต๋๋ค.
์ค์ ๋๋๋ง ์ดํด๋ณด๊ธฐ
๊ทธ๋ ๋ค๋ฉด, ์ค์ ๋๋๋ง์ด ์ํ๋ ๋๋ ๋ฆฌ์คํธ์์ ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ๋ฆฌ๋๋๋ง์ด ๋ฐ์ํ ๊น์? ์ฑํ ๋ฐฉ ๋ชฉ๋ก์ ์กฐํํ๋ ๊ฐ๋จํ api๋ฅผ ์์๋ก ๋ค์ด ๋๋๋ง ๋์์ ๊ด์ฐฐํด๋ณด๊ฒ ์ต๋๋ค.
// ์ฌ์ฉ๋ API ์๋ต ๊ตฌ์กฐ
[
// ๋ฆฌํจ์น๊ฐ ๋ฐ์ํ ๋ ๋ง๋ค id:1์ ์ฑํ
๋ฐฉ์ ์ด๋ฆ์ด ๋ณ๊ฒฝ๋๋ ๋์์ผ๋ก ๊ตฌํ๋จ.
{ "id": 1, "title": "์ฑํ
1" },
{ "id": 2, "title": "์ฑํ
2" },
]
// ์ปดํฌ๋ํธ ๋๋๋ง ์ฝ๋
function ChatList() {
const { data: chatList } = useGetChatsSuspenseQuery(undefined, { refetchInterval: 500 });
return (
<div>
<h3>์ฑํ
๋ชฉ๋ก</h3>
<div className='flex flex-col gap-2 p-2 border max-h-70 overflow-y-auto'>
{chatList.map((chat) => (
<ChatItem key={chat.id} chat={chat} />
))}
</div>
</div>
);
}
type Props = {
chat: ChatResponseDto;
};
function ChatItem({ chat }: Props) {
return <div className='flex flex-col gap-2 rounded border p-2'>{chat.title}</div>;
}์ฑํ
๋ฐฉ ๋ชฉ๋ก์ ์กฐํํ๋ ๊ฐ๋จํ ์ปดํฌ๋ํธ์
๋๋ค. refetchInterval: 500์ ์ฌ์ฉํด 0.5์ด๋ง๋ค API๊ฐ ๋ฆฌํ์น๋๋๋ก ์ค์ ํ์ต๋๋ค. TanStack Query์ ๊ตฌ์กฐ์ ๊ณต์ ์ ๋ฐ๋ฅด๋ฉด, id:1 ์ฑํ
๋ฐฉ๋ง ๋ฆฌ๋ ๋๋ง๋๊ณ id:2 ์ฑํ
๋ฐฉ์ ๋ฆฌ๋ ๋๋ง๋์ง ์์์ผ ํฉ๋๋ค.

์์๊ณผ ๋ฌ๋ฆฌ React DevTools๋ก ์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง์ ์๊ฐ์ ์ผ๋ก ํ์ธํด๋ณด๋, ๋ฆฌํ์น๊ฐ ๋ฐ์ํ ๋๋ง๋ค ๋ชจ๋ ChatItem์ด ๋ฆฌ๋ ๋๋ง๋๋ ๊ฒ์ ํ์ธํ์ต๋๋ค.
์ ๊ทธ๋ด๊น์?
๋ฐ๋ก id:1์ ์ ์ธํ ํญ๋ชฉ๋ค์ ๊ฐ ์์ดํ
์ ์ฐธ์กฐ๋ ์ ์ง๋์ง๋ง, ์์์ chatList ๋ฐฐ์ด ์์ฒด๋ ์ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ธ๋ฐ์.
TanStack Query์ ๊ตฌ์กฐ์ ๋น๊ต๋ฅผ ์ํํ๋ replaceEqualDeepํจ์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์ด ํจ์๋ ๊ฐ์ฒด/๋ฐฐ์ด์ผ ๋ ํ์ ํญ๋ชฉ์ ์ฌ๊ท์ ์ผ๋ก ๋น๊ตํด์ ๊ฐ๋ฅํ ์๋ธํธ๋ฆฌ๋ฅผ ์ฌ์ฌ์ฉํ๊ณ , ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ์๋ก ๋ง๋ค์ด ์ ์ปจํ ์ด๋์ ์กฐํฉํฉ๋๋ค. ๊ทธ ๊ฒฐ๊ณผ ๋์ผํ ๋ด์ฉ์ ํ์ ๋ ธ๋๋ ์ด์ ์ฐธ์กฐ๋ฅผ ์ ์งํ๋๋ผ๋ ์ปจํ ์ด๋ ์์ฒด๋ ์๋ก ์์ฑ๋์ด ์ฃผ์๊ฐ ๋ฌ๋ผ์ง๋๋ค.
/**
* This function returns `a` if `b` is deeply equal.
* If not, it will replace any deeply equal children of `b` with those of `a`.
* This can be used for structural sharing between JSON values for example.
*/
export function replaceEqualDeep<T>(a: unknown, b: T): T
export function replaceEqualDeep(a: any, b: any): any {
if (a === b) {
return a
}
const array = isPlainArray(a) && isPlainArray(b)
if (array || (isPlainObject(a) && isPlainObject(b))) {
const aItems = array ? a : Object.keys(a)
const aSize = aItems.length
const bItems = array ? b : Object.keys(b)
const bSize = bItems.length
const copy: any = array ? [] : {} // copy๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ์ฐธ์กฐ ๋ฌ๋ผ์ง
const aItemsSet = new Set(aItems)
let equalItems = 0
for (let i = 0; i < bSize; i++) {
const key = array ? i : bItems[i]
if (
((!array && aItemsSet.has(key)) || array) &&
a[key] === undefined &&
b[key] === undefined
) {
copy[key] = undefined
equalItems++
} else {
copy[key] = replaceEqualDeep(a[key], b[key])
if (copy[key] === a[key] && a[key] !== undefined) {
equalItems++
}
}
}
return aSize === bSize && equalItems === aSize ? a : copy
}
return b
}์ด๋ก ์ธํด ChatList๊ฐ ๋ฆฌ๋ ๋๋๊ณ , React์์ ๋ถ๋ชจ๊ฐ ๋ฆฌ๋ ๋๋ ๊ฒฝ์ฐ React.memo ๋ฑ ๋ณ๋์ ์ต์ ํ๊ฐ ์ ์ฉ๋์ด ์์ง ์์ผ๋ฉด ์์ ์ปดํฌ๋ํธ๋ค์ด ๋ชจ๋ ๋ค์ ๋ ๋๋ฉ๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ๋ชจ๋ ChatItem ์ปดํฌ๋ํธ์ ๋ฆฌ๋๋๋ง์ด ๋ฐ์ํฉ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๊ตฌ์กฐ์ ๊ณต์ ๊ฐ ์์ฉ ์๋๊ฑฐ ์๋๊ฐ?
์ ๋์์ ๋ณด๋ฉด ๊ตฌ์กฐ์ ๊ณต์ ์ ํจ๊ณผ๊ฐ ์๋ ๊ฒ์ฒ๋ผ ๋๊ปด์ง ์ ์์ต๋๋ค. ํ์ง๋ง ๊ตฌ์กฐ์ ๊ณต์ ๋ ์ฌ์ ํ ์ค์ํฉ๋๋ค. ๊ตฌ์กฐ์ ๊ณต์ ๊ฐ ์๊ธฐ์ ๋ณ๊ฒฝ๋ ์๋ธํธ๋ฆฌ๋ง ์ ์ฐธ์กฐ๋ฅผ ๊ฐ์ง๋๋ก ํ๊ณ , React.memo๋ select ๊ฐ์ ์๋จ์ผ๋ก ๊ทธ ์ด์ ์ ์ค์ ๋ ๋๋ง ์ต์ ํ๋ก ์ฐ๊ฒฐํ ์ ์์ต๋๋ค. ๊ตฌ์กฐ์ ๊ณต์ ๊ฐ ์๋ค๋ฉด ๋ชจ๋ ํญ๋ชฉ์ด ๋ฌด์กฐ๊ฑด ์ ๊ฐ์ฒด๊ฐ ๋์ด ์ด๋ค ๋ฐฉ์ด๋ ๋ถ๊ฐ๋ฅํ์ ๊ฒ์ ๋๋ค.
React.Memo๋ฅผ ์ฌ์ฉํ ๋ฆฌ๋๋๋ง ์ต์ ํ
function ChatList() {
const { data: chatList } = useGetChatsSuspenseQuery(undefined, { refetchInterval: 500 });
return (
<div>
<h3>์ฑํ
๋ชฉ๋ก</h3>
<div className='flex flex-col gap-2 p-2 border max-h-70 overflow-y-auto'>
{chatList.map((chat) => (
<ChatMemo key={chat.id} chat={chat} />
))}
</div>
</div>
);
}
type Props = {
chat: ChatResponseDto;
};
const ChatMemo = React.memo(function Chat({ chat }: Props) {
return <div className='flex flex-col gap-2 rounded border p-2'>{chat.title}</div>;
});ChatItem์ React.memo๋ก ๊ฐ์ธ๋ฉด, ๋ถ๋ชจ(ChatList)๊ฐ ๋ฆฌ๋ ๋๋๋๋ผ๋ ์์์ ์ ๋ฌ๋ props์ ์์ ๋น๊ต ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ ๋๋ง ๋ ๋๋ฅผ ๊ฑด๋๋๋๋ค. ๊ตฌ์กฐ์ ๊ณต์ ๋๋ถ์ ๋ณ๊ฒฝ๋์ง ์์ ํญ๋ชฉ์ ์ฐธ์กฐ๋ ์ ์ง๋๋ฏ๋ก, ์ค์ ๋ก๋ id:1์ฒ๋ผ ์ฐธ์กฐ๊ฐ ๋ฐ๋ ํญ๋ชฉ๋ง ๋ฆฌ๋ ๋๋ฉ๋๋ค.

์ ์ด๋ฏธ์ง๋ ChatItem์ React.memo๋ก ๊ฐ์์ ๋์ ๋์์ ๋๋ค. ChatList๊ฐ ๋ฆฌ๋ ๋๋๋๋ผ๋ props๊ฐ ๋์ผํ ํญ๋ชฉ๋ค์ ๋ ๋๋ฅผ ๊ฑด๋๋ฐ๊ณ , id:1์ฒ๋ผ props๊ฐ ๋ฐ๋ ํญ๋ชฉ๋ง ๋ค์ ๋ ๋๋ง๋ฉ๋๋ค.
select๋ฅผ ์ฌ์ฉํ ์ ํ์ ๊ตฌ๋
function ChatList() {
const { data: chatList } = useGetChatsSuspenseQuery(undefined, {
select: (list) => list.map((c) => c.id),
});
return (
<div className='p-4'>
<h3>์ฑํ
๋ชฉ๋ก (select)</h3>
<div className='flex flex-col gap-2 p-2 border max-h-70 overflow-y-auto'>
{/* ๋ถ๋ชจ๋ ids๋ง ๊ณ์ฐํด์ ๋๊น */}
{chatList.map((id) => (
<ChatBySelect key={id} id={id} />
))}
</div>
</div>
);
}
type Props = {
chat: ChatResponseDto;
};
/**
* ChatBySelect: ์์ ๋ด๋ถ์์ useGetChatsSuspenseQuery์ select๋ก ์๊ธฐ ํญ๋ชฉ๋ง ๊ตฌ๋
* - select๋ ์๋ณธ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐํํด์ผ ์ฐธ์กฐ ๋ณด์กด์ ์ด์ ์ด ์๋ค.
*/
function ChatBySelect({ id, render }: { id: number; render: (key: string) => React.ReactNode }) {
const { data: chat } = useGetChatsSuspenseQuery(undefined, {
select: (list) => list.find((c) => c.id === id),
});
if (!chat) return null;
return <div className='flex flex-col gap-2 rounded border p-2'>{chat.title}</div>;
}select๋ฅผ ์ฌ์ฉํ๋ฉด ChatList๋ id ๋ชฉ๋ก๋ง ๊ตฌ๋ ํ๊ณ , ์์ ์ปดํฌ๋ํธ(ChatBySelect)๋ ์์ ์ด ์ฌ์ฉํ๋ ํญ๋ชฉ๋ง select๋ก ๊ตฌ๋ ํ๋๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค.

React.Memo๋ฅผ ์ฌ์ฉํ๋ ์์์ ๋ค๋ฅด๊ฒ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์๋ ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ์ง ์๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ์๋ํ๋ฉด chatList ๋ฐฐ์ด ์์ฒด์ ์ฐธ์กฐ๊ฐ ๋ ๋ ์ ํ์ ๋์ผํ๊ฒ ์ ์ง๋๊ธฐ ๋๋ฌธ์ด์ฃ . ๊ทธ ๊ฒฐ๊ณผ ์์ ์ปดํฌ๋ํธ๋ ๋ณ๊ฒฝ๋ ํญ๋ชฉ๋ง ๊ฐ์งํด ๋ค์ ๋ ๋๋งํ๋ฏ๋ก, ์ด ๊ฒฝ์ฐ์๋ id:1์ธ ์ฑํ
ํญ๋ชฉ๋ง ๋ฆฌ๋ ๋๋ง๋ฉ๋๋ค.
๊ฐ๊ฐ์ ์ฑ๋ฅ์ ๋น๊ตํด ๋ณผ๊น์?
100๊ฐ, 50000๊ฐ์ chat item์ ๋ถ๋ฌ์์ ย React Profiler๋ก ๋ ๋๋ง ์๋๋ฅผ ์ธก์ ํด ๋ณด์์ต๋๋ค. ๊ฒฐ๊ณผ ์์ ๋ฐ์ดํฐ๋ ์๋ ์ด๋ฏธ์ง์ ๊ฐ์ต๋๋ค.

์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์์ฑํ ์ฐจํธ๋ก ํ์์ ๋ถ์ํ๊ฒ ์ต๋๋ค.
๋จ์ผ item ๋๋๋ง ๋น๊ต
๋จ์ผ item ๊ด์ ์์๋ ์๋ฌด๊ฒ๋ ํ์ง ์์ < React.Memo ์ ์ฉ < select ์ฌ์ฉ ์์ผ๋ก ํ๊ท ๋ ๋ ์๊ฐ์ด ๊ธธ์ด์ง๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค๋ฆฌ์คํธ ํฌ๊ธฐ๊ฐ 100๊ฐ์์ 50k๋ก ๋์ด๋๋ฉด ์ฐจ์ด๊ฐ ๋ ํ์ฐํ ๋๋ฌ๋ฌ์ต๋๋ค.
์๋ํ๋ฉด memo๋ props ์ฐธ์กฐ๋ฅผ ์๊ฒ ๋น๊ตํ๋ ๋น์ฉ์ด ์ถ๊ฐ๋ก ๋ค๊ณ , select๋ find() ๊ฐ์ ํ์์ ์ํํ๋ฏ๋ก ์์ดํ
์์ ๋น๋กํ ์ถ๊ฐ ๋น์ฉ์ด ๋ฐ์ํฉ๋๋ค.
์ ์ฒด ๋ฆฌ์คํธ ๋๋๋ง ๋น๊ต
์ ์ฒด ๋ฆฌ์คํธ ๊ด์ ์์๋ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์์ต๋๋ค.
size: 100
select < memo < nothing ์์ผ๋ก ์ ์ฒด ์์ ์๊ฐ์ด ์์์ต๋๋ค.
์ด์ ๋ select์ ๊ฒฝ์ฐ ๋ถ๋ชจ๋ ๋ฆฌ๋๋๋์ง ์๊ณ ๋ณ๊ฒฝ๋ ์์ ์ปดํฌ๋ํธ์์ 100๊ฐ ์ ๋์ find() ๋น์ฉ์ด ๋ฐ์ํฉ๋๋ค. ์ด ๋น์ฉ์ด memo๋ก ์ธํ ๋น๊ต ๋น์ฉ๊ณผ ๋ถ๋ชจ ๋ฆฌ๋ ๋ ์ํฅ์ ํฉ์น ๋น์ฉ๋ณด๋ค ์๊ฒ ์์ฉํ๊ธฐ ๋๋ฌธ์ด์ฃ .
size: 50k
๋จ, ํญ๋ชฉ์ด ๋งค์ฐ ๋ง์(5๋ง๊ฐ ์ ๋)์ธ ๊ฒฝ์ฐ select๊ฐ ์๋ฑํ ๋๋ ค์ง ๊ฒ์ ํ์ธํ ์ ์์๋๋ฐ์. ์ด๋ find() ๋น์ฉ์ด ๋ฐ์ดํฐ ์์ ๋ฐ๋ผ ๊ธ๊ฒฉํ ์ปค์ก๊ธฐ ๋๋ฌธ์ ๋๋ค. ํ์ง๋ง ๋ง์ดํธ ์์ ์ ์ ์ธํ๋ฉด ์ํฉ์ด ๋ฌ๋ผ์ก์ต๋๋ค. ์ด๊ธฐ ๋ง์ดํธ ์์๋ item์ ๊ฐ์์ ์ ๊ณฑ๋งํผ ์ฆ O(N^2)์ ์๊ฐ๋ณต์ก๋๊ฐ ์์๋์ด ๋น์ฉ์ด ํฌ๊ฒ ๋ค์ด๊ฐ์ง๋ง, ๋ฆฌ๋๋๋ง ๋จ๊ณ์์๋ ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ๋ฐ์๋๋ฏ๋ก ์๋์ ์ผ๋ก ์ ๋ ดํด์ง๋๋ค.
๊ทธ๋ผ ์ธ์ memo๋ฅผ ์ฐ๊ณ , ์ธ์ select๋ฅผ ์ธ๊น?
React.memo๋ง์ผ๋ก๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ๋ฆฌ๋ ๋๋ง์ ๋ง์ ์ ์์ต๋๋ค. ๋ถ๋ชจ๊ฐ ๋ฆฌ๋ ๋๋๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ์์๋ค๋ ๋ค์ ํ๊ฐ๋๋ฉฐ, ์ด๋ ์์์ด React.memo๋ก ๊ฐ์ธ์ ธ ์๊ณ ์ ๋ฌ๋ props์ ์ฐธ์กฐ๊ฐ ๋์ผํ ๋์๋ง ๋ ๋๋ฅผ ๊ฑด๋๋ธ ์ ์์ต๋๋ค. ๋ถ๋ชจ๊ฐ ๋ฆฌ๋๋๋ง๋ ๋ ์์์ memo๋ ์๊ฐ๋ณด๋ค ๊นจ์ง๊ธฐ ์ฝ์ต๋๋ค. props ์คํ๋๋, JSX children ์ ๋ฌ, ์ฝ๋ฐฑ ํจ์ ๋ฑ์ ์์ ํํ์ง ์์ ๊ฒฝ์ฐ์ด์ฃ . ๋ฐ๋ผ์ props์ ์ฐธ์กฐ ์์ ํ๋ฅผ ์ ๊ฒฝ ์จ์ผ ํฉ๋๋ค.
๋ฐ๋ฉด select๋ฅผ ์ฌ์ฉํ๋ฉด ๊ตฌ๋ ๋ฒ์๋ฅผ item ๋จ์๋ก ์ขํ ๋ถ๋ชจ๊ฐ ์์ ๋ฆฌ๋๋๋์ง ์๋๋ก ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ ๋ฆฌ๋ ๋๋ก ์ธํ ํ์ ํธ๋ฆฌ์ ์ฌ์ด๋ ์ดํํธ๋ฅผ ์์ฒ์ ์ผ๋ก ์ค์ผ ์ ์์ต๋๋ค. ๋ค๋ง, ์์ ์ธ๊ธํ๋ฏ์ด select๋ ํญ๋ชฉ์ ์ฐพ๋ ๋ฑ์ ๋น์ฉ์ด ์ถ๊ฐ๋ ์ ์์ผ๋ฏ๋ก ํธ๋ ์ด๋์คํ๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
- React.memo
- ์ฅ์ : ์์ ๋น๊ต ๋น์ฉ์ด ์๊ณ ๊ตฌํ์ด ๊ฐ๋จํฉ๋๋ค.
- ๋จ์ : ๋ถ๋ชจ๊ฐ ๋๊ธฐ๋ props๋ฅผ ์์ ํํด์ผ ํด์ ์ ์ง๋ณด์ ๋ณต์ก๋๊ฐ ์ฌ๋ผ๊ฐ๋๋ค.
- select
- ์ฅ์ : ๊ตฌ๋ ๋ฒ์๋ฅผ ์ถ์ํด ๋ถ๋ชจ ๋ ๋ฒจ์ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ฅผ ํผํฉ๋๋ค.
- ๋จ์ : ๊ฒ์/๋ณํ ๋น์ฉ(์: find, map ์์ฑ ๋ฑ)์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
๋ง์ฝ ์๋ฒ ์๋ต์ ์ ๊ทํํด์ ์ ์ฅํ๋ฉด, select์ ๊ฒ์ ๋น์ฉ์ O(1)์ผ๋ก ํฌ๊ฒ ์ค์ผ ์ ์์ด ๋ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ์ฃ . ์ด๋ฐ ์ด์ ๋ก api ์๋ต์ด obejct์ธ ๊ฒฝ์ฐ select๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ด ๋ ์ ์ฉํฉ๋๋ค.
object์ ๊ฒฝ์ฐ
/**
* ์๋ฒ ์๋ต ๊ฐ์
*/
type ServerResponse = {
id: number;
title: string;
user: {
id: number;
name: string;
};
};
function ChatMetaInfoWidget({ id }: { id: number }) {
return (
<section className='chat-meta-widget'>
<ChatTitle id={id} />
<ChatParticipants id={id} />
</section>
);
}
/**
* ์์ 1: ์ ๋ชฉ๋ง ๊ตฌ๋
* - select๋ก title๋ง ๊ณจ๋ผ์ ๊ตฌ๋
-> title์ด ๋ฐ๋ ๋๋ง ๋ฆฌ๋ ๋
*/
function ChatTitle({ id }: { id: number }) {
const { data: title } = useGetChatMetaByIdSuspenseQuery(
{ id },
{
select: (chat) => chat.title,
},
);
return <h3>{title}</h3>;
}
/**
* ์์ 2: ์ฐธ์ฌ์ ์ด๋ฆ
* - select๋ก user.name๋ง ๊ณจ๋ผ์ ๊ตฌ๋
-> user.name์ด ๋ฐ๋ ๋๋ง ๋ฆฌ๋ ๋
*/
function ChatParticipants({ id }: { id: number }) {
const { data: user } = useGetChatMetaByIdSuspenseQuery(
{ id },
{
select: (chat) => chat.user,
},
);
return <div>{user.name}</div>;
}์ ์์์ฒ๋ผ ๋ถ๋ชจ๊ฐ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์์ ์ง์ props๋ก ๋ด๋ ค์ฃผ๋ ๊ตฌ์กฐ์๋ค๋ฉด, ์ ๋ชฉ ํ๋๋ง ๋ฐ๋์ด๋ ๋ถ๋ชจ๊ฐ ๋ฆฌ๋ ๋๋๋ฉฐ ๋ชจ๋ ํ์ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ํ๊ฐ๋ฉ๋๋ค. React.memo๋ก ์์์ ๊ฐ์ธ๋ฉด ์ผ๋ถ ๋ฐฉ์ด๋ ๋์ง๋ง, ๊ทธ๋ด ๊ฒฝ์ฐ ๋ชจ๋ ํ์ ์ปดํฌ๋ํธ์ ๋ํด ๋ฉ๋ชจ๋ฅผ ์ ์ฉํ๊ฑฐ๋ ์ ๋ฌ๋๋ props๋ฅผ ๊ผผ๊ผผํ ์์ ํํด์ค์ผ ํ๋ฏ๋ก ์ฝ๋ ๋ณต์ก๋๊ฐ ํฌ๊ฒ ์ฌ๋ผ๊ฐ๋๋ค.
๋ฐ๋ฉด select๋ฅผ ์ฌ์ฉํด ์์์ด ํ์ํ ํ๋๋ง ๊ตฌ๋ ํ๊ฒ ๊ตฌ์ฑํ๋ฉด, ์ ๋ชฉ ๋ณ๊ฒฝ์ ChatTitle๋ง, ์ด๋ฆ ๋ณ๊ฒฝ์ ChatParticipants๋ง ๋ฆฌ๋ ๋๋๋๋ก ๋ฒ์๋ฅผ ์ถ์ํ ์ ์์ต๋๋ค.
React.memo๋ ์์ ๋น๊ต ๋น์ฉ + ์ ์ง๋ณด์ ๋น์ฉ์ ์น๋ฅด๋ฉฐ ์ป๋ ์ต์ ํ์ ๋๋ค. ๋ฐ๋ฉด select๋ ๊ตฌ๋ ๋ฒ์๋ฅผ ์ถ์ํด ๋ถ๋ชจ ๋ ๋ฒจ์ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ฅผ ํผํ๋ ์ ๋ต์ ๋๋ค.
๋๋๋ง ์ต์ ํ๋ ๊ผญ ํด์ผํ ๊น?
๊ฒฐ๋ก ๋ถํฐ ๋งํ๋ฉด, ๋ฆฌ๋๋๋ง ์์ฒด๋ ๋์ ๊ฒ์ด ์๋๋ค๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋๋ค๋ ๊ฑด ๋จ์ง ์ปดํฌ๋ํธ ํจ์๊ฐ ๋ค์ ์คํ๋๋ ๊ฒ์ผ ๋ฟ์ด๊ณ , ์ค์ DOM ๋ณ๊ฒฝ์ ๊ฐ์๋์ ์ฌ์ฉํด ์ค์ ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ๋ฐ์๋ฉ๋๋ค. ๋ฌธ์ ๋ ๋ฆฌ๋๋๋ง ์์ฒด๊ฐ ์๋๋ผ ํ ๋ฒ์ ๋ ๋๊ฐ ๋๋ ค์ ํ๋ ์์ ์ง์ฐ์ํค๋ ๊ฒฝ์ฐ์ ๋๋ค. ์ฆ, โ์์ฃผ ๋ ๋๋๋๋ผ๋ ๊ด์ฐฎ๊ฒโ ์ค๊ณํ๋ ๊ฒ์ด ๋ ์ฌ๋ฐ๋ฅธ ๋ฐฉํฅ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
๋ํ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ฃ๋ถ๋ฅธ ์ต์ ํ๋ ๋ ์ด ๋ฉ๋๋ค. LABjs์ ์ฌ๋ก์ฒ๋ผ ์ต์ ํ๊ฐ ์คํ๋ ค ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ผ์ผํค๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์ธก์ ์์ด ์ต์ ํํ๋ฉด ์ฑ๋ฅ์ด ๊ฐ์ ๋๋์ง์กฐ์ฐจ ์๊ธฐ ์ด๋ ต๊ณ , ์คํ๋ ค ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค.
๊ทธ๋ผ ์ธ์ ์ต์ ํ๋ฅผ ํ๋ฉด ์ข์๊น?
Performance๋ก ๋๋ฆฐ ๋๋๋ง ํ์ธํ๊ธฐ
๋ธ๋ผ์ฐ์ Performance ํญ์ผ๋ก ๋ ๋๋ง ์๊ฐ์ ์ธก์ ํ์ธ์. 60fps ๊ธฐ์ค ํ ํ๋ ์์ ์ฝ 16.7ms์ ๋๋ค. ๋ง์ฝ ํ ํ๋ ์์ด 16.7ms๋ฅผ ๊พธ์คํ ๋๋๋ค๋ฉด โ์ฌ์ฉ์๊ฐ ์ฒด๊ฐํ๋ ๋๋ฆฐ ๋ ๋๋งโ์ ๋๋ค.
์ด๋๋ ์ปดํฌ๋ํธ ํจ์๊ฐ ๋๋ ค์ ๋ฐ์ํ ๋ฌธ์ ์ธ์ง, ์๋๋ฉด ๋ ์ด์์/์คํ์ผ/ํ์ธํธ ๋ฑ ๋ธ๋ผ์ฐ์ ๋ ๋ ๋จ๊ณ์ ๋ณ๋ชฉ์ธ์ง๋ฅผ ๋จผ์ ๊ตฌ๋ถํด์ผ ํฉ๋๋ค.
๊ตฌ์กฐ์ ๊ฐ์ ์ ๋จผ์ ์๋ํ๊ธฐ
Performance์์ ๋ณ๋ชฉ์ด ์์ฃผ ๋ฐ์ํ๋ ์ง์ ์ด ๋์์ด๋ memo๋ select ๊ฐ์ ๊ธฐ๋ฒ์ ๋ฐ๋ก ๋์ ํ์ง ๋ง์ธ์. ๋จ์ํ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊พธ๋ ๊ฒ ๋ง์ผ๋ก๋ ๊ทผ๋ณธ ์์ธ์ด ํด๊ฒฐ๋ ์ ์์ต๋๋ค.
- ์ปดํฌ๋ํธ ๋ถ๋ฆฌ: ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ด ์๋ ๋ถ๋ถ์ ๋ณ๋์ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํด์ ๋ฆฌ๋๋ ์ต์ ํ
- ์ฟผ๋ฆฌ ํค/์บ์ ์ฌ์ค๊ณ: ๋ถํ์ํ invalidate ๋ฐฉ์ง
์ด๋ฐ ๊ตฌ์กฐ์ ๊ฐ์ ์ผ๋ก ๋ฌธ์ ๊ฐ ํ๋ฆฌ๋ฉด ์ฝ๋๋ ๊น๋ํด์ง๊ณ ์ ์ง๋ณด์ ๋น์ฉ๋ ๋๋ฆฌ์ง ์์ผ๋ฉด์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
React Profiler๋ก React ์ปดํฌ๋ํธ ๋๋๋ง ์๊ฐ ํ์ธํ๊ธฐ
๊ตฌ์กฐ์ ๊ฐ์ ํ์๋ ๋ฆฌ๋๋๋ง์ด ๋ณ๋ชฉ์ด๋ฉด React Profiler๋ฅผ ์ผ์ ์ด๋ค ์ปดํฌ๋ํธ๊ฐ render์ ์๊ฐ์ ๋ง์ด ์ฐ๋์ง ํ์ธํ์ธ์. React Profiler๋ฅผ ํตํด ์ปดํฌ๋ํธ์ ๋ฆฌ๋๋๋ง ํ์์ ์๊ฐ์ ํ์ธํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์ ์ค์ํ ์ ์ ๋ธ๋ผ์ฐ์ ์ Performance๋ก ํ์ธํ๋ ๊ฒ์ ์ค์ DOM์ ๋ฐ์๋๋ ๋น์ฉ์ด๊ณ , React Profiler๋ฅผ ํตํด ํ์ธํ๋ ๊ฒ์ React ๋ด๋ถ์ render/commit ๋จ๊ณ์ ๋๋ค. ์ฆ "์ปดํฌ๋ํธ ํจ์๊ฐ ์คํ๋๋ ๊ฒ"์ ๋ํ ์ฑ๋ฅ ์ธก์ ์ด๊ณ DOM ๋ฐ์๊ณผ ๋ค๋ฅธ ์๋ฏธ์ ๋๋ค.
๋ง๋ฌด๋ฆฌ
TanStack Query๋ฅผ ์ฐ๋ค๊ฐ ๋ ์๋ฌธ์์ ์์ํด ๊ณต๋ถ๋ฅผ ์งํํ๋ค ๋ณด๋, ๊ตฌ์กฐ์ ๊ณต์ ๋ถํฐ React ๋ฆฌ๋ ๋๋ง ์ต์ ํ ๋ฐฉ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ธ์ ์ต์ ํ๋ฅผ ํด์ผ ํ๋์ง๊น์ง ์ ๋ฆฌํ ์ ์์์ต๋๋ค. ํ์ตํ๋ค๋ณด๋ ๊ฐ์ธ์ ์ผ๋ก ํ ๊ฐ์ง ํ๋จ์ด ๋ค์๋๋ฐ์. ๋ฐ๋ก โ๋ฆฌ๋ ๋๋ง ํ์ ์์ฒด๋ฅผ ์ค์ด๋ ๊ฒโ์ ์ฐ์ ์์๊ฐ ๋์ง ์๋ค ๋ผ๋ ๊ฒ์ ๋๋ค.
๋์ ์ฑ๋ฅ์ ๋์ค์ ๊ฐ์ ํ ์ ์์ง๋ง, ๊ตฌ์กฐ์ ์ธ ๊ฒฐํจ ๋๋ฌธ์ ๋ฐ์ํ ์ฑ๋ฅ ๋ฌธ์ ๋ ๋์ค์ ๊ณ ์น๊ธฐ ์ด๋ ต๋ค๊ณ ๋๊ผ์ต๋๋ค. ๊ทธ๋์ ๊ฐ๋ฐํ ๋๋ ์ธ์ธํ ์ต์ ํ์ ๋งค๋ชฐ๋๊ธฐ๋ณด๋ค ์ ์ฒด ๊ตฌ์กฐ์ ์ค๊ณ๋ฅผ ๋จผ์ ๋จ๋จํ๊ฒ ๋ง๋๋ ๊ฒ์ด ๋ ์ค์ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋ฌผ๋ก ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค๋ง, ์ ๊ธฐ์ค์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ฑ๋ฅ ๊ฐ์ ์ ์ค์ ๋ฌธ์ ๊ฐ ์์ ๋ ํ์. ์ธก์ ์์ด ๋ฏธ๋ฆฌ ์๋๋ฉด ๋ถํ์ํ ๋ณต์ก๋๋ง ์์ธ๋ค.
- ๊ตฌ์กฐ ๋ฌธ์ ๋ฅผ ๋จผ์ ๊ณ ์น์. ์ปดํฌ๋ํธ๋ฅผ ์ ๋ถ๋ฆฌํ๊ณ ํฉ์น๋ ๊ฒ์ ์ค๊ณ ์์ค์ ๋ฌธ์ ๋ผ์, ๊ตฌ์กฐ๊ฐ ์๋ชป๋๋ฉด ์ด๋ค ๋ฏธ์ธ ์ต์ ํ๋ ๊ทผ๋ณธ์ ํด๊ฒฐ์ด ๋์ง ์๋๋ค.
- ๊ทธ๋๋ ์ฌ์ ํ ๋๋ฆฌ๋ค๋ฉด select, React.memo, API ์๋ต ์ ๊ทํ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฐ์.
๊ธด ๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ๊ถ๊ธํ ์ ์ด๋ ํผ๋๋ฐฑ์ ์ธ์ ๋ ์ง ํ์ํฉ๋๋ค.
์ฐธ๊ณ ์๋ฃ
- React Query Render Optimizations | TkDodo's blog
- Fix the slow render before you fix the re-render
- React, Inline Functions, and Performance
- query/packages/query-core/src/utils.ts at b6516bd25edcc67dfaced09412f52c9660386a9b ยท TanStack/query ยท GitHub
- query/src/core/tests/utils.test.tsx at 80cecef22c3e088d6cd9f8fbc5cd9e2c0aab962f ยท TanStack/query ยท GitHub