๐ŸŽจ 09. ๋ Œ๋”๋ง ์ตœ์ ํ™” โ€” atom ๊ตฌ๋…์„ ์ชผ๊ฐœ๋Š” ๊ธฐ์ˆ 

๐Ÿ“‹ ๊ฐœ์š”

useAtomValue ๋ถ„๋ฆฌ, selectAtom vs focusAtom vs splitAtom ๋น„๊ต, ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ์ „๋žต์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

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

  • ํฐ atom ์„ ๊ตฌ๋…ํ•  ๋•Œ ์™œ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์ด ์ƒ๊ธฐ๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • selectAtom, focusAtom, splitAtom ์˜ ์ฐจ์ด์™€ ์–ธ์ œ ๊ฐ๊ฐ์„ ์“ฐ๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ์ „๋žต์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง ๋ฒ”์œ„๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ์„ค๊ณ„๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

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

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: ์˜์ˆ™ ๋‹˜์˜ UX ํ”ผ๋“œ๋ฐฑ

์–ด๋А ๋ชฉ์š”์ผ ์˜คํ›„, ์˜์ˆ™ ๋‹˜์ด ๋””์ž์ธ ๊ฒ€ํ†  ์ฑ„๋„์— ๋ฉ”์‹œ์ง€๋ฅผ ๋‚จ๊ฒผ์–ด:

  • ๐ŸŽจ ์˜์ˆ™ ๋‹˜ (๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜, ์Šคํ„ฐ๋”” ์นด๋“œ์—์„œ ์ข‹์•„์š” ๋ฒ„ํŠผ ํ•˜๋‚˜ ๋ˆ„๋ฅด๋ฉด ์นด๋“œ ๋ชฉ๋ก ์ „์ฒด๊ฐ€ ๊นœ๋นก์ด๋Š” ๊ฑฐ ๋งž์•„์š”? ์Šคํฌ๋กค ์œ„์น˜๊นŒ์ง€ ํŠ€์–ด์š”. ์ด๊ฑฐ ๋ฒ„๊ทธ ์•„๋‹Œ๊ฐ€์š”?"
  • ๐Ÿฃ ์˜์ฒ : (๋‹นํ™ฉ) "์•„... ์›๋ž˜ ์ด๋Ÿฌ์ง„ ์•Š์•˜๋Š”๋ฐ์š”? ์ž ๊น๋งŒ์š”."
  • ๐Ÿฃ ์˜์ฒ : (์ฝ”๋“œ ์—ด์–ด๋ณด๊ณ ) "์•„ studyListAtom ์— ์Šคํ„ฐ๋”” ์ „์ฒด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐฐ์—ด๋กœ ์žˆ๋Š”๋ฐ, ์ข‹์•„์š” ์ˆ˜ ํ•˜๋‚˜ ๋ฐ”๋€Œ๋ฉด ๋ฐฐ์—ด ์ „์ฒด๊ฐ€ ์ƒˆ ์ฐธ์กฐ๊ฐ€ ๋˜๋‹ˆ๊นŒ ์ „์ฒด ๋ชฉ๋ก์ด ๋ฆฌ๋ Œ๋”๋˜๋Š” ๊ฑฐ๋„ค์š”..."
  • ๐Ÿฆ ์˜ํ˜ธ ๋‹˜: "๊ทธ๊ฑฐ์˜ˆ์š”. splitAtom ์ด๋‚˜ selectAtom ์œผ๋กœ ์ชผ๊ฐœ๋ฉด ํ•ด๋‹น ์นด๋“œ๋งŒ ๋ฆฌ๋ Œ๋”๋ผ์š”. ๊ฐ™์ด ๋ณผ๊นŒ์š”?"

๐Ÿค” ์™œ ๋ Œ๋”๋ง ์ตœ์ ํ™”๋ฅผ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€? ๐ŸŸข

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

  • atom ์ด ๋ฐ”๋€” ๋•Œ ๋ฆฌ๋ Œ๋”๋ง์ด ์ „ํŒŒ๋˜๋Š” ๋ฒ”์œ„๋ฅผ ์ดํ•ดํ•œ๋‹ค
  • ์–ธ์ œ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•˜๊ณ  ์–ธ์ œ ์˜ค๋ฒ„ ์—”์ง€๋‹ˆ์–ด๋ง์ด ๋˜๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์Šคํ„ฐ๋”” ๋ชฉ๋ก์— ์นด๋“œ๊ฐ€ 100๊ฐœ ์žˆ์–ด. ์ฒซ ๋ฒˆ์งธ ์นด๋“œ์˜ ์ข‹์•„์š” ์ˆ˜๋งŒ ๋ฐ”๋€Œ์—ˆ์–ด. ๋ช‡ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋  ๊ฒƒ ๊ฐ™์•„?

์˜์ˆ™ ๋‹˜์˜ ํ”ผ๋“œ๋ฐฑ์ด ์ •ํ™•ํ–ˆ์–ด. Jotai ๋Š” Context ๋ณด๋‹ค ํ›จ์”ฌ ์„ ํƒ์ ์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€๋งŒ, atom ์„ ์ž˜๋ชป ์„ค๊ณ„ํ•˜๋ฉด ์—ฌ์ „ํžˆ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๊ฐ€ ๋ฐœ์ƒํ•ด.

๋ฌธ์ œ ์ƒํ™ฉ:
studyListAtom = [ study1, study2, ..., study100 ]

study1.likeCount ๊ฐ€ ๋ฐ”๋€Œ๋ฉด:
  โ†’ studyListAtom ์ „์ฒด ๋ฐฐ์—ด ์ฐธ์กฐ ๋ณ€๊ฒฝ
  โ†’ studyListAtom ์„ ๊ตฌ๋…ํ•˜๋Š” StudyList ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋”
  โ†’ StudyList ๊ฐ€ ๋ Œ๋”ํ•˜๋Š” 100๊ฐœ์˜ StudyCard ๋ชจ๋‘ ๋ฆฌ๋ Œ๋”

์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ:
  โ†’ study1.likeCount ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” StudyCard[0] ๋งŒ ๋ฆฌ๋ Œ๋”

Jotai ๊ณต์‹ ์„ฑ๋Šฅ ๊ฐ€์ด๋“œ๋Š” ์ด๋ ‡๊ฒŒ ๋งํ•ด:

"Jotai ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ atomic ํ•˜๊ฒŒ ์ชผ๊ฐœ๋„๋ก ๊ถŒ์žฅํ•œ๋‹ค. ๊ฐ atom ์€ ๋…๋ฆฝ์ ์œผ๋กœ ์ €์žฅ๋˜๊ณ  ์ž์‹ ์˜ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค."

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"ํฐ atom ํ•˜๋‚˜ = ํŒฌ๋ค ์นดํŽ˜ ๋‹จ์ฒด ์•Œ๋ฆผ". ๊ณต์ง€ ํ•˜๋‚˜ ์˜ฌ๋ผ์˜ค๋ฉด ์นดํŽ˜ ํšŒ์› ์ „์ฒด๊ฐ€ ์•Œ๋ฆผ ๋ฐ›์•„. atom ์„ ์ชผ๊ฐค์ˆ˜๋ก ์•Œ๋ฆผ์ด ์ •ํ™•ํ•ด์ ธ.


๐Ÿ” ๋ฆฌ๋ Œ๋”๋ง ์›์ธ ๋ถ„์„ โ€” ํฐ atom ์˜ ํ•จ์ • ๐ŸŸข

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

  • ๊ฐ์ฒด๋‚˜ ๋ฐฐ์—ด atom ์—์„œ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ๋ฐ”๋€Œ์–ด๋„ ์ „์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋˜๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
// ๐Ÿฃ ์˜์ฒ ์ด ์ฒ˜์Œ์— ์ง  ๊ตฌ์กฐ โ€” ์Šคํ„ฐ๋”” ํ•˜๋‚˜์˜ ๋ชจ๋“  ์ •๋ณด๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด atom ์œผ๋กœ
interface Study {
  id: string
  title: string
  description: string
  category: string
  likeCount: number   // ์ž์ฃผ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ
  memberCount: number // ๊ฐ€๋” ๋ฐ”๋€Œ๋Š” ํ•„๋“œ
  tags: string[]      // ๊ฑฐ์˜ ์•ˆ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ
}
 
// ๐Ÿฃ ์˜์ฒ : "์ผ๋‹จ ๋‹ค ๋„ฃ์–ด๋’€์–ด์š”. ํŽธํ•˜๊ฒŒ ์“ฐ๋ ค๊ณ ์š”."
const studyAtom = atom<Study>({
  id: 'study-1',
  title: 'React ์‹ฌํ™” ์Šคํ„ฐ๋””',
  description: '๋ฆฌ๋ Œ๋”๋ง ์ตœ์ ํ™” ์ง‘์ค‘ ํƒ๊ตฌ',
  category: 'frontend',
  likeCount: 42,
  memberCount: 8,
  tags: ['react', 'jotai', 'typescript'],
})
 
// ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ด ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ตฌ๋…
const StudyCard = () => {
  // ๐Ÿฃ ์˜์ฒ : "์ด๋ ‡๊ฒŒ ํ•˜๋ฉด studyAtom ์˜ ์–ด๋–ค ํ•„๋“œ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋จ"
  const study = useAtomValue(studyAtom)
  //                          โ†‘
  // likeCount ๋งŒ ๋ฐ”๋€Œ์–ด๋„ title, description, tags ๋“ฑ์„ ํ‘œ์‹œํ•˜๋Š” ๋ถ€๋ถ„๊นŒ์ง€ ์ „๋ถ€ ๋ฆฌ๋ Œ๋”!
 
  return (
    <div>
      <h2>{study.title}</h2>         {/* likeCount ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์—ฌ๊ธฐ๋„ ๋ฆฌ๋ Œ๋” */}
      <p>{study.description}</p>    {/* likeCount ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์—ฌ๊ธฐ๋„ ๋ฆฌ๋ Œ๋” */}
      <span>โค๏ธ {study.likeCount}</span>
      <span>๐Ÿ‘ฅ {study.memberCount}</span>
    </div>
  )
}
๋ฌธ์ œ:
likeCount: 42 โ†’ 43 ์œผ๋กœ ๋ณ€๊ฒฝ
  โ†’ studyAtom ์˜ value ๊ฐ€ ์ƒˆ ๊ฐ์ฒด๋กœ ๊ต์ฒด
  โ†’ StudyCard ์ „์ฒด ๋ฆฌ๋ Œ๋”
  โ†’ title, description, tags ๋Š” ๋ณ€ํ•˜์ง€ ์•Š์•˜๋Š”๋ฐ๋„ React ๊ฐ€ ๋‹ค์‹œ ๊ณ„์‚ฐ

์ด๊ฑด ๋‚ญ๋น„์•ผ. 100๊ฐœ ์นด๋“œ๋ฉด 100๋ฐฐ ๋‚ญ๋น„.

โœ‚๏ธ ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ์ „๋žต โ€” ๊ฐ€์žฅ ๋จผ์ € ์‹œ๋„ํ•  ๊ฒƒ ๐ŸŸข

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

  • ํฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์€ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ๋ฆฌ๋ Œ๋” ๋ฒ”์œ„๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์ดํ•ดํ•œ๋‹ค
  • useAtomValue ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์™œ ํšจ๊ณผ์ ์ธ์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค

Jotai ๊ณต์‹ ์„ฑ๋Šฅ ๊ฐ€์ด๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ์กฐ์–ธ์€ "์ž‘์€ ์ปดํฌ๋„ŒํŠธ" ์•ผ. ๋ณต์žกํ•œ ๋„๊ตฌ๋ฅผ ์“ฐ๊ธฐ ์ „์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ชผ๊ฐœ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ํฐ ํšจ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด.

// โœ… Before / After โ€” ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ๋งŒ์œผ๋กœ ์ตœ์ ํ™”
 
// โŒ Before โ€” ํ•˜๋‚˜์˜ ํฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‘ atom ์„ ๋ชจ๋‘ ๊ตฌ๋…
// ๐Ÿฃ ์˜์ฒ : "์ด๋ ‡๊ฒŒ ํ•˜๋ฉด likeCount ๊ฐ€ ๋ฐ”๋€” ๋•Œ title ๊นŒ์ง€ ๋ฆฌ๋ Œ๋”๋ผ์š”"
const StudyCardBig = () => {
  const title = useAtomValue(studyTitleAtom)      // ๊ฑฐ์˜ ์•ˆ ๋ฐ”๋€œ
  const likeCount = useAtomValue(studyLikeAtom)   // ์ž์ฃผ ๋ฐ”๋€œ
  return (
    <div>
      <h2>{title}</h2>          {/* likeCount ๋ณ€๊ฒฝ ์‹œ ์—ฌ๊ธฐ๋„ ๋ฆฌ๋ Œ๋” */}
      <LikeButton count={likeCount} />
    </div>
  )
}
// โœ… After โ€” ์ž์ฃผ ๋ฐ”๋€Œ๋Š” ๋ถ€๋ถ„๋งŒ ๋ณ„๋„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌ
// ๐Ÿฆ ์˜ํ˜ธ: "title ๊ณผ likeCount ๋Š” ๋ณ€๊ฒฝ ๋นˆ๋„๊ฐ€ ๋‹ฌ๋ผ์š”. ์ชผ๊ฐœ๋Š” ๊ฒŒ ๋งž์•„์š”."
 
const StudyTitle = () => {
  const title = useAtomValue(studyTitleAtom) // title ์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”
  return <h2>{title}</h2>
}
 
const StudyLikeButton = () => {
  const [likeCount, setLikeCount] = useAtom(studyLikeAtom) // likeCount ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”
  return (
    <button onClick={() => setLikeCount((c) => c + 1)}>
      โค๏ธ {likeCount}
    </button>
  )
}
 
// StudyCard ๋Š” ์ด์ œ ๋‘ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐ๋ฆฝ๋งŒ ํ•จ โ€” ์ง์ ‘ atom ๊ตฌ๋… ์—†์Œ
const StudyCard = () => (
  <div>
    <StudyTitle />
    <StudyLikeButton />
  </div>
)
// likeCount ๋ณ€๊ฒฝ โ†’ StudyLikeButton ๋งŒ ๋ฆฌ๋ Œ๋” โœ…

๐Ÿ’ก ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ์›์น™
"๋ณ€๊ฒฝ ๋นˆ๋„๊ฐ€ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ์— ๋‘์ง€ ๋งˆ๋ผ."
์ž์ฃผ ๋ฐ”๋€Œ๋Š” ๋ถ€๋ถ„๊ณผ ๊ฑฐ์˜ ์•ˆ ๋ฐ”๋€Œ๋Š” ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌํ•˜๋ฉด, ์“ธ๋ฐ์—†๋Š” ๋ฆฌ๋ Œ๋”๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ง‰์„ ์ˆ˜ ์žˆ์–ด.


๐Ÿ”ญ selectAtom โ€” ์ฝ๊ธฐ ์ „์šฉ ์Šฌ๋ผ์ด์Šค ๐ŸŸก

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

  • selectAtom ์œผ๋กœ ํฐ atom ์˜ ํŠน์ • ํ•„๋“œ๋งŒ ๊ตฌ๋…ํ•˜๋Š” ํŒŒ์ƒ atom ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค
  • equalityFn ์„ ํ™œ์šฉํ•ด์„œ ์ฐธ์กฐ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ๊ฐ’์ด ๊ฐ™์œผ๋ฉด ๋ฆฌ๋ Œ๋”๋ฅผ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‹ค

์ปดํฌ๋„ŒํŠธ๋ฅผ ์ชผ๊ฐœ๊ธฐ ์–ด๋ ค์šด ์ƒํ™ฉ์—์„œ๋Š” selectAtom ์œผ๋กœ ํฐ atom ์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋ฝ‘์•„๋‚ด๋Š” "์ฝ๊ธฐ ์ „์šฉ ํŒŒ์ƒ atom" ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.

โš ๏ธ ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค ์ฃผ์˜์‚ฌํ•ญ: selectAtom ์€ "escape hatch" (ํƒˆ์ถœ๊ตฌ)๋กœ ์ œ๊ณต๋ผ. ์ˆœ์ˆ˜ atom ๋ชจ๋ธ(derived atom) ์„ ์šฐ์„ ํ•˜๊ณ , equalityFn ์ด๋‚˜ prevSlice ๊ฐ€ ๊ผญ ํ•„์š”ํ•  ๋•Œ๋งŒ selectAtom ์„ ์จ.

import { selectAtom } from 'jotai/utils'
 
// ์˜์ˆ˜๋„ค ์•ฑ์˜ ์Šคํ„ฐ๋”” ์ƒ์„ธ atom (์ „์ฒด ์ •๋ณด)
const studyDetailAtom = atom<Study>({
  id: 'study-1',
  title: 'React ์‹ฌํ™” ์Šคํ„ฐ๋””',
  description: '๋ฆฌ๋ Œ๋”๋ง ์ตœ์ ํ™” ์ง‘์ค‘ ํƒ๊ตฌ',
  likeCount: 42,
  tags: ['react', 'jotai'],
})
 
// ๐Ÿฆ ์˜ํ˜ธ: "selectAtom ์œผ๋กœ title ๋งŒ ๊ตฌ๋…ํ•˜๋Š” ํŒŒ์ƒ atom ์„ ๋งŒ๋“ค์–ด์š”"
// title ์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๊ตฌ๋… ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋” โ€” likeCount ๋ณ€๊ฒฝ์—๋Š” ๋ฐ˜์‘ ์•ˆ ํ•จ
const studyTitleAtom = selectAtom(studyDetailAtom, (study) => study.title)
 
// likeCount ๋งŒ ๊ตฌ๋…ํ•˜๋Š” ํŒŒ์ƒ atom
const studyLikeCountAtom = selectAtom(studyDetailAtom, (study) => study.likeCount)
 
// ์‚ฌ์šฉ
const StudyTitle = () => {
  const title = useAtomValue(studyTitleAtom) // title ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ๋ฆฌ๋ Œ๋”
  return <h2>{title}</h2>
}
 
const StudyLikeDisplay = () => {
  const likeCount = useAtomValue(studyLikeCountAtom) // likeCount ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ๋ฆฌ๋ Œ๋”
  return <span>โค๏ธ {likeCount}</span>
}

equalityFn โ€” ์ฐธ์กฐ๊ฐ€ ๋‹ฌ๋ผ๋„ ๊ฐ’์ด ๊ฐ™์œผ๋ฉด ๋ฆฌ๋ Œ๋” ๋ง‰๊ธฐ

๋ฐฐ์—ด์ด๋‚˜ ๊ฐ์ฒด ์Šฌ๋ผ์ด์Šค๋ฅผ ์„ ํƒํ•  ๋•Œ, ๋งค ๋ Œ๋”๋งˆ๋‹ค ์ƒˆ ์ฐธ์กฐ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด selectAtom ์˜ ๊ธฐ๋ณธ ์ฐธ์กฐ ๋น„๊ต(Object.is)๊ฐ€ ํ•ญ์ƒ ๋‹ค๋ฅด๋‹ค๊ณ  ํŒ๋‹จํ•ด์„œ ๋ฆฌ๋ Œ๋”๊ฐ€ ๋ฐœ์ƒํ•ด.

import { selectAtom } from 'jotai/utils'
import deepEqual from 'fast-deep-equal' // ๋˜๋Š” lodash.isequal
 
// ๐Ÿฃ ์˜์ฒ : "tags ๋ฐฐ์—ด์„ ์„ ํƒํ–ˆ๋Š”๋ฐ ์™œ ๊ณ„์† ๋ฆฌ๋ Œ๋”๋ผ์š”?"
// ๐Ÿฆ ์˜ํ˜ธ: "๋ฐฐ์—ด์€ ๋งค๋ฒˆ ์ƒˆ ์ฐธ์กฐ๊ฐ€ ์ƒ๊ฒจ์š”. equalityFn ์„ ์ฃผ๋ฉด ๊ฐ’ ๋น„๊ต๋ฅผ ํ•ด์š”."
 
// โŒ tags ๊ฐ€ ๊ฐ™์€ ๋‚ด์šฉ์ด์–ด๋„ ์ฐธ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋ฆฌ๋ Œ๋”๋จ
const tagsAtomBad = selectAtom(studyDetailAtom, (study) => study.tags)
 
// โœ… ๊นŠ์€ ๋น„๊ต๋กœ ์‹ค์ œ ๋‚ด์šฉ์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”
const tagsAtom = selectAtom(
  studyDetailAtom,
  (study) => study.tags,
  deepEqual, // equalityFn: ๊ฐ’์ด ๊ฐ™์œผ๋ฉด ๋ฆฌ๋ Œ๋” ์Šคํ‚ต
)

์•ˆ์ •์ ์ธ selector ํ•จ์ˆ˜ โ€” ๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€

// โš ๏ธ ์ค‘์š”: selectAtom ์„ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋” ํ•จ์ˆ˜ ์•ˆ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด ๋ฌดํ•œ ๋ฃจํ”„!
const Component = () => {
  // โŒ ๋ Œ๋”๋งˆ๋‹ค ์ƒˆ selectAtom ํ˜ธ์ถœ โ†’ ์ƒˆ atom config โ†’ ์ƒˆ ๊ตฌ๋… โ†’ ๋ฌดํ•œ ๋ฃจํ”„
  const [title] = useAtom(selectAtom(studyDetailAtom, (s) => s.title))
}
 
// โœ… ํ•ด๊ฒฐ์ฑ… 1: ๋ชจ๋“ˆ ์ตœ์ƒ๋‹จ์— ์„ ์–ธ (๊ถŒ์žฅ)
const titleAtom = selectAtom(studyDetailAtom, (s) => s.title)
 
// โœ… ํ•ด๊ฒฐ์ฑ… 2: useMemo ๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
const Component = () => {
  const titleAtom = useMemo(
    () => selectAtom(studyDetailAtom, (s) => s.title),
    [] // ์˜์กด์„ฑ ์—†์œผ๋ฉด ๋นˆ ๋ฐฐ์—ด
  )
  const title = useAtomValue(titleAtom)
}
 
// โœ… ํ•ด๊ฒฐ์ฑ… 3: useCallback ์œผ๋กœ selector ํ•จ์ˆ˜ ์•ˆ์ •ํ™”
const selector = useCallback((s: Study) => s.title, [])
const titleAtom = selectAtom(studyDetailAtom, selector)

๐Ÿ”ฌ focusAtom โ€” ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ๋ Œ์ฆˆ ๐Ÿ”ด

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

  • focusAtom ์ด selectAtom ๊ณผ ๋‹ค๋ฅด๊ฒŒ ์“ฐ๊ธฐ๋„ ๊ฐ€๋Šฅํ•œ ์ด์œ ๋ฅผ ์ดํ•ดํ•œ๋‹ค
  • ์–ธ์ œ jotai-optics ๋ฅผ ์„ค์น˜ํ•ด์„œ focusAtom ์„ ์“ฐ๋Š” ๊ฒŒ ์œ ๋ฆฌํ•œ์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค

selectAtom ์€ ์ฝ๊ธฐ ์ „์šฉ์ด์•ผ. ํŠน์ • ํ•„๋“œ๋ฅผ ์ฝ๊ณ  ์“ฐ๋Š” ๋ Œ์ฆˆ๊ฐ€ ํ•„์š”ํ•˜๋ฉด focusAtom ์„ ์จ.

npm install jotai-optics optics-ts
import { focusAtom } from 'jotai-optics'
 
interface StudyProfile {
  title: string
  description: string
  settings: {
    isPrivate: boolean
    maxMembers: number
  }
}
 
const studyProfileAtom = atom<StudyProfile>({
  title: 'React ์‹ฌํ™” ์Šคํ„ฐ๋””',
  description: '๋ฆฌ๋ Œ๋”๋ง ์ตœ์ ํ™” ์ง‘์ค‘ ํƒ๊ตฌ',
  settings: {
    isPrivate: false,
    maxMembers: 10,
  },
})
 
// settings ๋งŒ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ํŒŒ์ƒ atom
// ๐Ÿฆ ์˜ํ˜ธ: "focusAtom ์€ ๋ Œ์ฆˆ(lens)์•ผ. ํŠน์ • ๋ถ€๋ถ„์„ ํ™•๋Œ€ํ•ด์„œ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ์–ด์š”."
const settingsAtom = focusAtom(studyProfileAtom, (optic) => optic.prop('settings'))
 
// ๋” ๊นŠ์ด ๋“ค์–ด๊ฐˆ ์ˆ˜๋„ ์žˆ์Œ
const isPrivateAtom = focusAtom(studyProfileAtom, (optic) =>
  optic.prop('settings').prop('isPrivate')
)
 
// ์‚ฌ์šฉ ์˜ˆ์‹œ
const StudySettings = () => {
  // isPrivate ๋งŒ ๊ตฌ๋… โ€” title ์ด๋‚˜ description ์ด ๋ฐ”๋€Œ์–ด๋„ ๋ฆฌ๋ Œ๋” ์•ˆ ๋จ
  const [isPrivate, setIsPrivate] = useAtom(isPrivateAtom)
 
  return (
    <label>
      <input
        type="checkbox"
        checked={isPrivate}
        onChange={() => setIsPrivate((prev) => !prev)}
      />
      ๋น„๊ณต๊ฐœ ์Šคํ„ฐ๋””
    </label>
  )
}

selectAtom vs focusAtom ๋น„๊ต

// selectAtom โ€” ์ฝ๊ธฐ ์ „์šฉ
const titleAtom = selectAtom(studyAtom, (s) => s.title)
const title = useAtomValue(titleAtom) // ์ฝ๊ธฐ O
// setTitle(newTitle) // โŒ ์“ฐ๊ธฐ ๋ถˆ๊ฐ€
 
// focusAtom โ€” ์ฝ๊ธฐ + ์“ฐ๊ธฐ (๋ Œ์ฆˆ)
const titleAtom = focusAtom(studyAtom, (optic) => optic.prop('title'))
const [title, setTitle] = useAtom(titleAtom) // ์ฝ๊ธฐ O, ์“ฐ๊ธฐ O โœ…
// setTitle('์ƒˆ ์ œ๋ชฉ') โ†’ studyAtom ์˜ title ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ

๐Ÿ—‚๏ธ splitAtom โ€” ๋ฐฐ์—ด์„ ๊ฐœ๋ณ„ atom ์œผ๋กœ ๋ถ„๋ฆฌ ๐ŸŸก

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

  • splitAtom ์œผ๋กœ ๋ฐฐ์—ด atom ์„ ๊ฐœ๋ณ„ item atom ๋ฐฐ์—ด๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‹ค
  • ๋ฐฐ์—ด์˜ ํ•œ ํ•ญ๋ชฉ์ด ๋ฐ”๋€” ๋•Œ ํ•ด๋‹น ํ•ญ๋ชฉ์˜ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋˜๋Š” ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•œ๋‹ค

์˜์ˆ™ ๋‹˜์ด ๋ฐœ๊ฒฌํ•œ ๋ฒ„๊ทธ์˜ ํ•ต์‹ฌ์ด ์ด๊ฑฐ์•ผ. ์Šคํ„ฐ๋”” ๋ชฉ๋ก ๋ฐฐ์—ด์—์„œ ํ•œ ์นด๋“œ์˜ ์ข‹์•„์š”๋งŒ ๋ฐ”๋€Œ๋Š”๋ฐ ๋ชฉ๋ก ์ „์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋์–ด. splitAtom ์ด ํ•ด๊ฒฐ์‚ฌ์•ผ.

import { atom, useAtom, useAtomValue, PrimitiveAtom } from 'jotai'
import { splitAtom } from 'jotai/utils'
 
// ๋ฐฐ์—ด atom
const studyListAtom = atom<Study[]>([
  { id: '1', title: '์˜์ฒ ์ด์˜ React ์Šคํ„ฐ๋””', likeCount: 42 },
  { id: '2', title: '์˜ํ˜ธ ๋‹˜์˜ ์•„ํ‚คํ…์ฒ˜ ์Šคํ„ฐ๋””', likeCount: 100 },
  { id: '3', title: '์˜์ˆ˜ ๋‹˜์˜ ๋ฐฑ์—”๋“œ ์Šคํ„ฐ๋””', likeCount: 23 },
])
 
// splitAtom ์œผ๋กœ ๋ฐฐ์—ด์„ ๊ฐœ๋ณ„ item atom ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
// ๐Ÿฆ ์˜ํ˜ธ: "studyAtomsAtom ์€ [atom<Study>, atom<Study>, atom<Study>] ๋ฅผ ๋‹ด์€ atom ์ด์—์š”"
const studyAtomsAtom = splitAtom(studyListAtom)
// ๊ฐ ์Šคํ„ฐ๋”” ์นด๋“œ๊ฐ€ ์ž์‹ ์˜ atom ๋งŒ ๊ตฌ๋…
const StudyCard = ({ studyAtom }: { studyAtom: PrimitiveAtom<Study> }) => {
  // ๐Ÿฆ ์˜ํ˜ธ: "์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์ž๊ธฐ studyAtom ์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋ผ์š”"
  const [study, setStudy] = useAtom(studyAtom)
 
  const handleLike = () => {
    setStudy((prev) => ({ ...prev, likeCount: prev.likeCount + 1 }))
    // studyListAtom ์˜ ํ•ด๋‹น ํ•ญ๋ชฉ๋งŒ ์—…๋ฐ์ดํŠธ โ€” ๋‚˜๋จธ์ง€ ์นด๋“œ๋Š” ๋ฆฌ๋ Œ๋” ์•ˆ ๋จ!
  }
 
  return (
    <div>
      <h3>{study.title}</h3>
      <button onClick={handleLike}>โค๏ธ {study.likeCount}</button>
    </div>
  )
}
 
// ๋ชฉ๋ก ์ปดํฌ๋„ŒํŠธ โ€” splitAtom ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉ
const StudyList = () => {
  // ๐Ÿฃ ์˜์ฒ : "studyAtomsAtom ์€ atom ๋ฐฐ์—ด์„ ๋‹ด์€ atom ์ด์—์š”. ์‹ ๊ธฐํ•˜๋‹ค!"
  const [studyAtoms, dispatch] = useAtom(studyAtomsAtom)
 
  return (
    <ul>
      {studyAtoms.map((studyAtom) => (
        <StudyCard
          key={`${studyAtom}`} // atom ์ž์ฒด๋ฅผ key ๋กœ ์‚ฌ์šฉ
          studyAtom={studyAtom}
        />
      ))}
    </ul>
  )
}

splitAtom ์˜ dispatch ๋กœ ๋ชฉ๋ก ์ˆ˜์ •

// splitAtom ์€ dispatch ํ•จ์ˆ˜๋„ ๋ฐ˜ํ™˜ํ•ด โ€” remove, insert, move ์•ก์…˜ ์ง€์›
const StudyList = () => {
  const [studyAtoms, dispatch] = useAtom(studyAtomsAtom)
 
  return (
    <ul>
      {studyAtoms.map((studyAtom) => (
        <li key={`${studyAtom}`}>
          <StudyCard studyAtom={studyAtom} />
          {/* ๊ฐœ๋ณ„ ํ•ญ๋ชฉ ์‚ญ์ œ */}
          <button onClick={() => dispatch({ type: 'remove', atom: studyAtom })}>
            ์‚ญ์ œ
          </button>
        </li>
      ))}
      {/* ์ƒˆ ํ•ญ๋ชฉ ์ถ”๊ฐ€ */}
      <button onClick={() => dispatch({
        type: 'insert',
        value: { id: Date.now().toString(), title: '์ƒˆ ์Šคํ„ฐ๋””', likeCount: 0 },
      })}>
        ์Šคํ„ฐ๋”” ์ถ”๊ฐ€
      </button>
    </ul>
  )
}

keyExtractor โ€” ์•ˆ์ •์ ์ธ atom ๋งคํ•‘

// keyExtractor ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐฐ์—ด ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ atom ์ด ์žฌ์ƒ์„ฑ๋˜์ง€ ์•Š์•„
// ๐Ÿฆ ์˜ํ˜ธ: "id ๊ฐ€ ๊ฐ™์€ ํ•ญ๋ชฉ์€ ๊ฐ™์€ atom ์„ ์žฌ์‚ฌ์šฉํ•ด์š”. ์„ฑ๋Šฅ ํ–ฅ์ƒ!"
const studyAtomsAtom = splitAtom(
  studyListAtom,
  (study) => study.id, // keyExtractor โ€” ๊ณ ์œ  ์‹๋ณ„์ž
)

โš–๏ธ ์„ธ ๊ฐ€์ง€ ๋„๊ตฌ ์„ ํƒ ๊ธฐ์ค€ ๋น„๊ตํ‘œ ๐ŸŸก

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

  • ์ƒํ™ฉ์— ๋”ฐ๋ผ selectAtom, focusAtom, splitAtom ์ค‘ ์–ด๋–ค ๊ฒƒ์„ ์“ธ์ง€ ์ฆ‰์‹œ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค
๋„๊ตฌ์ฝ๊ธฐ์“ฐ๊ธฐ๋Œ€์ƒ์–ธ์ œ
derived atomโœ…โŒ๋‹จ์ผ ๊ฐ์ฒด ํ•„๋“œ๊ฐ€์žฅ ๊ธฐ๋ณธ, ์šฐ์„  ๊ณ ๋ ค
selectAtomโœ…โŒ๋‹จ์ผ ๊ฐ์ฒด ํ•„๋“œequalityFn ์ด ํ•„์š”ํ•  ๋•Œ
focusAtomโœ…โœ…๋‹จ์ผ ๊ฐ์ฒด ํ•„๋“œํŠน์ • ํ•„๋“œ๋ฅผ ์ฝ๊ณ  ์จ์•ผ ํ•  ๋•Œ
splitAtomโœ…โœ…๋ฐฐ์—ด ๊ฐ ํ•ญ๋ชฉ๋ฐฐ์—ด์˜ ๊ฐœ๋ณ„ ํ•ญ๋ชฉ์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ๋•Œ

์—…๋ฐ์ดํŠธ ๋นˆ๋„์— ๋”ฐ๋ฅธ ์„ ํƒ ๊ธฐ์ค€

์ž์ฃผ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ (e.g., likeCount, isOnline):
  โ†’ ๋ณ„๋„ atom ์œผ๋กœ ๋ถ„๋ฆฌ or splitAtom
  โ†’ focusAtom ์€ overhead ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Œ

๊ฐ€๋” ๋ฐ”๋€Œ๋Š” ํ•„๋“œ (e.g., description, tags):
  โ†’ selectAtom with equalityFn (deepEqual)
  โ†’ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋” ๋ฐฉ์ง€

๊ฑฐ์˜ ์•ˆ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ (e.g., id, createdAt):
  โ†’ ๊ตณ์ด ๋ถ„๋ฆฌ ์•ˆ ํ•ด๋„ ๋จ
  โ†’ ๋ถ„๋ฆฌํ•˜๋ฉด ์˜คํžˆ๋ ค ์ฝ”๋“œ ๋ณต์žก๋„๋งŒ ์˜ฌ๋ผ๊ฐ

๐Ÿ’ก Jotai ๊ณต์‹ ์„ฑ๋Šฅ ๊ฐ€์ด๋“œ์˜ ํ•ต์‹ฌ ์กฐ์–ธ
"์ž์ฃผ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ์™€ ๊ฑฐ์˜ ์•ˆ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณ€ํ•œ๋‹ค๋ฉด focusAtom ์ด๋‚˜ selectAtom ์„ ์จ์„œ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ฅผ ๋ง‰์•„๋ผ. ํ•„๋“œ๋“ค์ด ๋™์‹œ์— ๋ฐ”๋€๋‹ค๋ฉด ๋ถ„๋ฆฌํ•ด๋ดค์ž overhead ๋งŒ ์ƒ๊ธด๋‹ค."


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

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F ๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด. ๋Œ€๋ถ€๋ถ„ ์—ฌ๊ธฐ ์žˆ์–ด.


โŒ selectAtom ์‚ฌ์šฉ ์‹œ ๋ฌดํ•œ ๋ฃจํ”„

์ฆ์ƒ: ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ๊ณ„์† ๋ฆฌ๋ Œ๋”๋จ

์›์ธ: ๋ Œ๋” ํ•จ์ˆ˜ ์•ˆ์—์„œ selectAtom() ์„ ์ง์ ‘ ํ˜ธ์ถœ

// โŒ ๋ฌธ์ œ ์ฝ”๋“œ
const Component = () => {
  const [title] = useAtom(selectAtom(studyAtom, (s) => s.title)) // ๋ฌดํ•œ ๋ฃจํ”„!
}

ํ•ด๊ฒฐ์ฑ…:

// โœ… ๋ชจ๋“ˆ ์ตœ์ƒ๋‹จ์— ์„ ์–ธ
const titleAtom = selectAtom(studyAtom, (s) => s.title)
 
// โœ… ๋˜๋Š” useMemo
const Component = () => {
  const titleAtom = useMemo(() => selectAtom(studyAtom, (s) => s.title), [])
  const title = useAtomValue(titleAtom)
}

โŒ splitAtom ์—์„œ ํƒ€์ž… ์—๋Ÿฌ

์ฆ์ƒ: studyAtom ์˜ ํƒ€์ž…์ด PrimitiveAtom<Study> ๊ฐ€ ์•„๋‹ˆ๋ผ๊ณ  ์—๋Ÿฌ

ํ•ด๊ฒฐ์ฑ…:

import type { PrimitiveAtom } from 'jotai'
import { splitAtom } from 'jotai/utils'
 
// keyExtractor ๋ฅผ ์“ฐ๋ฉด ํƒ€์ž… ์ถ”๋ก ์ด ๋” ๋ช…ํ™•ํ•ด์ง
const studyAtomsAtom = splitAtom(studyListAtom, (s) => s.id)
 
const StudyCard = ({ studyAtom }: { studyAtom: PrimitiveAtom<Study> }) => {
  const [study] = useAtom(studyAtom)
  return <div>{study.title}</div>
}

โŒ focusAtom ์„ ์จ๋„ ๋ฆฌ๋ Œ๋”๊ฐ€ ์ค„์ง€ ์•Š์•„

์›์ธ: ์„ ํƒํ•œ ํ•„๋“œ๊ฐ€ ์‚ฌ์‹ค ์ž์ฃผ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ์—ฌ์„œ, ์ตœ์ ํ™” ํšจ๊ณผ๊ฐ€ ์—†๋Š” ์ƒํ™ฉ

ํ•ด๊ฒฐ์ฑ…: ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋‹ค์‹œ ์„ค๊ณ„ โ€” ์ž์ฃผ ๋ฐ”๋€Œ๋Š” ๋ฐ์ดํ„ฐ์™€ ๊ฑฐ์˜ ์•ˆ ๋ฐ”๋€Œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ณ„๋„ atom ์œผ๋กœ ๋ถ„๋ฆฌ


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

๐Ÿ“‹ ๋ Œ๋”๋ง ์ตœ์ ํ™” ๋„๊ตฌ ์š”์•ฝ

์ „๋žต๋ณต์žก๋„ํšจ๊ณผ์–ธ์ œ
atom ๋ถ„๋ฆฌ (์ฒ˜์Œ๋ถ€ํ„ฐ)๋‚ฎ์Œ๋†’์Œatom ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ
์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ๋‚ฎ์Œ๋†’์Œ๋ฆฌํŒฉํ† ๋ง 1์ˆœ์œ„
selectAtom์ค‘๊ฐ„์ค‘๊ฐ„ํฐ atom ์—์„œ ์ฝ๊ธฐ ์ „์šฉ ์Šฌ๋ผ์ด์Šค
focusAtom๋†’์Œ๋†’์Œ์ฝ๊ณ  ์“ฐ๋Š” ๋ Œ์ฆˆ๊ฐ€ ํ•„์š”ํ•  ๋•Œ
splitAtom์ค‘๊ฐ„๋†’์Œ๋ฐฐ์—ด ๋ชฉ๋ก ์ตœ์ ํ™”

โš ๏ธ ์ตœ์ ํ™” ํ•จ์ •

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆโœ… ์ข‹์€ ์˜ˆ
๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋ฌด์กฐ๊ฑด ๋ถ„๋ฆฌ๋ณ€๊ฒฝ ๋นˆ๋„ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ „๋ถ€ selectAtom์ž์ฃผ ๋ฐ”๋€Œ๋Š” ํ•„๋“œ๋งŒ ๋ถ„๋ฆฌ
๋ Œ๋” ํ•จ์ˆ˜ ๋‚ด selectAtomuseAtom(selectAtom(atom, fn))๋ชจ๋“ˆ ์ตœ์ƒ๋‹จ or useMemo
๋ฐฐ์—ด ๋ชฉ๋ก ์ตœ์ ํ™” ๋ฌด์‹œ๋ฐฐ์—ด ์ „์ฒด atom ํ•˜๋‚˜๋กœ ๊ด€๋ฆฌsplitAtom ์œผ๋กœ ๊ฐœ๋ณ„ atom ๋ถ„๋ฆฌ

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

Q1. ๐ŸŽจ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ (์˜์ˆ™์˜ UX ์ž”์†Œ๋ฆฌ)

์˜์ˆ™ ๋‹˜์ด "์Šคํ„ฐ๋”” ๋ชฉ๋ก์—์„œ ์ข‹์•„์š” ๋ฒ„ํŠผ ํ•˜๋‚˜ ๋ˆŒ๋ €์„ ๋•Œ ๋ชฉ๋ก ์ „์ฒด๊ฐ€ ๊นœ๋นก์ธ๋‹ค" ๊ณ  ํ”ผ๋“œ๋ฐฑํ–ˆ์–ด.
์˜์ฒ ์ด๊ฐ€ ํ™•์ธํ•˜๋‹ˆ studyListAtom = atom<Study[]>([...]) ํ•˜๋‚˜๋กœ ์ „์ฒด ๋ชฉ๋ก์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์—ˆ์–ด.
์˜ํ˜ธ ๋‹˜์ด ์ œ์•ˆํ•  ๊ฐ€์žฅ ์ง์ ‘์ ์ธ ํ•ด๊ฒฐ์ฑ…์€?

  • A) React.memo ๋กœ StudyCard ๋ฅผ ๊ฐ์‹ผ๋‹ค
  • B) splitAtom ์œผ๋กœ ๋ฐฐ์—ด์„ ๊ฐœ๋ณ„ item atom ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , ๊ฐ StudyCard ๊ฐ€ ์ž์‹ ์˜ atom ๋งŒ ๊ตฌ๋…ํ•˜๋„๋ก ํ•œ๋‹ค
  • C) useCallback ์œผ๋กœ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•œ๋‹ค
  • D) ์ข‹์•„์š” ์ˆ˜๋ฅผ ๋ณ„๋„ API ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์„œ๋ฒ„์—์„œ๋งŒ ๊ด€๋ฆฌํ•œ๋‹ค

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: studyListAtom ์ „์ฒด๋ฅผ ๊ตฌ๋…ํ•˜๋ฉด ๋ฐฐ์—ด์˜ ์–ด๋А ํ•ญ๋ชฉ์ด ๋ฐ”๋€Œ์–ด๋„ ์ƒˆ ๋ฐฐ์—ด ์ฐธ์กฐ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๊ตฌ๋… ์ปดํฌ๋„ŒํŠธ ์ „์ฒด๊ฐ€ ๋ฆฌ๋ Œ๋”๋ผ. splitAtom ์„ ์“ฐ๋ฉด ๋ฐฐ์—ด์ด [atom<Study>, atom<Study>, ...] ๋กœ ๋ณ€ํ™˜๋˜๊ณ , ๊ฐ StudyCard ๋Š” ์ž์‹ ์—๊ฒŒ ํ• ๋‹น๋œ atom ๋งŒ ๊ตฌ๋…ํ•ด. ํŠน์ • ์นด๋“œ์˜ likeCount ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ทธ ์นด๋“œ์˜ atom ๋งŒ ์—…๋ฐ์ดํŠธ๋˜๊ณ , ํ•ด๋‹น StudyCard ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋ผ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A ๋Š” React.memo ๊ฐ€ prop ๋น„๊ต๋ฅผ ํ•˜๋Š”๋ฐ, studyListAtom ์ „์ฒด๋ฅผ ๊ตฌ๋…ํ•˜๋ฉด prop ์ด ๋งค๋ฒˆ ์ƒˆ ๊ฐ์ฒด๋ผ ํšจ๊ณผ๊ฐ€ ์—†์–ด. C ๋Š” ์ด ๋ฌธ์ œ์™€ ๋ฌด๊ด€ํ•ด. D ๋Š” ์•„ํ‚คํ…์ฒ˜ ๊ณผ์ž‰์ด์•ผ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋ฐฐ์—ด ์ตœ์ ํ™” = splitAtom". ๋ชฉ๋ก ์•„์ดํ…œ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ทธ ์•„์ดํ…œ๋งŒ ๊นจ์›Œ.

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

๋ฉด์ ‘๊ด€์ด ๋ฌผ์—ˆ์–ด: "selectAtom ๊ณผ derived atom (๋‹จ์ˆœ ํŒŒ์ƒ atom) ์˜ ์ฐจ์ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? selectAtom ์€ ์–ธ์ œ ์‚ฌ์šฉํ•˜๋‚˜์š”?"

  • A) selectAtom ์€ ์“ฐ๊ธฐ๋„ ๊ฐ€๋Šฅํ•˜๊ณ , derived atom ์€ ์ฝ๊ธฐ ์ „์šฉ์ด๋‹ค
  • B) selectAtom ์€ equalityFn ์œผ๋กœ ์ปค์Šคํ…€ ๋™๋“ฑ์„ฑ ๋น„๊ต๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด์„œ, ๊นŠ์€ ๋น„๊ต๋‚˜ ์ด์ „ ๊ฐ’ ์ฐธ์กฐ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ๋‹จ์ˆœ ํŒŒ์ƒ์ด๋ฉด derived atom ์ด ๋” ๊ถŒ์žฅ๋œ๋‹ค
  • C) selectAtom ์ด derived atom ๋ณด๋‹ค ํ•ญ์ƒ ๋น ๋ฅด๋‹ค
  • D) selectAtom ์€ ๋ฐฐ์—ด ์ „์šฉ ์ตœ์ ํ™” ๋„๊ตฌ์ด๋‹ค

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค์—์„œ selectAtom ์€ "escape hatch" (ํƒˆ์ถœ๊ตฌ) ๋กœ ์ •์˜๋ผ. ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” atom((get) => get(baseAtom).title) ๊ฐ™์€ ์ˆœ์ˆ˜ derived atom ์„ ๊ถŒ์žฅํ•ด. selectAtom ์€ ๋‘ ๊ฐ€์ง€ ๊ฒฝ์šฐ์— ์œ ์šฉํ•ด: (1) equalityFn โ€” ๋ฐฐ์—ด/๊ฐ์ฒด ์Šฌ๋ผ์ด์Šค๋ฅผ ๊นŠ์€ ๋น„๊ต๋กœ ์•ˆ์ •ํ™”ํ•ด์•ผ ํ•  ๋•Œ, (2) prevSlice โ€” ์ด์ „ ์Šฌ๋ผ์ด์Šค ๊ฐ’์„ ์ฐธ์กฐํ•ด์„œ ๊ณ„์‚ฐํ•ด์•ผ ํ•  ๋•Œ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A ๋Š” ํ‹€๋ ค. selectAtom ๋„ ์ฝ๊ธฐ ์ „์šฉ์ด์•ผ. ์“ฐ๊ธฐ๊ฐ€ ํ•„์š”ํ•˜๋ฉด focusAtom. C ๋Š” ํ•ญ์ƒ ๋” ๋น ๋ฅธ ๊ฒŒ ์•„๋‹ˆ๋ผ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "selectAtom = ํƒˆ์ถœ๊ตฌ. ํ‰์†Œ์—” derived atom ์˜ ๋ฌธ์œผ๋กœ ๋“ค์–ด๊ฐ€๊ณ , ๊นŠ์€ ๋น„๊ต๊ฐ€ ํ•„์š”ํ•  ๋•Œ๋งŒ ํƒˆ์ถœ๊ตฌ๋ฅผ ์จ."

Q3. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„ (ํŒ€ ์ „์ฒด ํšŒ์˜)

์˜์ˆ˜ ๋‹˜์ด ํšŒ์˜์—์„œ ๋ฌผ์—ˆ์–ด: "์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ vs selectAtom ์ค‘ ๋ญ˜ ๋จผ์ € ์‹œ๋„ํ•ด์•ผ ํ•ด?"
์˜ํ˜ธ ๋‹˜์ด ํŒ€ ์ „์ฒด์— ์„ค๋ช…ํ–ˆ์–ด. ๊ฐ€์žฅ ์ ์ ˆํ•œ ๋‹ต๋ณ€์€?

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

"์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ๋ฅผ ๋จผ์ € ์‹œ๋„ํ•ด์•ผ ํ•ด์š”. ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘๊ฒŒ ์ชผ๊ฐœ๋ฉด ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์—๊ฒŒ ํ•„์š”ํ•œ atom ๋งŒ ๊ตฌ๋…ํ•˜๊ฒŒ ๋ผ์„œ, ๋ณต์žกํ•œ ๋„๊ตฌ ์—†์ด๋„ ๋ฆฌ๋ Œ๋” ๋ฒ”์œ„๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์–ด์š”. selectAtom ์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋” ์ชผ๊ฐœ๊ธฐ ์–ด๋ ค์šด ์ƒํ™ฉ์ด๊ฑฐ๋‚˜, ๋ฐฐ์—ด/๊ฐ์ฒด ์Šฌ๋ผ์ด์Šค์— equalityFn ์ด ํ•„์š”ํ•  ๋•Œ ๋„์ž…ํ•ด์š”. ๋จผ์ € ๋‹จ์ˆœํ•œ ๊ฒƒ๋ถ€ํ„ฐ, ํ•„์š”ํ•  ๋•Œ๋งŒ ๋ณต์žกํ•œ ๋„๊ตฌ๋ฅผ ์จ์•ผ ์ฝ”๋“œ๊ฐ€ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฌ์›Œ์š”."

๐Ÿ’ก ํ•ต์‹ฌ: "๋‹จ์ˆœํ•จ ์šฐ์„ " ์›์น™. ๋„๊ตฌ๊ฐ€ ๋งŽ๋‹ค๊ณ  ๋‹ค ์จ์•ผ ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ์•ผ.


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

์˜ค๋Š˜ ์˜์ˆ™ ๋‹˜ ๋•๋ถ„์— ์ง„์งœ ์ค‘์š”ํ•œ ๊ฑธ ๋ฐฐ์› ๋‹ค.

์‚ฌ์‹ค ์ฒ˜์Œ์—” "๋ญ, ์ข€ ๊นœ๋นก์ด๋Š” ๊ฒŒ ๋ญ ์–ด๋•Œ์„œ" ๋ผ๋Š” ์ƒ๊ฐ๋„ ์žˆ์—ˆ๋Š”๋ฐ, ์˜์ˆ™ ๋‹˜์ด "์Šคํฌ๋กค ์œ„์น˜๊นŒ์ง€ ํŠ€์–ด์š”" ๋ผ๊ณ  ํ•˜๋Š” ์ˆœ๊ฐ„ ์ง„์งœ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ์ƒ๊ฐํ•ด๋ณด๊ฒŒ ๋์–ด. ๋‚ด๊ฐ€ ์•ฑ ์“ฐ๋‹ค๊ฐ€ ๋ชฉ๋ก์ด ๊ณ„์† ๊นœ๋นก์ด๋ฉด ๋‚˜๋„ ํ™”๋‚˜๊ฒ ๋‹ค ์‹ถ์—ˆ๊ฑฐ๋“ .

splitAtom ์ ์šฉํ•˜๊ณ  React DevTools ๋กœ ํ™•์ธํ–ˆ๋”๋‹ˆ, ์ข‹์•„์š” ๋ˆ„๋ฅธ ์นด๋“œ๋งŒ ๋…น์ƒ‰์œผ๋กœ ๋น›๋‚˜๋”๋ผ. ๋‚˜๋จธ์ง€ ์นด๋“œ๋“ค์€ ์•„์˜ˆ ๋ฆฌ๋ Œ๋” ์•ˆ ๋์–ด. ๊ทธ๋•Œ ์ง„์งœ ๋ฟŒ๋“ฏํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  selectAtom ์ด "ํƒˆ์ถœ๊ตฌ" ๋ผ๋Š” ํ‘œํ˜„์ด ๋จธ๋ฆฟ์†์— ๋”ฑ ๋ฐ•ํ˜”์–ด. ํ‰์†Œ์—” derived atom ์“ฐ๊ณ , ๊นŠ์€ ๋น„๊ต ํ•„์š”ํ•  ๋•Œ๋งŒ selectAtom.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "ํฐ atom ์€ ํŒฌ๋ค ์นดํŽ˜ ๋‹จ์ฒด ์•Œ๋ฆผ์ด๋‹ค. ์ชผ๊ฐค์ˆ˜๋ก ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ๋งŒ ์•Œ๋ฆผ์ด ์˜จ๋‹ค."

์˜์ˆ™ ๋‹˜์ด ์˜ค๋Š˜ ์ˆ˜๊ณ ํ–ˆ๋‹ค๊ณ  ๋ฉ”์‹œ์ง€๋„ ๋ณด๋‚ด์คฌ์–ด. UX ํ”ผ๋“œ๋ฐฑ์„ ๋ฒ„๊ทธ๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€ ๋ง๊ณ  ๊ฐœ์„  ๊ธฐํšŒ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ผ๋Š” ์กฐ์–ธ๋„. ๋””์ž์ด๋„ˆ๋ž‘ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ด๋ ‡๊ฒŒ ์ž˜ ํ˜‘๋ ฅํ•˜๋ฉด ์ข‹์€ ๊ฑฐ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒ ๋‹ค ์‹ถ์—ˆ์–ด.

์˜ค๋Š˜์€ ํ‡ด๊ทผํ•˜๊ณ  ์น˜ํ‚จ ๋จน์–ด์•ผ์ง€. ์ง„์งœ ์˜ค๋Š˜ ์ง‘์ค‘ ์ž˜ ํ–ˆ์œผ๋‹ˆ๊นŒ. ๊ต์ดŒ ์ˆœ์‚ด์ด๋ƒ ๋ฟŒ๋งํด์ด๋ƒ... ์ด๊ฒƒ๋„ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•œ ์„ ํƒ์ธ๊ฐ€?


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