๐Ÿ’ก 06. atomFamily โ€” ID ๋ณ„ ๋™์  atom ์ƒ์„ฑ ํŒจํ„ด

๐Ÿ“‹ ๊ฐœ์š”

atomFamily์˜ ๊ฐœ๋…, jotai-family๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€, ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ๋ณ„ ๋…๋ฆฝ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • atomFamily ๊ฐ€ "ํŒŒ๋ผ๋ฏธํ„ฐ๋ณ„ atom ์บ์‹œ" ๋ผ๋Š” ๊ฐœ๋…์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • jotai/utils ์˜ atomFamily ๊ฐ€ deprecated ๋˜์–ด jotai-family ํŒจํ‚ค์ง€๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•ˆ๋‹ค.
  • ๋ฌดํ•œ ์Šคํฌ๋กค ํ™˜๊ฒฝ์—์„œ remove() ์™€ setShouldRemove() ๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 20๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 12๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„

[๊ณ ์ • atom ์ˆ˜์‹ญ ๊ฐœ ์„ ์–ธ ์ง€์˜ฅ] โ†’ [atomFamily ๊ฐœ๋…] โ†’ [jotai-family ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜] โ†’ [๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•] โ†’ [๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€] โ†’ [TypeScript ํƒ€์ดํ•‘]

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

์˜์ฒ ์ด๊ฐ€ ์Šคํ„ฐ๋”” ์นด๋“œ๋งˆ๋‹ค ์ข‹์•„์š” ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ž‘์—…์„ ๋งก์€ ๋‚ ์ด์•ผ.

๐Ÿฃ ์˜์ฒ : (์Šฌ๋ž™์—์„œ ์˜ํ˜ธ ๋‹˜ ๋ฉ˜์…˜) "์˜ํ˜ธ ๋‹˜! ์Šคํ„ฐ๋”” ์นด๋“œ ์ข‹์•„์š” ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ–ˆ์–ด์š”. ๋ฆฌ๋ทฐ ๋ถ€ํƒ๋“œ๋ ค์š”!"

๐Ÿฆ ์˜ํ˜ธ ๋‹˜: (์ฝ”๋“œ ์—ด๊ณ  ์ž ์‹œ ์นจ๋ฌต) "์˜์ฒ  ๋‹˜... ์ด๊ฒŒ likeCount_001, likeCount_002, likeCount_003 ...?"

๐Ÿฃ ์˜์ฒ : (์ž์‹  ์žˆ๊ฒŒ) "๋„ค! ์Šคํ„ฐ๋”” ID ๋งˆ๋‹ค atom ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋’€์–ด์š”. ์Šคํ„ฐ๋””๊ฐ€ ์ง€๊ธˆ 50๊ฐœ๋‹ˆ๊นŒ atom 50๊ฐœ ์„ ์–ธํ–ˆ์–ด์š”!"

๐Ÿฆ ์˜ํ˜ธ ๋‹˜: "๋งŒ์•ฝ ์Šคํ„ฐ๋””๊ฐ€ 1000๊ฐœ๊ฐ€ ๋˜๋ฉด์š”? atom 1000๊ฐœ ์„ ์–ธํ•˜์‹ค ๊ฑด๊ฐ€์š”?"

๐Ÿฃ ์˜์ฒ : (๋ง๋ฌธ์ด ๋ง‰ํ˜€์„œ) "...์•„."

๐Ÿฆ ์˜ํ˜ธ ๋‹˜: "๊ฒŒ๋‹ค๊ฐ€ API ์—์„œ ์Šคํ„ฐ๋”” ๋ชฉ๋ก์ด ๋™์ ์œผ๋กœ ๋‚ด๋ ค์˜ค์ž–์•„์š”. ID ๋ฅผ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์—†์–ด์š”. atomFamily ๋ผ๋Š” ๊ฒŒ ์žˆ์–ด์š”. ํŒŒ๋ผ๋ฏธํ„ฐ๋งˆ๋‹ค atom ์„ ๋™์ ์œผ๋กœ ์บ์‹ฑํ•ด์ฃผ๊ฑฐ๋“ ์š”."

๐Ÿฃ ์˜์ฒ : (๋ฉ”๋ชจ์žฅ ์—ด๋ฉฐ) "๊ทธ๊ฒŒ ๋ญ”๋ฐ์š”...?"


๐Ÿค” ์™œ atomFamily ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€? ๐ŸŸข

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ๋ณ„ ๋…๋ฆฝ ์ƒํƒœ ๊ด€๋ฆฌ์—์„œ ๊ณ ์ • atom ์„ ์–ธ ๋ฐฉ์‹์˜ ํ•œ๊ณ„๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • atomFamily ๊ฐ€ "ํŒŒ๋ผ๋ฏธํ„ฐ โ†’ atom" ์˜ ์บ์‹œ ๋งต ์—ญํ• ์„ ํ•œ๋‹ค๋Š” ๊ฐœ๋…์„ ์ดํ•ดํ•œ๋‹ค

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์Šคํ„ฐ๋”” ์นด๋“œ 100๊ฐœ๊ฐ€ ์žˆ๊ณ , ๊ฐ ์นด๋“œ์— "์ข‹์•„์š”" ์ƒํƒœ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์žˆ์–ด์•ผ ํ•ด. atom ์„ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ• ๊นŒ?
"์Šคํ„ฐ๋”” ID ๋ฅผ key ๋กœ ํ•˜๋Š” Map ์ฒ˜๋Ÿผ atom ์„ ์ €์žฅํ•˜๋ฉด ์–ด๋–จ๊นŒ?" ๊นŒ์ง€ ๋– ์˜ฌ๋ ธ์œผ๋ฉด ์ถฉ๋ถ„ํ•ด.

๊ณ ์ • atom ์„ ์–ธ ๋ฐฉ์‹์˜ ํ•œ๊ณ„

// โŒ ๐Ÿฃ ์˜์ฒ ์˜ ์ดˆ๊ธฐ ์‹œ๋„ โ€” ์Šคํ„ฐ๋”” ID ๋งˆ๋‹ค atom ํ•˜๋“œ์ฝ”๋”ฉ
const likeCountAtom_001 = atom(0)
const likeCountAtom_002 = atom(0)
const likeCountAtom_003 = atom(0)
// ... 50๊ฐœ ๋ฐ˜๋ณต
const likeCountAtom_050 = atom(0)
 
// ๐Ÿฃ ์˜์ฒ : "์Šคํ„ฐ๋”” ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค ์—ฌ๊ธฐ๋‹ค ํ•œ ์ค„์”ฉ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜์ฃ  ๋ญ!"
// ๐Ÿฆ ์˜ํ˜ธ: "API ์—์„œ ๋™์ ์œผ๋กœ ID ๊ฐ€ ๋‚ด๋ ค์˜ค๋Š”๋ฐ, ์ฝ”๋“œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ฏธ๋ฆฌ ์„ ์–ธํ•ด์š”?
//          ๊ทธ๋ฆฌ๊ณ  ์Šคํ„ฐ๋”” 1000๊ฐœ ๋˜๋ฉด ์ด ํŒŒ์ผ ์–ด๋–ป๊ฒŒ ๋  ๊ฒƒ ๊ฐ™์•„์š”?"
// ๐ŸŸก ๊ฐœ์„  ์‹œ๋„ โ€” ๊ฐ์ฒด๋กœ ๋ฌถ๊ธฐ
const likeCountAtoms: Record<string, PrimitiveAtom<number>> = {
  '001': atom(0),
  '002': atom(0),
  '003': atom(0),
}
 
// ๐Ÿฃ ์˜์ฒ : "์ด๊ฑด ์–ด๋•Œ์š”? ๊ฐ์ฒด๋กœ ๋ฌถ์—ˆ์–ด์š”!"
// ๐Ÿฆ ์˜ํ˜ธ: "์—ฌ์ „ํžˆ API ๊ฐ€ ๋‚ด๋ ค์ฃผ๋Š” ID ๋ฅผ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์—†์–ด์š”.
//          ๊ทธ๋ฆฌ๊ณ  ์ด ๊ฐ์ฒด, ๋ชจ๋“ˆ ๋กœ๋“œ ์‹œ์ ์— ์ „๋ถ€ ๋ฉ”๋ชจ๋ฆฌ ์˜ฌ๋ผ๊ฐ€์š”.
//          ์“ฐ์ง€ ์•Š๋Š” atom ๋„ ์ „๋ถ€ ์‚ด์•„์žˆ์–ด์š”."

atomFamily ์˜ ํ•ด๋ฒ• โ€” ํ•„์š”ํ•  ๋•Œ ๋งŒ๋“ค๊ณ , ์บ์‹œํ•˜๊ณ , ์žฌ์‚ฌ์šฉ

// โœ… ๐Ÿฆ ์˜ํ˜ธ: "atomFamily ๋Š” param โ†’ atom ์˜ Map ์ด์—์š”.
//             ์ฒ˜์Œ ์š”์ฒญํ•˜๋ฉด atom ์„ ๋งŒ๋“ค์–ด ์บ์‹œํ•˜๊ณ ,
//             ๊ฐ™์€ param ์œผ๋กœ ๋‹ค์‹œ ์š”์ฒญํ•˜๋ฉด ์บ์‹œ๋œ atom ์„ ๋ฐ˜ํ™˜ํ•ด์š”."
import { atomFamily } from 'jotai-family'
import { atom } from 'jotai'
 
const studyLikeAtomFamily = atomFamily((studyId: string) => atom(0))
 
// ์‚ฌ์šฉ
const StudyCard = ({ studyId }: { studyId: string }) => {
  // ์ฒ˜์Œ: atom(0) ์ƒ์„ฑ ํ›„ ์บ์‹œ
  // ์ดํ›„: ์บ์‹œ์—์„œ ๊บผ๋‚ด ์žฌ์‚ฌ์šฉ
  const [likeCount, setLikeCount] = useAtom(studyLikeAtomFamily(studyId))
  return (
    <button onClick={() => setLikeCount((prev) => prev + 1)}>
      โค๏ธ {likeCount}
    </button>
  )
}
atomFamily ๋‚ด๋ถ€ ์บ์‹œ (Map)
  โ”œโ”€โ”€ '001' โ†’ PrimitiveAtom<number> (value: 5)
  โ”œโ”€โ”€ '002' โ†’ PrimitiveAtom<number> (value: 2)
  โ””โ”€โ”€ '003' โ†’ PrimitiveAtom<number> (value: 12)

studyLikeAtomFamily('001') โ†’ ์บ์‹œ์—์„œ ๋™์ผํ•œ atom config ๋ฐ˜ํ™˜
studyLikeAtomFamily('999') โ†’ ์ƒˆ atom(0) ์ƒ์„ฑ + ์บ์‹œ ์ €์žฅ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
atomFamily ๋Š” "ID ๋ณ„ ๊ฐœ์ธ ์‚ฌ๋ฌผํ•จ" ์ด์•ผ. ์ฒ˜์Œ ์˜ค๋ฉด ์ƒˆ ์‚ฌ๋ฌผํ•จ์„ ๋งŒ๋“ค์–ด ์ฃผ๊ณ , ๋‹ค์Œ์— ์˜ค๋ฉด ๊ฐ™์€ ์‚ฌ๋ฌผํ•จ์œผ๋กœ ์•ˆ๋‚ดํ•ด์ค˜.


โš ๏ธ DEPRECATED ๊ฒฝ๊ณ  โ€” jotai-family ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • jotai/utils ์˜ atomFamily ๊ฐ€ Jotai v3 ์—์„œ ์ œ๊ฑฐ ์˜ˆ์ •์ด๋ผ๋Š” ๊ฒƒ์„ ์•ˆ๋‹ค
  • jotai-family ํŒจํ‚ค์ง€๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค

๊ณต์‹ Deprecated ์„ ์–ธ

Jotai ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ช…์‹œ๋˜์–ด ์žˆ์–ด:

"atomFamily is deprecated and will be removed in Jotai v3. Please migrate to the jotai-family package, which provides the same API with additional features like atomTree."

jotai/utils ์—์„œ atomFamily ๋ฅผ import ํ•˜๋ฉด ์ง€๊ธˆ๋„ deprecation warning ์ด ๋‚˜์™€. ์ƒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ jotai-family ๋ฅผ ์จ์•ผ ํ•ด.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐฉ๋ฒ•

# 1. jotai-family ํŒจํ‚ค์ง€ ์„ค์น˜
npm install jotai-family
# ๋˜๋Š”
pnpm add jotai-family
// โŒ Before (deprecated)
import { atomFamily } from 'jotai/utils'
 
// โœ… After โ€” import ๊ฒฝ๋กœ๋งŒ ๋ฐ”๊พธ๋ฉด ๋!
import { atomFamily } from 'jotai-family'
 
// API ๋Š” ์™„์ „ํžˆ ๋™์ผํ•ด. ์ฝ”๋“œ ๋กœ์ง์€ ์ˆ˜์ • ๋ถˆํ•„์š”.
const studyLikeAtomFamily = atomFamily((studyId: string) => atom(0))

๐Ÿ’ก ํŒ jotai-family ๋Š” atomFamily ์˜ ๋“œ๋กญ์ธ ๋Œ€์ฒด์ œ์•ผ. import ๊ฒฝ๋กœ ํ•˜๋‚˜๋งŒ ๋ฐ”๊พธ๋ฉด ๋ผ. atomTree ๊ฐ™์€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•ด.


๐Ÿ—๏ธ atomFamily ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ• ๐ŸŸข

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • atomFamily ๋กœ ์Šคํ„ฐ๋”” ID ๋ณ„ ๋…๋ฆฝ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค
  • primitive atom ๋ฟ ์•„๋‹ˆ๋ผ derived atom, async atom ๋„ family ๋กœ ๋งŒ๋“œ๋Š” ๋ฒ•์„ ์ดํ•ดํ•œ๋‹ค

๊ธฐ๋ณธ ํŒจํ„ด โ€” ์Šคํ„ฐ๋”” ์ข‹์•„์š”

import { atom, useAtom } from 'jotai'
import { atomFamily } from 'jotai-family'
 
// ๐Ÿฆ ์˜ํ˜ธ: "initializeAtom ํ•จ์ˆ˜๊ฐ€ param ์„ ๋ฐ›์•„์„œ ์–ด๋–ค ์ข…๋ฅ˜์˜ atom ์ด๋“  ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์–ด์š”"
const studyLikeAtomFamily = atomFamily((studyId: string) => atom(false))
// studyId ๋ณ„๋กœ ๋…๋ฆฝ์ ์ธ PrimitiveAtom<boolean> ์„ ์บ์‹ฑ
 
// ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ โ€” studyId ๊ฐ€ key ์—ญํ• 
const StudyLikeButton = ({ studyId }: { studyId: string }) => {
  const [isLiked, setIsLiked] = useAtom(studyLikeAtomFamily(studyId))
  return (
    <button
      onClick={() => setIsLiked((prev) => !prev)}
      className={isLiked ? 'liked' : ''}
    >
      {isLiked ? 'โค๏ธ' : '๐Ÿค'} ์ข‹์•„์š”
    </button>
  )
}
 
// ์Šคํ„ฐ๋”” ๋ชฉ๋ก โ€” ๊ฐ ์นด๋“œ๊ฐ€ ๋…๋ฆฝ ์ƒํƒœ๋ฅผ ๊ฐ€์ง
const StudyList = ({ studies }: { studies: Study[] }) => (
  <ul>
    {studies.map((study) => (
      <li key={study.id}>
        <StudyCard study={study} />
        {/* ๊ฐ ์Šคํ„ฐ๋””๋งˆ๋‹ค ๋…๋ฆฝ์ ์ธ ์ข‹์•„์š” ์ƒํƒœ */}
        <StudyLikeButton studyId={study.id} />
      </li>
    ))}
  </ul>
)

๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ โ€” ์ƒ์„ธ ์ƒํƒœ ๊ด€๋ฆฌ

interface StudyCardState {
  isExpanded: boolean
  commentDraft: string
  activeTab: 'info' | 'members' | 'comments'
}
 
// ๐Ÿฆ ์˜ํ˜ธ: "๊ฐ์ฒด atom ๋„ family ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”"
const studyCardStateAtomFamily = atomFamily((studyId: string) =>
  atom<StudyCardState>({
    isExpanded: false,
    commentDraft: '',
    activeTab: 'info',
  })
)
 
const StudyCard = ({ studyId }: { studyId: string }) => {
  const [cardState, setCardState] = useAtom(studyCardStateAtomFamily(studyId))
 
  return (
    <div>
      <button onClick={() => setCardState((prev) => ({ ...prev, isExpanded: !prev.isExpanded }))}>
        {cardState.isExpanded ? '์ ‘๊ธฐ' : 'ํŽผ์น˜๊ธฐ'}
      </button>
      {cardState.isExpanded && (
        <div>
          {/* ํƒญ, ๋Œ“๊ธ€ ์ดˆ์•ˆ ๋“ฑ ๊ฐ ์Šคํ„ฐ๋”” ์นด๋“œ์˜ ๋…๋ฆฝ ์ƒํƒœ */}
        </div>
      )}
    </div>
  )
}

areEqual ์˜ต์…˜ โ€” ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ ๋น„๊ต

import deepEqual from 'fast-deep-equal'
 
// ๐Ÿฆ ์˜ํ˜ธ: "ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๊ฐ์ฒด์ผ ๋•Œ๋Š” ์ฐธ์กฐ ๋น„๊ต ๋Œ€์‹  deepEqual ์„ ์จ์•ผ ํ•ด์š”"
// ๊ธฐ๋ณธ๊ฐ’์€ Object.is() โ€” ์ฐธ์กฐ ๋น„๊ต๋ผ ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋งค๋ฒˆ ์ƒˆ atom ์ด ์ƒ์„ฑ๋ผ
 
const studyFilterAtomFamily = atomFamily(
  ({ category, tag }: { category: string; tag: string }) =>
    atom<Study[]>([]),
  deepEqual // ๐Ÿฆ ์˜ํ˜ธ: "{ category: 'react', tag: 'beginner' } ๊ฐ€ ๋™์ผํ•˜๋ฉด ๊ฐ™์€ atom ๋ฐ˜ํ™˜"
)

๐Ÿงน ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€ โ€” remove ์™€ setShouldRemove ๐Ÿ”ด

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • atomFamily ๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ Map ์„ ์œ ์ง€ํ•œ๋‹ค๋Š” ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•œ๋‹ค
  • ๋ฌดํ•œ ์Šคํฌ๋กค ํ™˜๊ฒฝ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
๋ฌดํ•œ ์Šคํฌ๋กค๋กœ ์Šคํ„ฐ๋”” ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋ฉด ID ๊ฐ€ ๊ณ„์† ์ถ”๊ฐ€๋ผ. atomFamily ๋Š” ID ๋งˆ๋‹ค ์บ์‹œ๋ฅผ ์œ ์ง€ํ•ด. ์ด๊ฒŒ ๊ณ„์† ์Œ“์ด๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ๊นŒ?

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์›๋ฆฌ

Jotai ๋ ˆํผ๋Ÿฐ์Šค์— ๋ช…์‹œ๋˜์–ด ์žˆ์–ด:

"Internally, atomFamily is just a Map whose key is a param and whose value is an atom config. Unless you explicitly remove unused params, this leads to memory leaks."

๋ฌดํ•œ ์Šคํฌ๋กค ์‹œ๋‚˜๋ฆฌ์˜ค:
  1ํŽ˜์ด์ง€: ID 001~020 โ†’ atomFamily ์บ์‹œ์— 20๊ฐœ ์ถ”๊ฐ€
  2ํŽ˜์ด์ง€: ID 021~040 โ†’ atomFamily ์บ์‹œ์— 40๊ฐœ ๋ˆ„์ 
  3ํŽ˜์ด์ง€: ID 041~060 โ†’ atomFamily ์บ์‹œ์— 60๊ฐœ ๋ˆ„์ 
  ...
  100ํŽ˜์ด์ง€: ID 001~2000 โ†’ atomFamily ์บ์‹œ์— 2000๊ฐœ (ํ™”๋ฉด์—” 20๊ฐœ๋งŒ ๋ณด์ž„!)

๐Ÿฃ ์˜์ฒ : "...์ด๊ฑฐ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ธฐ๋Šฅ ๋งŒ๋“ค๋ฉด ์Šคํฌ๋กคํ• ์ˆ˜๋ก ๋ฉ”๋ชจ๋ฆฌ ํญํƒ„์ด๋„ค์š”."

ํ•ด๊ฒฐ์ฑ… 1: remove(param) โ€” ๊ฐœ๋ณ„ ์‚ญ์ œ

const studyLikeAtomFamily = atomFamily((studyId: string) => atom(false))
 
const StudyCard = ({ studyId }: { studyId: string }) => {
  useEffect(() => {
    // ์ปดํฌ๋„ŒํŠธ unmount ์‹œ ํ•ด๋‹น ID ์˜ atom ์„ ์บ์‹œ์—์„œ ์‚ญ์ œ
    return () => {
      studyLikeAtomFamily.remove(studyId)
      // ๐Ÿฆ ์˜ํ˜ธ: "ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง„ ์Šคํ„ฐ๋”” ์นด๋“œ์˜ atom ์€ ํ•„์š” ์—†์œผ๋‹ˆ๊นŒ ์ง€์›Œ์š”"
    }
  }, [studyId])
 
  const [isLiked, setIsLiked] = useAtom(studyLikeAtomFamily(studyId))
  return <LikeButton isLiked={isLiked} onToggle={() => setIsLiked((prev) => !prev)} />
}

ํ•ด๊ฒฐ์ฑ… 2: setShouldRemove โ€” ์ž๋™ TTL ์„ค์ •

// ๐Ÿฆ ์˜ํ˜ธ: "์บ์‹œ์—์„œ ๊บผ๋‚ผ ๋•Œ๋งˆ๋‹ค ์ด ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ด์„œ ์‚ญ์ œ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•ด์š”"
const studyLikeAtomFamily = atomFamily((studyId: string) => atom(false))
 
// ์ƒ์„ฑ๋œ ์ง€ 10๋ถ„์ด ์ง€๋‚œ atom ์€ ์ž๋™ ์‚ญ์ œ
studyLikeAtomFamily.setShouldRemove((createdAt, studyId) => {
  const TEN_MINUTES = 10 * 60 * 1000
  return Date.now() - createdAt > TEN_MINUTES
  // ๐Ÿฆ ์˜ํ˜ธ: "10๋ถ„ ๋„˜์€ ํ•ญ๋ชฉ์€ ์บ์‹œ์—์„œ ๊บผ๋‚ผ ๋•Œ ์ž๋™์œผ๋กœ ์‚ญ์ œํ•ด์š”"
})
 
// null ์„ ๋„˜๊ธฐ๋ฉด shouldRemove ํ•จ์ˆ˜ ํ•ด์ œ
// studyLikeAtomFamily.setShouldRemove(null)

ํ•ด๊ฒฐ์ฑ… 3: getParams() ๋กœ ์บ์‹œ ํ˜„ํ™ฉ ํ™•์ธ (๋””๋ฒ„๊น…)

// ๐Ÿฃ ์˜์ฒ : "์ง€๊ธˆ ์บ์‹œ์— ๋ญ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”?"
// ๐Ÿฆ ์˜ํ˜ธ: "getParams() ๋กœ ํ˜„์žฌ ์บ์‹ฑ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ชฉ๋ก์„ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”"
 
for (const studyId of studyLikeAtomFamily.getParams()) {
  console.log('์บ์‹ฑ ์ค‘์ธ studyId:', studyId)
}
// ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋””๋ฒ„๊น…์— ํ™œ์šฉ

๋ฌดํ•œ ์Šคํฌ๋กค ์™„์ „ ์˜ˆ์‹œ

// โœ… ๋ฌดํ•œ ์Šคํฌ๋กค + atomFamily ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „ ํŒจํ„ด
const studyCardStateFamily = atomFamily((studyId: string) =>
  atom({ isExpanded: false, isLiked: false })
)
 
// ๊ฐœ๋ณ„ ์นด๋“œ โ€” unmount ์‹œ ์บ์‹œ ์ •๋ฆฌ
const StudyCard = ({ study }: { study: Study }) => {
  useEffect(() => {
    return () => {
      // ๐Ÿฆ ์˜ํ˜ธ: "virtualizer ๋กœ ํ™”๋ฉด ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ„ ์นด๋“œ๊ฐ€ unmount ๋˜๋ฉด atom ๋„ ์ •๋ฆฌํ•ด์š”"
      studyCardStateFamily.remove(study.id)
    }
  }, [study.id])
 
  const [cardState, setCardState] = useAtom(studyCardStateFamily(study.id))
  return (
    <div>
      <h3>{study.title}</h3>
      <button onClick={() => setCardState((prev) => ({ ...prev, isLiked: !prev.isLiked }))}>
        {cardState.isLiked ? 'โค๏ธ' : '๐Ÿค'}
      </button>
    </div>
  )
}
 
// ๋ฌดํ•œ ์Šคํฌ๋กค ๋ชฉ๋ก
const InfiniteStudyList = () => {
  const { data, fetchNextPage } = useInfiniteQuery({ ... })
 
  return (
    <>
      {data.pages.flat().map((study) => (
        <StudyCard key={study.id} study={study} />
      ))}
      <button onClick={() => fetchNextPage()}>๋” ๋ณด๊ธฐ</button>
    </>
  )
}

๐Ÿ”ท TypeScript ์™€ ํ•จ๊ป˜ โ€” ์ œ๋„ค๋ฆญ ํƒ€์ž… ๋ช…์‹œ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • atomFamily ์˜ TypeScript ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ๋ช…์‹œํ•˜๋Š” ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค
import { atom } from 'jotai'
import type { PrimitiveAtom } from 'jotai'
import { atomFamily } from 'jotai-family'
 
// ๋ฐฉ๋ฒ• 1: TypeScript ์ž๋™ ์ถ”๋ก  (initializeAtom ์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์—์„œ ์ถ”๋ก )
const studyLikeFamily = atomFamily((studyId: string) => atom(false))
// โ†’ AtomFamily<string, PrimitiveAtom<boolean>>
 
// ๋ฐฉ๋ฒ• 2: ๋ช…์‹œ์  ์ œ๋„ค๋ฆญ (ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…๊ณผ atom ํƒ€์ž… ์ง์ ‘ ์ง€์ •)
const studyLikeFamily2 = atomFamily<string, PrimitiveAtom<boolean>>(
  (studyId: string) => atom(false)
)
 
// ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ๋ช…์‹œ
interface StudyKey {
  studyId: string
  userId: string
}
 
const userStudyStatusFamily = atomFamily<StudyKey, PrimitiveAtom<'applied' | 'approved' | 'rejected'>>(
  ({ studyId, userId }) => atom<'applied' | 'approved' | 'rejected'>('applied'),
  (a, b) => a.studyId === b.studyId && a.userId === b.userId // ์ปค์Šคํ…€ ๋น„๊ต
)
 
// ์‚ฌ์šฉ โ€” ํƒ€์ž… ์™„๋ฒฝ ์ ์šฉ
const StatusBadge = ({ studyId, userId }: StudyKey) => {
  const status = useAtomValue(userStudyStatusFamily({ studyId, userId }))
  // status: 'applied' | 'approved' | 'rejected' โ€” ์ž๋™์™„์„ฑ ์™„๋ฒฝ
  return <span>{status === 'approved' ? '์Šน์ธ๋จ' : '๋Œ€๊ธฐ ์ค‘'}</span>
}

โš–๏ธ TanStack Query queryKey ์™€์˜ ์—ญํ•  ๋น„๊ต ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • queryKey ์™€ atomFamily ์˜ ์—ญํ•  ์ฐจ์ด๋ฅผ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๊ฐ๊ฐ ์–ด๋–ค ์ƒํƒœ๋ฅผ ๋‹ด๋‹นํ•ด์•ผ ํ•˜๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค

์˜์ˆ˜๋„ค ํŒ€์€ ์„œ๋ฒ„ ์ƒํƒœ๋Š” TanStack Query, ํด๋ผ์ด์–ธํŠธ UI ์ƒํƒœ๋Š” Jotai ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ์žˆ์–ด:

TanStack Query queryKey ๊ธฐ๋ฐ˜ ์บ์‹ฑ
  โ”œโ”€โ”€ ['study', '001'] โ†’ ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ์Šคํ„ฐ๋”” ์ƒ์„ธ ๋ฐ์ดํ„ฐ (์ž๋™ ์žฌ๊ฒ€์ฆ)
  โ”œโ”€โ”€ ['studies'] โ†’ ์Šคํ„ฐ๋”” ๋ชฉ๋ก ๋ฐ์ดํ„ฐ
  โ””โ”€โ”€ ['user', 'me'] โ†’ ๋‚ด ํ”„๋กœํ•„ ๋ฐ์ดํ„ฐ

atomFamily ๊ธฐ๋ฐ˜ ์บ์‹ฑ
  โ”œโ”€โ”€ '001' โ†’ { isExpanded: false, isLiked: true }  (UI ์ธํ„ฐ๋ž™์…˜ ์ƒํƒœ)
  โ”œโ”€โ”€ '002' โ†’ { isExpanded: true, isLiked: false }
  โ””โ”€โ”€ '003' โ†’ { isExpanded: false, isLiked: true }
// โœ… ์˜์ˆ˜๋„ค ํŒ€์˜ ์—ญํ•  ๋ถ„๋ฆฌ ์˜ˆ์‹œ
const StudyCard = ({ studyId }: { studyId: string }) => {
  // ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ โ€” TanStack Query ๊ฐ€ ๊ด€๋ฆฌ (์บ์‹ฑ, ์žฌ๊ฒ€์ฆ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํŒจ์น˜)
  const { data: study } = useQuery({
    queryKey: ['study', studyId],
    queryFn: () => fetchStudy(studyId),
  })
 
  // UI ์ƒํƒœ โ€” Jotai atomFamily ๊ฐ€ ๊ด€๋ฆฌ (์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๊ณต์œ , ๋…๋ฆฝ์ )
  const [uiState, setUiState] = useAtom(studyCardStateFamily(studyId))
  // ๐Ÿฆ ์˜ํ˜ธ: "์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์™€ UI ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด ๊ฐ์ž์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”"
 
  if (!study) return <Skeleton />
 
  return (
    <div>
      <h3>{study.title}</h3>  {/* ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ */}
      <button onClick={() => setUiState((prev) => ({ ...prev, isExpanded: !prev.isExpanded }))}>
        {uiState.isExpanded ? '์ ‘๊ธฐ' : 'ํŽผ์น˜๊ธฐ'}  {/* UI ์ƒํƒœ */}
      </button>
    </div>
  )
}
๊ธฐ์ค€TanStack Query (queryKey)Jotai (atomFamily)
๋ฐ์ดํ„ฐ ์ถœ์ฒ˜์„œ๋ฒ„ (API)ํด๋ผ์ด์–ธํŠธ (์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜)
์ž๋™ ์žฌ๊ฒ€์ฆโœ… staleTime, refetchโŒ (UI ์ƒํƒœ๋Š” ์„œ๋ฒ„ ๋™๊ธฐํ™” ๋ถˆํ•„์š”)
์—ญํ• ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌํด๋ผ์ด์–ธํŠธ UI ์ƒํƒœ ๊ด€๋ฆฌ
์˜ˆ์‹œ์Šคํ„ฐ๋”” ์ƒ์„ธ ์ •๋ณด์นด๋“œ ํŽผ์นจ/์ ‘ํž˜ ์—ฌ๋ถ€, ์ข‹์•„์š” UI ์ƒํƒœ

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ


โŒ ๊ฐ™์€ ํŒŒ๋ผ๋ฏธํ„ฐ์ธ๋ฐ ๋‹ค๋ฅธ atom ์ด ๋ฐ˜ํ™˜๋จ (๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ)

์ฆ์ƒ: ๋™์ผํ•œ { studyId: '001', tag: 'react' } ๋กœ ํ˜ธ์ถœํ•ด๋„ ๋งค๋ฒˆ ์ƒˆ atom ์ด ์ƒ์„ฑ๋จ

์›์ธ:

// ๐Ÿฃ ์˜์ฒ : "๋ถ„๋ช… ๊ฐ™์€ ๊ฐ’์ธ๋ฐ ์™œ ๋‹ค๋ฅธ atom ์ด ๋‚˜์˜ค์ฃ ?"
const familyAtom = atomFamily(
  ({ studyId, tag }: { studyId: string; tag: string }) => atom([])
)
// ๊ธฐ๋ณธ๊ฐ’์€ Object.is() ๋น„๊ต โ†’ ๊ฐ์ฒด๋Š” ์ฐธ์กฐ ๋น„๊ต โ†’ ๋งค๋ฒˆ ์ƒˆ ๊ฐ์ฒด = ๋งค๋ฒˆ ์ƒˆ atom

ํ•ด๊ฒฐ์ฑ…:

import deepEqual from 'fast-deep-equal'
 
const familyAtom = atomFamily(
  ({ studyId, tag }: { studyId: string; tag: string }) => atom([]),
  deepEqual // โœ… ๊ฐ’ ๋น„๊ต๋กœ ๋ณ€๊ฒฝ
)

โŒ atomFamily import ์‹œ Deprecation Warning

์ฆ์ƒ:

Warning: atomFamily from 'jotai/utils' is deprecated.
Please migrate to 'jotai-family' package.

ํ•ด๊ฒฐ์ฑ…:

npm install jotai-family
// Before
import { atomFamily } from 'jotai/utils'
// After
import { atomFamily } from 'jotai-family'

โŒ ๋ฌดํ•œ ์Šคํฌ๋กค์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๊ณ„์† ์ฆ๊ฐ€

์›์ธ: atomFamily ์บ์‹œ๊ฐ€ ๋ฌด์ œํ•œ์œผ๋กœ ์ฆ๊ฐ€

ํ•ด๊ฒฐ์ฑ…:

// ์ปดํฌ๋„ŒํŠธ unmount ์‹œ remove ํ˜ธ์ถœ
useEffect(() => {
  return () => {
    myAtomFamily.remove(id)
  }
}, [id])
 
// ๋˜๋Š” TTL ๊ธฐ๋ฐ˜ ์ž๋™ ์‚ญ์ œ
myAtomFamily.setShouldRemove(
  (createdAt) => Date.now() - createdAt > 5 * 60 * 1000 // 5๋ถ„
)

๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๐Ÿ“‹ atomFamily ํ•ต์‹ฌ ๊ฐœ๋… ์š”์•ฝ

๊ฐœ๋…์„ค๋ช…
atomFamily(fn)(param) => Atom ํ˜•ํƒœ. param ๋ณ„ atom ์„ Map ์— ์บ์‹ฑ
areEqual๋‘ ๋ฒˆ์งธ ์ธ์ž. ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ ๋น„๊ต ํ•จ์ˆ˜ (๊ธฐ๋ณธ: Object.is)
remove(param)ํŠน์ • param ์˜ atom ์„ ์บ์‹œ์—์„œ ์‚ญ์ œ
setShouldRemove(fn)TTL ๊ธฐ๋ฐ˜ ์ž๋™ ์‚ญ์ œ ํ•จ์ˆ˜ ๋“ฑ๋ก
getParams()ํ˜„์žฌ ์บ์‹ฑ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ชฉ๋ก ๋ฐ˜ํ™˜ (๋””๋ฒ„๊น…์šฉ)

โš ๏ธ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง ๊ฒƒ

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆโœ… ์ข‹์€ ์˜ˆ
import ๊ฒฝ๋กœfrom 'jotai/utils'from 'jotai-family'
๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ ๋น„๊ต๊ธฐ๋ณธ Object.is() ์‚ฌ์šฉdeepEqual ํ•จ์ˆ˜ ์ง€์ •
๋ฌดํ•œ ์Šคํฌ๋กค์บ์‹œ ์‚ญ์ œ ์—†์ด ๊ณ„์† ์ถ”๊ฐ€remove() ๋˜๋Š” setShouldRemove()

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ๐Ÿ”ด ์‹œ๋‹ˆ์–ด ๋ฉด์ ‘ ์งˆ๋ฌธ (์˜์ฒ ์˜ ๋ฉด์ ‘ ๋„์ „)

"atomFamily ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ด๋ณด์„ธ์š”."

  • A) atomFamily ๋Š” WeakMap ์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐฉ์ง€๋œ๋‹ค
  • B) atomFamily ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Map ์„ ์‚ฌ์šฉํ•˜๊ณ , ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ฌดํ•œํžˆ ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ ์บ์‹œ๊ฐ€ ๋ฌด์ œํ•œ ์ฆ๊ฐ€ํ•˜๋ฏ€๋กœ remove() ๋˜๋Š” setShouldRemove() ๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์ •๋ฆฌํ•ด์•ผ ํ•œ๋‹ค
  • C) ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด Provider ๋ฅผ ์ƒˆ๋กœ ๋งˆ์šดํŠธํ•˜๋ฉด ํ•ด๊ฒฐ๋œ๋‹ค
  • D) atomFamily ๋Š” atom() ๊ณผ ๋‹ฌ๋ฆฌ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์ด ์ž๋™์œผ๋กœ ๋œ๋‹ค

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: Jotai ๋ ˆํผ๋Ÿฐ์Šค์— ๋ช…์‹œ๋œ ๊ฒƒ์ฒ˜๋Ÿผ, atomFamily ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Map<param, atomConfig> ์•ผ. Jotai store ๊ฐ€ WeakMap ์„ ์“ฐ๋Š” ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ, atomFamily ์˜ ์บ์‹œ Map ์€ ์ผ๋ฐ˜ Map ์ด๋ผ ํ‚ค ์ฐธ์กฐ๊ฐ€ ์‚ฌ๋ผ์ ธ๋„ ์ž๋™ GC ๊ฐ€ ์•ˆ ๋ผ. ๋ฌดํ•œ ์Šคํฌ๋กค์ฒ˜๋Ÿผ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๊ณ„์† ์ถ”๊ฐ€๋˜๋Š” ํ™˜๊ฒฝ์—์„œ๋Š” remove(param) ์ด๋‚˜ setShouldRemove(fn) ์œผ๋กœ ๋ช…์‹œ์  ์ •๋ฆฌ๊ฐ€ ํ•„์ˆ˜์•ผ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A ๋Š” ํ‹€๋ ธ์–ด โ€” atomFamily ์บ์‹œ๋Š” WeakMap ์ด ์•„๋‹Œ ์ผ๋ฐ˜ Map ์ด์•ผ. C ์™€ D ๋„ ํ‹€๋ ธ์–ด โ€” Provider ์žฌ๋งˆ์šดํŠธ๋กœ๋Š” atomFamily ์บ์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜์ง€ ์•Š๊ณ , GC ๋„ ์ž๋™์œผ๋กœ ๋˜์ง€ ์•Š์•„.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "Jotai store ๋Š” WeakMap ์ด์ง€๋งŒ, atomFamily ์บ์‹œ๋Š” ์ผ๋ฐ˜ Map. ์•Œ์•„์„œ ์ •๋ฆฌ ์•ˆ ๋ผ."

Q2. ๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ (์˜์ˆ™์˜ UX ํ”ผ๋“œ๋ฐฑ)

์˜์ˆ™ ๋‹˜์ด ๋””์ž์ธ ๋ฆฌ๋ทฐ์—์„œ ๋งํ–ˆ์–ด: "์Šคํ„ฐ๋”” ์นด๋“œ๋ฅผ ํŽผ์ณค๋‹ค๊ฐ€ ๋‹ค๋ฅธ ์นด๋“œ ํด๋ฆญํ•˜๋ฉด ํŽผ์ณ์ง„ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”. ์ง€๊ธˆ์€ ์Šคํฌ๋กคํ•˜๋ฉด ์ดˆ๊ธฐํ™”๋ผ์š”."
์˜์ฒ ์ด๊ฐ€ ํ˜„์žฌ ํŽผ์นจ/์ ‘ํž˜ ์ƒํƒœ๋ฅผ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€ useState ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์–ด. ์ด๊ฑธ atomFamily ๋กœ ์ „ํ™˜ํ•˜๋ฉด ์˜์ˆ™ ๋‹˜์˜ ํ”ผ๋“œ๋ฐฑ์ด ํ•ด๊ฒฐ๋˜๋Š” ์ด์œ ๋Š”?

  • A) atomFamily ๋Š” ์„œ๋ฒ„ ์ƒํƒœ์™€ ๋™๊ธฐํ™”๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค
  • B) atomFamily ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ(studyId) ๋ณ„๋กœ atom ์„ ์บ์‹ฑํ•˜๋ฏ€๋กœ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount/mount ๋˜์–ด๋„ atom ์ด ์œ ์ง€๋˜์–ด ์ƒํƒœ๊ฐ€ ๋ณด์กด๋œ๋‹ค
  • C) atomFamily ๋Š” ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ž๋™์œผ๋กœ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค
  • D) React ๊ฐ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ virtualize ํ•ด์„œ ์ƒํƒœ๋ฅผ ๋ณด์กดํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: useState ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋กœ์ปฌ ์ƒํƒœ๋ผ์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount ๋˜๋ฉด ์ƒํƒœ๋„ ์‚ฌ๋ผ์ ธ. ๊ฐ€์ƒ ์Šคํฌ๋กค(virtualizer)์ด๋‚˜ ํŽ˜์ด์ง€๋„ค์ด์…˜์—์„œ ํ™”๋ฉด ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount ๋˜๋ฉด useState ๊ฐ’์€ ์ดˆ๊ธฐํ™”๋ผ. ๋ฐ˜๋ฉด atomFamily(studyId) ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด atom ์ด studyId ๋ฅผ key ๋กœ Jotai store ์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount ๋˜์–ด๋„ ์ƒํƒœ๊ฐ€ ๋ณด์กด๋ผ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "useState ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ์–ต, atomFamily ๋Š” store ์˜ ๊ธฐ์–ต. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‚ฌ๋ผ์ ธ๋„ store ๋Š” ๊ธฐ์–ตํ•ด."

Q3. ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

atomFamily ์™€ TanStack Query ์˜ queryKey ๊ฐ€ ๋น„์Šทํ•ด ๋ณด์ด๋Š”๋ฐ, ๋‘ ๊ฐ€์ง€์˜ ์—ญํ•  ์ฐจ์ด๋ฅผ ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•ด๋ด.

์˜ˆ์‹œ ๋‹ต๋ณ€:

"๋‘˜ ๋‹ค 'ํŒŒ๋ผ๋ฏธํ„ฐ๋ณ„ ์บ์‹œ' ํŒจํ„ด์ด์ง€๋งŒ ๋‹ด๋Š” ๋‚ด์šฉ์ด ๋‹ฌ๋ผ. queryKey ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜๊ณ  ์ž๋™์œผ๋กœ ์žฌ๊ฒ€์ฆํ•ด์ค˜. atomFamily ๋Š” ์‚ฌ์šฉ์ž์˜ ํด๋ฆญ, ํŽผ์นจ/์ ‘ํž˜, ์ข‹์•„์š” ๊ฐ™์€ ํด๋ผ์ด์–ธํŠธ UI ์ƒํƒœ๋ฅผ ID ๋ณ„๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•ด. ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋Š” Query, UI ์ƒํƒœ๋Š” Jotai ๋ผ๋Š” ์—ญํ•  ๋ถ„๋ฆฌ๊ฐ€ ํ•ต์‹ฌ์ด์•ผ."

๐Ÿ’ก ์ด ๋น„์œ ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด: ์ƒํƒœ ๊ด€๋ฆฌ ์•„ํ‚คํ…์ฒ˜์˜ ์—ญํ•  ๋ถ„๋ฆฌ๋ฅผ ์ดํ•ดํ•œ ๊ฑฐ์•ผ. ๋‹ค์Œ ๊ฐ€์ด๋“œ๋กœ ๋„˜์–ด๊ฐ€๋„ ์ถฉ๋ถ„ํ•ด!


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ ์ •๋ง ์ฐฝํ”ผํ•˜๊ณ  ๋ฟŒ๋“ฏํ•œ ํ•˜๋ฃจ๋ฅผ ๋™์‹œ์— ์‚ด์•˜์–ด.

์•„์นจ์— ์ž์‹  ์žˆ๊ฒŒ PR ์˜ฌ๋ฆฐ ์ฝ”๋“œ๊ฐ€ likeCountAtom_001 ๋ถ€ํ„ฐ likeCountAtom_050 ๊นŒ์ง€ 50๊ฐœ๊ฐ€ ์ค„์ค„์ด ์„ ์–ธ๋ผ ์žˆ์—ˆ๋Š”๋ฐ, ์˜ํ˜ธ ๋‹˜์ด ๊ทธ๊ฑธ ๋ณด๊ณ  ์ž ์‹œ ์นจ๋ฌตํ•˜์…จ๊ฑฐ๋“ . ๊ทธ ์นจ๋ฌต์ด ๋‚˜ํ•œํ…Œ๋Š” ๊ต‰์žฅํžˆ ๊ธธ๊ฒŒ ๋А๊ปด์กŒ์–ด. "์˜์ฒ  ๋‹˜, ์Šคํ„ฐ๋”” 1000๊ฐœ ๋˜๋ฉด ์–ด์ฉŒ๋ ค๊ณ ์š”?" ๋ผ๋Š” ํ•œ ๋งˆ๋””์— ๋จธ๋ฆฟ์†์ด ํ•˜์–˜์กŒ๋‹ค.

atomFamily ๋ฅผ ๋ฐฐ์šฐ๊ณ  ๋‚˜์„œ ์ฝ”๋“œ๋ฅผ ์ „๋ถ€ ๊ฐˆ์•„์—Ž์—ˆ๋Š”๋ฐ, 50์ค„์ด 4์ค„๋กœ ์ค„์—ˆ์–ด. ์ง„์งœ์•ผ. ๊ฒŒ๋‹ค๊ฐ€ ๋™์ ์œผ๋กœ ์Šคํ„ฐ๋””๊ฐ€ ์ถ”๊ฐ€๋ผ๋„ ์ฝ”๋“œ ์ˆ˜์ • ์—†์ด ๋™์ž‘ํ•œ๋‹ค๋Š” ๊ฒŒ ๋„ˆ๋ฌด ์‹ ๊ธฐํ–ˆ์–ด.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ์ƒํƒœ๋Š” ๋ฏธ๋ฆฌ ์„ ์–ธํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ํ•„์š”ํ•  ๋•Œ ๋™์ ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค."

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์–˜๊ธฐ๋„ ์ฒ˜์Œ์—” "์„ค๋งˆ ๊ทธ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ๋˜๊ฒ ์–ด์š”?" ์‹ถ์—ˆ๋Š”๋ฐ, ๋ฌดํ•œ ์Šคํฌ๋กค์—์„œ ํŽ˜์ด์ง€๊ฐ€ ์Œ“์ผ์ˆ˜๋ก ์บ์‹œ๊ฐ€ ๋ฌด์ œํ•œ์œผ๋กœ ๋Š˜์–ด๋‚œ๋‹ค๋Š” ๊ฑฐ ๋“ค์œผ๋‹ˆ๊นŒ ๋ฌด์„ญ๋”๋ผ๊ณ . remove() ์ฑ™๊ธฐ๋Š” ๊ฑฐ ์žŠ์ง€ ๋ง์•„์•ผ์ง€.

ํ‡ด๊ทผํ•˜๊ณ  ํŽธ์˜์ ์—์„œ ๋งฅ์ฃผ ํ•œ ์บ” ์‚ฌ์•ผ๊ฒ ๋‹ค. ์˜ค๋Š˜ ๋ญ”๊ฐ€ ๋งŽ์ด ๋‚ ๋ ธ๋‹ค ์ƒˆ๋กœ ์ผ๋‹ค ํ•ด์„œ ์ง„์ด ์ข€ ๋น ์ง„ ๊ฒƒ ๊ฐ™์•„. ๊ทธ๋ž˜๋„ ์ฝ”๋“œ๋Š” ํ›จ์”ฌ ๊น”๋”ํ•ด์กŒ์œผ๋‹ˆ๊นŒ.


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ