๐Ÿงฉ 01. Jotai ๋ฉ˜ํƒˆ ๋ชจ๋ธ โ€” "์ „์—ญ useState"๋ผ๋Š” ์˜คํ•ด์™€ ์ง„์‹ค

๐Ÿ“‹ ๊ฐœ์š”

atom์˜ ์ฒ ํ•™, Redux/Context/Zustand ๋น„๊ต, WeakMap ๊ธฐ๋ฐ˜ store ์›๋ฆฌ๋ฅผ ํ†ตํ•ด Jotai๊ฐ€ ์™œ ํƒ„์ƒํ–ˆ๋Š”์ง€ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค.

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

  • Jotai๊ฐ€ Redux, Zustand, React Context์™€ ๊ตฌ์กฐ์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • atom์ด "๊ฐ’"์ด ์•„๋‹ˆ๋ผ "๊ตฌ๋… ์ฑ„๋„์˜ ์„ค๊ณ„๋„"๋ผ๋Š” ๊ฐœ๋…์„ ์ดํ•ดํ•œ๋‹ค.
  • SSR ํ™˜๊ฒฝ(Next.js App Router)์—์„œ Provider-less ๋ชจ๋“œ๊ฐ€ ์™œ ์œ„ํ—˜ํ•œ์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


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

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

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

[์™œ ๊ธฐ์กด ๋ฐฉ๋ฒ•์ด ๋ถˆํŽธํ•œ๊ฐ€] โ†’ [atom์ด๋ผ๋Š” ์ƒˆ ๋‹จ์œ„] โ†’ [๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋น„๊ต] โ†’ [๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ] โ†’ [์‹ค๋ฌด ์ ์šฉ]

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • "Jotai๋ฅผ ์™œ ์“ฐ๋Š” ๊ฑฐ์•ผ?" ๋ผ๋Š” ์งˆ๋ฌธ์— ์ž์‹  ์žˆ๊ฒŒ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • Context ์˜ ๋ฆฌ๋ Œ๋”๋ง ๋ฌธ์ œ๋ฅผ Jotai ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • Provider-less ๋ชจ๋“œ๋ฅผ ์–ธ์ œ ์“ฐ๊ณ  ์–ธ์ œ ํ”ผํ•ด์•ผ ํ•˜๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค

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

์˜์ˆ˜๋„ค ํŒ€์€ "๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ์ปค๋ฎค๋‹ˆํ‹ฐ" ์•ฑ์„ Next.js App Router + TanStack Query + TypeScript ๋กœ ๊ฐœ๋ฐœ ์ค‘์ด์•ผ.

  • ์˜์ˆ˜ ๋‹˜ (PM/๋ฐฑ์—”๋“œ): "์ผ๋‹จ Context ๋กœ ์ „์—ญ ์ƒํƒœ ๋‹ค ๋•Œ๋ ค๋„ฃ์œผ๋ฉด ์•ˆ ๋ผ? ๋ฐฐํฌ๊ฐ€ ์ฝ”์•ž์ธ๋ฐ."
  • ์˜ํ˜ธ ๋‹˜ (ํ”„๋ก ํŠธ ๋ฆฌ๋“œ): "๊ทธ๋Ÿฌ๋ฉด ๊ฒ€์ƒ‰ ํ•„ํ„ฐ ํ•˜๋‚˜ ๋ฐ”๊ฟ€ ๋•Œ๋งˆ๋‹ค ์œ ์ € ์•„๋ฐ”ํƒ€๊นŒ์ง€ ๋ฐ˜๋ณตํ•ด์„œ ๊ทธ๋ ค์ง€๋ฉด์„œ ํ™”๋ฉด์ด ํˆญํˆญ ๋Š๊น๋‹ˆ๋‹ค. ๋ฐฐํฌ ํ›„์— ์„ฑ๋Šฅ ์ง€ํ‘œ๊ฐ€ ๋‹ค ๋ฐ•์‚ด ๋‚  ๊ฑฐ์˜ˆ์š”."
  • ๐Ÿฃ ์˜์ฒ : (์กฐ์‹ฌ์Šค๋Ÿฝ๊ฒŒ) "๊ทธ๋Ÿผ Context ๋ฅผ ์ƒํƒœ๋งˆ๋‹ค ์ชผ๊ฐœ๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€์š”? ๊ทธ๊ฒŒ ์‹ซ์œผ๋ฉด... Jotai ๊ฐ€ Context ๋ž‘ ๋ญ๊ฐ€ ๋‹ฌ๋ผ์š”? ์–ด์ฐจํ”ผ ์ „์—ญ ์ƒํƒœ ๊ณต์œ ํ•˜๋Š” ๊ฑฐ์ž–์•„์š”."
  • ๐Ÿฆ ์˜ํ˜ธ ๋‹˜: "Context ๋ฅผ ์ชผ๊ฐœ๋ฉด Provider ์ง€์˜ฅ์ด ํŽผ์ณ์ ธ์š”. Jotai ๊ฐ€ ๊ทธ ๋‘ ๋ฌธ์ œ๋ฅผ ๋™์‹œ์— ํ•ด๊ฒฐํ•˜๋Š”๋ฐ, ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ์ง€๊ฐ€ ๋”ฑ ์˜ค๋Š˜ ์ฃผ์ œ์˜ˆ์š”."

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

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

  • React Context ๊ฐ€ ์™œ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค
  • "Provider ์ง€์˜ฅ" ์ด ๋ฌด์—‡์ธ์ง€ ์•Œ๊ณ , Jotai ๊ฐ€ ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์˜์ˆ˜๋„ค ์•ฑ์—์„œ ๋กœ๊ทธ์ธ ์œ ์ € ์ •๋ณด, ๊ฒ€์ƒ‰ ํ•„ํ„ฐ, ๋ชจ๋‹ฌ ์—ด๋ฆผ ์—ฌ๋ถ€, ์„ ํƒ๋œ ์Šคํ„ฐ๋”” ID ๋ฅผ ๋ชจ๋‘ Context ํ•˜๋‚˜๋กœ ๊ด€๋ฆฌํ•œ๋‹ค๋ฉด ์–ด๋–ค ์ผ์ด ์ƒ๊ธธ๊นŒ?
"์ƒํƒœ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ทธ๊ฑธ ์“ฐ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋  ๊ฒƒ ๊ฐ™๋‹ค" ๊นŒ์ง€ ๋– ์˜ฌ๋ ธ์œผ๋ฉด ์ถฉ๋ถ„ํ•ด.

Context ์˜ ๋ฆฌ๋ Œ๋”๋ง ํญํƒ„

์˜์ฒ ์ด๊ฐ€ ์ฒ˜์Œ ์ง  ์ฝ”๋“œ์•ผ:

// ๐Ÿฃ ์˜์ฒ : "์ผ๋‹จ Context ํ•˜๋‚˜๋กœ ๋‹ค ๋„ฃ์ž! ๊ฐ„๋‹จํ•˜์ž–์•„"
interface AppState {
  user: User | null
  searchKeyword: string
  selectedFilter: FilterType
  isModalOpen: boolean
}
 
const AppContext = createContext<AppState>(null!)
 
export function AppProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState<AppState>({
    user: null,
    searchKeyword: '',
    selectedFilter: 'all',
    isModalOpen: false,
  })
  return <AppContext.Provider value={state}>{children}</AppContext.Provider>
}
 
// ๊ฒ€์ƒ‰์–ด๋งŒ ๋ฐ”๊ฟ”๋„... ์ด ์ปดํฌ๋„ŒํŠธ๋„ ๋ฆฌ๋ Œ๋”๋จ
const UserAvatar = () => {
  const { user } = useContext(AppContext) // user ๋Š” ๋ณ€ํ•˜์ง€ ์•Š์•˜๋Š”๋ฐ!
  return <img src={user?.avatar} alt="avatar" />
}

์˜ํ˜ธ ๋‹˜์˜ ์ฝ”๋“œ ๋ฆฌ๋ทฐ:

๐Ÿฆ ์˜ํ˜ธ: "์˜์ฒ  ๋‹˜, `searchKeyword` ๋งŒ ๋ฐ”๊ฟจ๋Š”๋ฐ `UserAvatar` ๊นŒ์ง€ ๋ฆฌ๋ Œ๋”๋˜๊ณ  ์žˆ์–ด์š”.
         Context ๋Š” ๊ฐ’ ํ•˜๋‚˜๋ผ๋„ ๋ฐ”๋€Œ๋ฉด Provider ํ•˜์œ„์˜ ๋ชจ๋“  ๊ตฌ๋…์ž๋ฅผ ๋ฆฌ๋ Œ๋”์‹œ์ผœ์š”.
         ์œ ์ € ์•„๋ฐ”ํƒ€๊ฐ€ ๊ฒ€์ƒ‰์–ด๋ž‘ ๋ฌด์Šจ ์ƒ๊ด€์ด์—์š”?"

Context ๋ฅผ ์ชผ๊ฐœ๋ฉด? โ€” Provider ์ง€์˜ฅ

๊ทธ๋ž˜์„œ Context ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด:

// ๐Ÿฃ ์˜์ฒ : "์ชผ๊ฐœ๋ฉด ๋˜๊ฒ ์ง€!" (5๊ฐœ ๋ถ„๋ฆฌ ํ›„...)
<UserContext.Provider value={userState}>
  <SearchContext.Provider value={searchState}>
    <FilterContext.Provider value={filterState}>
      <ModalContext.Provider value={modalState}>
        <NotificationContext.Provider value={notificationState}>
          {/* ์•ฑ์ด ์ปค์งˆ์ˆ˜๋ก ์ด ํ”ผ๋ผ๋ฏธ๋“œ๊ฐ€ ๋์—†์ด ๊นŠ์–ด์ง */}
          <App />
        </NotificationContext.Provider>
      </ModalContext.Provider>
    </FilterContext.Provider>
  </SearchContext.Provider>
</UserContext.Provider>

๐Ÿ“– ์šฉ์–ด: Provider ์ง€์˜ฅ(Provider Hell) โ€” Context ๋ฅผ ์ƒํƒœ๋ณ„๋กœ ๋ถ„๋ฆฌํ• ์ˆ˜๋ก <Provider> ์ค‘์ฒฉ์ด ๊นŠ์–ด์ง€๋Š” ์ƒํ™ฉ. ๊ธฐ๋Šฅ ์ถ”๊ฐ€๋งˆ๋‹ค Provider ๋ฅผ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด์„œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ ธ.

Jotai ๋Š” ์ด ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ๋™์‹œ์— ํ•ด๊ฒฐํ•ด:

  1. ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง ์ œ๊ฑฐ โ€” atom ์„ ๊ตฌ๋…ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋งŒ ์ •ํ™•ํžˆ ์—…๋ฐ์ดํŠธ
  2. Provider ์ง€์˜ฅ ํƒˆ์ถœ โ€” Provider ์—†์ด๋„ atom ์„ ์–ด๋””์„œ๋“  ์„ ์–ธํ•˜๊ณ  ๊ตฌ๋…
// โœ… ๐Ÿฆ ์˜ํ˜ธ: "Jotai ๋กœ ์ด๋ ‡๊ฒŒ ๋ฐ”๊ฟ”๋ณด์„ธ์š”"
import { atom, useAtom, useAtomValue } from 'jotai'
 
const userAtom = atom<User | null>(null)
const searchKeywordAtom = atom('')
const selectedFilterAtom = atom<FilterType>('all')
const isModalOpenAtom = atom(false)
 
// searchKeyword ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ๋ Œ๋” ์•ˆ ๋จ!
const UserAvatar = () => {
  const user = useAtomValue(userAtom) // userAtom ๋งŒ ๊ตฌ๋…
  return <img src={user?.avatar} alt="avatar" />
}
 
const SearchBar = () => {
  const [keyword, setKeyword] = useAtom(searchKeywordAtom) // searchKeywordAtom ๋งŒ ๊ตฌ๋…
  return <input value={keyword} onChange={(e) => setKeyword(e.target.value)} />
}

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Context ๋Š” "ํ•œ ๋ฐฉ์— ๋ฐฉ์†กํ•˜๋Š” ๋ผ๋””์˜ค"์•ผ. ์ฑ„๋„ ํ•˜๋‚˜๊ฐ€ ์‹ ํ˜ธ๋ฅผ ๋ฐ”๊พธ๋ฉด ๋ผ๋””์˜ค ์ „์ฒด๊ฐ€ ๊นจ์–ด๋‚˜.
Jotai atom ์€ "๊ฐœ์ธ ์•Œ๋ฆผ"์ด์•ผ. ๋‚ด๊ฐ€ ๊ตฌ๋…ํ•œ atom ๋งŒ ๋‚˜๋ฅผ ๊นจ์›Œ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ ๐ŸŸข

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

  • atom ์ด "๊ฐ’์„ ๋‹ด๋Š” ๊ทธ๋ฆ‡" ์ด ์•„๋‹ˆ๋ผ "๊ตฌ๋… ์ฑ„๋„์˜ ์„ค๊ณ„๋„" ์— ๊ฐ€๊น๋‹ค๋Š” ๊ฒƒ์„ ์ดํ•ดํ•œ๋‹ค
  • atom config ์™€ atom value ์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?
์นœ๊ตฌ๋“ค ๋‹ค ๊ฐ™์ด ์•„์ฃผ ์ปค๋‹ค๋ž€ ๋„ํ™”์ง€ ํ•˜๋‚˜์— ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฐ๋‹ค๊ณ  ์ƒ๊ฐํ•ด ๋ด. ํ•œ ๋ช…์ด ์ง€์šฐ๊ฐœ๋ฅผ ๋ง‰ ์“ฐ๋ฉด ์บ”๋ฒ„์Šค ์ „์ฒด๊ฐ€ ๋œ์ปน๊ฑฐ๋ ค์„œ ๋‹ค๋ฅธ ์นœ๊ตฌ๋“ค์˜ ๊ทธ๋ฆผ๋„ ์—‰๋ง์ด ๋˜๊ฒ ์ง€? (์ด๊ฒŒ ๊ธฐ์กด์˜ Context ๋ฐฉ์‹์ด์•ผ!)
ํ•˜์ง€๋งŒ Jotai๋Š” ๊ฐ์ž '์ž๊ธฐ๋งŒ์˜ ์ž‘์€ ์ „์šฉ ์Šค์ผ€์น˜๋ถ(atom)'์„ ๋ฐฉ๋งˆ๋‹ค ๊ฐ€์ง€๊ณ  ๊ทธ๋ฆฌ๋Š” ๊ฑฐ์•ผ. ๋‚ด ์Šค์ผ€์น˜๋ถ์„ ๋งˆ๊ตฌ ์ง€์šฐ๊ณ  ์ƒˆ๋กœ ๊ทธ๋ ค๋„ ์˜†๋ฐฉ ์นœ๊ตฌ์˜ ์Šค์ผ€์น˜๋ถ์€ ์ „ํ˜€ ํ”๋“ค๋ฆฌ์ง€ ์•Š๊ณ  ํ‰ํ™”๋กญ๋‹จ๋‹ค!

atom ์€ "๊ฐ’" ์ด ์•„๋‹ˆ๋ผ "์„ค๊ณ„๋„" ๋‹ค

์ด๊ฒŒ Jotai ๋ฅผ ์ดํ•ดํ•˜๋Š” ํ•ต์‹ฌ์ด์•ผ. atom() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ฐ’์„ ๋งŒ๋“œ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์„ค๊ณ„๋„(config)๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑฐ์•ผ.

import { atom } from 'jotai'
 
// ๐Ÿฃ ์˜์ฒ : "์ด๊ฒŒ ๊ฐ’ 0์„ ๋งŒ๋“œ๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€์š”?"
const studyCountAtom = atom(0)
// ๐Ÿฆ ์˜ํ˜ธ: "์•„๋‹ˆ์—์š”. ์ด๊ฑด { init: 0, read: ..., write: ... } ๊ฐ™์€ ์„ค๊ณ„๋„ ๊ฐ์ฒด์˜ˆ์š”.
//          ์‹ค์ œ ๊ฐ’(0)์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ useAtom ์œผ๋กœ ๊ตฌ๋…ํ•  ๋•Œ ๋น„๋กœ์†Œ store ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค."
 
const Component = () => {
  // ์ด ์ˆœ๊ฐ„ store ์— 0์ด ์ €์žฅ๋˜๊ณ , ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ตฌ๋…์ž๋กœ ๋“ฑ๋ก๋จ
  const [count, setCount] = useAtom(studyCountAtom)
  return <div>{count}</div>
}

๐Ÿ“– ์šฉ์–ด: atom config โ€” atom ์˜ ์ดˆ๊ธฐ๊ฐ’, ์ฝ๊ธฐ ํ•จ์ˆ˜, ์“ฐ๊ธฐ ํ•จ์ˆ˜๋ฅผ ๋‹ด์€ ๋ถˆ๋ณ€(immutable) ๊ฐ์ฒด. ์ปดํฌ๋„ŒํŠธ๊ฐ€ useAtom ์œผ๋กœ ๊ตฌ๋…ํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” store ์— ๊ฐ’์ด ์—†์–ด.

๊ตฌ๋… ์ปดํฌ๋„ŒํŠธ๋งŒ ์—…๋ฐ์ดํŠธ๋˜๋Š” ์ด์œ 

Jotai store (WeakMap ๊ธฐ๋ฐ˜)
  โ”œโ”€โ”€ studyCountAtom   โ†’ { value: 5,        listeners: [StudyCard, StudyCounter] }
  โ”œโ”€โ”€ searchKeywordAtom โ†’ { value: 'React',  listeners: [SearchBar] }
  โ””โ”€โ”€ userAtom          โ†’ { value: { name: '์˜์ฒ ' }, listeners: [Header, UserAvatar] }

searchKeyword ๋ณ€๊ฒฝ โ†’ SearchBar ๋งŒ ๋ฆฌ๋ Œ๋” โ†’ ๋‚˜๋จธ์ง€๋Š” ์กฐ์šฉ

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
atom ์€ "๋ ˆ์‹œํ”ผ" ์•ผ. ๋ ˆ์‹œํ”ผ๋ฅผ ๋งŒ๋“ ๋‹ค๊ณ  ์Œ์‹์ด ๋งŒ๋“ค์–ด์ง€๋Š” ๊ฒŒ ์•„๋‹ˆ๋“ฏ,
atom() ํ˜ธ์ถœ์ด ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ์•ผ. useAtom ์ด ํ˜ธ์ถœ๋  ๋•Œ ๋น„๋กœ์†Œ ๊ฐ’์ด ์ƒ๊ฒจ.


๐Ÿ” Redux / Context / Zustand / Jotai ๋น„๊ต ๐ŸŸก

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

  • 4๊ฐ€์ง€ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐฉ์‹์˜ ์ฒ ํ•™์  ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์–ด๋–ค ์ƒํ™ฉ์—์„œ Jotai ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒŒ ์œ ๋ฆฌํ•œ์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค

์ฒ ํ•™ ๋น„๊ต

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ƒํƒœ ๊ตฌ์กฐ๋ฐฉํ–ฅ๋น„์œ 
Redux๊ฑฐ๋Œ€ํ•œ ๋‹จ์ผ ๊ฐ์ฒด (Single Store)Top-down์ค‘์•™ ์ •๋ถ€ โ€” ๋ชจ๋“  ๋ฒ•์ด ์—ฌ๊ธฐ์„œ ๋‚˜์˜ด
ContextReact ํŠธ๋ฆฌ ๊ธฐ๋ฐ˜ (Subtree)Top-down๋™์‚ฌ๋ฌด์†Œ โ€” ๊ตฌ์—ญ๋ณ„๋กœ ์žˆ์ง€๋งŒ ๊ฒฐ๊ตญ ๊ณ„์ธต ๊ตฌ์กฐ
Zustand๋ชจ๋“ˆ ๋ ˆ๋ฒจ ์Šคํ† ์–ด (Module-first)Top-down๊ฐœ์ธ ์ฐฝ๊ณ  โ€” ํ•˜๋‚˜์˜ ํฐ ๋ฐฉ์— ๋‹ค ๋„ฃ์Œ
Jotai๋…๋ฆฝ atom ์˜ ์กฐํ•ฉ (Bottom-up)Bottom-up๋ ˆ๊ณ  ๋ธ”๋ก โ€” ์ž‘์€ ์กฐ๊ฐ๋“ค์„ ์กฐํ•ฉํ•ด์„œ ์•ฑ ์ƒํƒœ ๊ตฌ์„ฑ

์ฝ”๋“œ๋กœ ๋น„๊ต โ€” ์Šคํ„ฐ๋”” ์ข‹์•„์š” ๊ธฐ๋Šฅ

// โŒ Redux ๋ฐฉ์‹ โ€” ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„
// ๐Ÿฃ ์˜์ฒ : "์Šคํ„ฐ๋”” ์ข‹์•„์š” ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•˜๋Š”๋ฐ ํŒŒ์ผ์ด 3๊ฐœ๋‚˜ ํ•„์š”ํ•ด์š”...?"
const studySlice = createSlice({
  name: 'study',
  initialState: { likeCount: 0 },
  reducers: { increment: (state) => { state.likeCount += 1 } },
})
const likeCount = useSelector((state: RootState) => state.study.likeCount)
dispatch(studySlice.actions.increment())
// ๐ŸŸก Zustand ๋ฐฉ์‹ โ€” ํ•˜๋‚˜์˜ ์Šคํ† ์–ด ๊ฐ์ฒด
const useStudyStore = create<StudyStore>((set) => ({
  likeCount: 0,
  increment: () => set((s) => ({ likeCount: s.likeCount + 1 })),
}))
const { likeCount, increment } = useStudyStore()
// โœ… Jotai ๋ฐฉ์‹ โ€” useState ์™€ ๋™์ผํ•œ ์‚ฌ์šฉ๊ฐ
// ๐Ÿฆ ์˜ํ˜ธ: "์ด๊ฒŒ ์ „๋ถ€์˜ˆ์š”. ์„ ์–ธ ๋‘ ์ค„, ์‚ฌ์šฉ ํ•œ ์ค„."
const likeCountAtom = atom(0)
 
const LikeButton = () => {
  const [likeCount, setLikeCount] = useAtom(likeCountAtom)
  return (
    <button onClick={() => setLikeCount((prev) => prev + 1)}>
      โค๏ธ {likeCount}
    </button>
  )
}

์–ธ์ œ Jotai ๋ฅผ ์„ ํƒํ•˜๋Š”๊ฐ€?

์ƒํ™ฉ์ถ”์ฒœ
useState + useContext ํŒจํ„ด์„ ๋Œ€์ฒดํ•˜๊ณ  ์‹ถ๋‹คโœ… Jotai
Suspense ์™€ ๋น„๋™๊ธฐ ์ƒํƒœ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ์“ฐ๊ณ  ์‹ถ๋‹คโœ… Jotai
์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…์ด ์ค‘์š”ํ•˜๋‹คโœ… Jotai
Next.js App Router (SSR) ํ™˜๊ฒฝ์ด๋‹คโœ… Jotai (Provider ํ•„์ˆ˜)
Redux DevTools ๋ฅผ ๊ผญ ์จ์•ผ ํ•œ๋‹ค๐ŸŸก Zustand
์ „์—ญ ๊ฐ์ฒด ํ•˜๋‚˜๋กœ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๐ŸŸก Zustand

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
"Jotai ๋Š” Recoil ๊ณผ ๋น„์Šทํ•˜๊ณ , Zustand ๋Š” Redux ์™€ ๋น„์Šทํ•ด."
Bottom-up (์ž‘์€ ๊ฒƒ๋ถ€ํ„ฐ ์กฐํ•ฉ) vs Top-down (ํฐ ๊ฒƒ์„ ์ชผ๊ฐœ๊ธฐ) ์˜ ์ฒ ํ•™ ์ฐจ์ด์•ผ.


๐Ÿงฌ Jotai ๋‚ด๋ถ€ ๋™์ž‘ โ€” WeakMap Store ๐ŸŸก

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

  • Jotai store ๊ฐ€ WeakMap ๊ธฐ๋ฐ˜์ด์–ด์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์—†์ด ๋™์ž‘ํ•˜๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • atom ์˜ ๊ตฌ๋…/์•Œ๋ฆผ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์–ด๋–ป๊ฒŒ ์„ ํƒ์  ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
atom ์„ ๊ตฌ๋…ํ•˜๋˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount ๋˜๋ฉด, ๊ทธ atom ์˜ ๊ฐ’์€ ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?
"๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๋  ๊ฒƒ ๊ฐ™๋‹ค" ๊นŒ์ง€ ๋– ์˜ฌ๋ ธ์œผ๋ฉด ์ถฉ๋ถ„ํ•ด.

Jotai Store ์˜ ๊ตฌ์กฐ (๋‹จ์ˆœํ™”)

// Jotai ๋‚ด๋ถ€ ์˜์‚ฌ ์ฝ”๋“œ (์‹ค์ œ ์ฝ”๋“œ๋Š” ๋” ๋ณต์žกํ•ด)
type Store = {
  stateMap: WeakMap<Atom<unknown>, AtomState>
}
 
type AtomState = {
  value: unknown                // ํ˜„์žฌ ๊ฐ’
  deps: Set<Atom<unknown>>      // ์ด atom ์ด ์˜์กดํ•˜๋Š” atom ๋“ค (derived atom ์šฉ)
  listeners: Set<() => void>    // ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ํ˜ธ์ถœํ•  ๋ฆฌ์Šค๋„ˆ (๋ฆฌ๋ Œ๋” ํŠธ๋ฆฌ๊ฑฐ)
}

๊ฐ’์ด ๋ฐ”๋€” ๋•Œ ์ผ์–ด๋‚˜๋Š” ์ผ

setLikeCount(prev => prev + 1) ํ˜ธ์ถœ
  โ†“
store ์˜ likeCountAtom ๊ฐ’ ์—…๋ฐ์ดํŠธ
  โ†“
likeCountAtom.listeners ์ˆœํšŒ
  โ†“
๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฆฌ๋ Œ๋”๋ง ์Šค์ผ€์ค„๋ง (๋‚ด๋ถ€์ ์œผ๋กœ useReducer dispatch ํ˜ธ์ถœ)
  โ†“
likeCountAtom ์„ ๊ตฌ๋… ์ค‘์ธ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฆฌ๋ Œ๋”๋ง
  โ†“
๋‹ค๋ฅธ atom ์„ ๊ตฌ๋… ์ค‘์ธ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ทธ๋Œ€๋กœ

WeakMap ์„ ์“ฐ๋Š” ์ด์œ  โ€” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€

WeakMap ์˜ ํ•ต์‹ฌ ํŠน์„ฑ: ํ‚ค(key)์— ๋Œ€ํ•œ ๊ฐ•ํ•œ ์ฐธ์กฐ๊ฐ€ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜.

// StudyCard ๊ฐ€ unmount ๋˜๋ฉด likeCountAtom ์˜ ๊ตฌ๋…์ž ๋ชฉ๋ก์—์„œ ์ž๋™ ์ œ๊ฑฐ
// ๊ตฌ๋…์ž๊ฐ€ 0์ด ๋˜๋ฉด likeCountAtom ์˜ ๊ฐ’๋„ GC ๋Œ€์ƒ์ด ๋จ
const StudyCard = () => {
  const [likeCount] = useAtom(likeCountAtom) // ๊ตฌ๋… ์‹œ์ž‘
  // unmount ์‹œ โ†’ listeners ์—์„œ ์ œ๊ฑฐ โ†’ ์ฐธ์กฐ ์—†์œผ๋ฉด GC
  return <div>{likeCount}</div>
}

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Jotai store ๋Š” "์ž๋™ ์ •๋ฆฌ๋˜๋Š” ๊ตฌ๋… ๊ด€๋ฆฌ์†Œ" ์•ผ.
์—ด์‡ (atom config)๋ฅผ ๋ฐ˜๋‚ฉํ•˜๋ฉด(์ปดํฌ๋„ŒํŠธ unmount), ๊ทธ ๋ณด๊ด€ํ•จ(store entry)์€ ์ž๋™ ์‚ญ์ œ๋ผ.


โšก Provider-less ๋ชจ๋“œ์™€ ๊ทธ ์œ„ํ—˜์„ฑ ๐ŸŸก

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

  • Provider ์—†์ด๋„ Jotai ๊ฐ€ ๋™์ž‘ํ•˜๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • SSR(Next.js App Router)์—์„œ Provider-less ๋ชจ๋“œ๊ฐ€ ์™œ ์œ„ํ—˜ํ•œ์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค

Provider-less ๋ชจ๋“œ๋ž€?

Jotai ๋Š” <Provider> ์—†์ด๋„ ๊ธฐ๋ณธ ๋™์ž‘ํ•ด. ๋‚ด๋ถ€์ ์œผ๋กœ ์ „์—ญ ์‹ฑ๊ธ€ํ„ด store ๊ฐ€ ์žˆ๊ฑฐ๋“ :

// ๋ธŒ๋ผ์šฐ์ € CSR ์—์„œ๋Š” ์ด๊ฒŒ ์ž˜ ๋™์ž‘ํ•จ
const App = () => (
  // ๐Ÿฃ ์˜์ฒ : "Provider ์—†์–ด๋„ ๋˜๋„ค? ํŽธํ•˜๋‹ค!"
  <StudyList /> // ๋‚ด๋ถ€์ ์œผ๋กœ ์ „์—ญ ์‹ฑ๊ธ€ํ„ด store ์‚ฌ์šฉ
)

โš ๏ธ SSR ์—์„œ๋Š” Provider ์—†์ด ์ ˆ๋Œ€ ์“ฐ์ง€ ๋งˆ๋ผ

// โŒ ์œ„ํ—˜! Next.js App Router SSR ์—์„œ Provider ์—†์ด ์“ฐ๋ฉด...
// ๐Ÿฃ ์˜์ฒ : (์•„๋ฌด๊ฒƒ๋„ ๋ชจ๋ฅด๊ณ ) "๊ทธ๋ƒฅ ์ „์—ญ store ์จ๋„ ๋˜๊ฒ ์ง€"
 
// 1๋ฒˆ ์š”์ฒญ (์˜์ฒ ์˜ ์„ธ์…˜): userAtom ์— { name: '์˜์ฒ ', role: 'admin' } ์ €์žฅ
// 2๋ฒˆ ์š”์ฒญ (์˜์ˆ˜์˜ ์„ธ์…˜): ์ „์—ญ store ์— ์˜์ฒ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์•„์žˆ์Œ!
// โ†’ ์˜์ˆ˜๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”๋ฐ ํ™”๋ฉด์— "์˜์ฒ " ์ด๋ฆ„์ด ๋ณด์ผ ์ˆ˜๋„ ์žˆ์Œ
// โ†’ ์ด๊ฑด ๋‹จ์ˆœ ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐœ์ธ์ •๋ณด ๋ณด์•ˆ ์‚ฌ๊ณ ์•ผ

ํ•ด๊ฒฐ์ฑ…: layout.tsx ์—์„œ ๋ฐ˜๋“œ์‹œ Provider ๋กœ ๊ฐ์‹ธ๊ธฐ

// โœ… app/layout.tsx
// ๐Ÿฆ ์˜ํ˜ธ: "SSR ์—์„  ์ด๋ ‡๊ฒŒ ํ•ด์•ผ ์š”์ฒญ๋งˆ๋‹ค store ๊ฐ€ ๊ฒฉ๋ฆฌ๋ฉ๋‹ˆ๋‹ค"
'use client'
 
import { Provider } from 'jotai'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ko">
      <body>
        {/* Provider ๊ฐ€ ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ store ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด ๊ฒฉ๋ฆฌ */}
        <Provider>
          {children}
        </Provider>
      </body>
    </html>
  )
}
ํ™˜๊ฒฝProvider ํ•„์š” ์—ฌ๋ถ€
CSR ์ „์šฉ SPA์„ ํƒ ์‚ฌํ•ญ
Next.js App Router (SSR)ํ•„์ˆ˜ โ€” ์—†์œผ๋ฉด ์„œ๋ฒ„ ๊ฐ„ ์ƒํƒœ ๊ณต์œ  ๋ณด์•ˆ ์œ„ํ—˜
๋ชจ๋‹ฌ/์œ„์ ฏ ๋‚ด๋ถ€ ๋…๋ฆฝ ์ƒํƒœ๊ถŒ์žฅ โ€” ์Šค์ฝ”ํ”„ ๋ถ„๋ฆฌ ๋ชฉ์ 

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
Provider-less ๋ชจ๋“œ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์จ. ์„œ๋ฒ„์—์„œ ์ „์—ญ store ๋Š” "๊ณต์šฉ ๋ƒ‰์žฅ๊ณ " ์•ผ โ€” ๋‹ค๋ฅธ ์‚ฌ๋žŒ ์Œ์‹์ด ์„ž์—ฌ.

๐Ÿ”— ์—ฐ๊ฒฐ ๊ณ ๋ฆฌ
Provider ์˜ ์Šค์ฝ”ํ”„ ์ œ์–ด์™€ createStore ์ง์ ‘ ์ƒ์„ฑ์€ 08. Provider ์™€ ์Šค์ฝ”ํ”„ ์—์„œ ๊นŠ๊ฒŒ ๋‹ค๋ค„.


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

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


โŒ ๋ฌดํ•œ ๋ฆฌ๋ Œ๋”๋ง (๋ Œ๋” ํ•จ์ˆ˜ ๋‚ด atom ์ƒ์„ฑ)

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

์›์ธ:

// ๐Ÿฃ ์˜์ฒ ์˜ ์‹ค์ˆ˜ โ€” ๋ Œ๋”๋งˆ๋‹ค ์ƒˆ atom config ์ƒ์„ฑ
const StudyItem = ({ id }: { id: string }) => {
  const itemAtom = atom({ id }) // โŒ ๋ Œ๋”๋งˆ๋‹ค ์ƒˆ ๊ฐ์ฒด โ†’ ๋ฌดํ•œ ๋ฃจํ”„
  const [item] = useAtom(itemAtom)
}

ํ•ด๊ฒฐ์ฑ…:

// โœ… useMemo ๋กœ atom config ๋ฅผ ๋ฉ”๋ชจ์ด์ œ์ด์…˜
const StudyItem = ({ id }: { id: string }) => {
  const itemAtom = useMemo(() => atom({ id }), [id])
  const [item] = useAtom(itemAtom)
}
// ๋˜๋Š” ๋ชจ๋“ˆ ์ตœ์ƒ๋‹จ์— ์„ ์–ธ (id ๊ฐ€ ๊ณ ์ •์ผ ๋•Œ)

โŒ SSR ์—์„œ ์ƒํƒœ ๊ณต์œ  ๋ฒ„๊ทธ (์„œ๋ฒ„ ์š”์ฒญ ๊ฐ„ ์˜ค์—ผ)

์ฆ์ƒ: A ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”๋ฐ B ์œ ์ €์˜ ์ •๋ณด๊ฐ€ ๋ณด์ž„

์›์ธ: Next.js App Router ์—์„œ Provider ์—†์ด ์ „์—ญ store ์‚ฌ์šฉ

ํ•ด๊ฒฐ์ฑ…:

// app/layout.tsx ์— Provider ์ถ”๊ฐ€ (์œ„ ์˜ˆ์‹œ ์ฐธ๊ณ )

โŒ useAtom ์ด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ (undefined)

์›์ธ: atom ์ดˆ๊ธฐ๊ฐ’์ด undefined ์ด๊ฑฐ๋‚˜ ํƒ€์ž… ๊ฐ€๋“œ ๋ˆ„๋ฝ

ํ•ด๊ฒฐ์ฑ…:

const userAtom = atom<User | null>(null) // ๋ช…ํ™•ํ•œ ์ดˆ๊ธฐ๊ฐ’
 
const UserAvatar = () => {
  const user = useAtomValue(userAtom)
  if (!user) return <Skeleton /> // ํƒ€์ž… ๊ฐ€๋“œ ํ•„์ˆ˜
  return <img src={user.avatar} alt={user.name} />
}

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

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

๊ฐœ๋…์„ค๋ช…๋น„์œ 
atom config์ƒํƒœ๋ฅผ ์ •์˜ํ•˜๋Š” ์„ค๊ณ„๋„ (๊ฐ’ ์•„๋‹˜)๋ ˆ๊ณ  ๋ธ”๋ก ์„ค๊ณ„๋„
store (WeakMap)atom ์˜ ์‹ค์ œ ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ณต๊ฐ„๋ ˆ๊ณ  ๋ธ”๋ก ๋ณด๊ด€ํ•จ
๊ตฌ๋…useAtom ์œผ๋กœ atom ์— ์—ฐ๊ฒฐ"์ด ์ฑ„๋„ ์•Œ๋ฆผ ์ผœ๊ธฐ"
์„ ํƒ์  ๋ฆฌ๋ Œ๋”๋ง๊ตฌ๋… ์ค‘์ธ atom ์ด ๋ฐ”๋€” ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋‚ด ์ฑ„๋„ ์•Œ๋ฆผ๋งŒ ๋ฐ›๊ธฐ

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

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆโœ… ์ข‹์€ ์˜ˆ
Next.js SSRProvider ์—†์ด ์ „์—ญ store ์‚ฌ์šฉlayout.tsx ์— <Provider> ์ถ”๊ฐ€
๋ Œ๋” ํ•จ์ˆ˜ ๋‚ด atom ์ƒ์„ฑatom() ์„ ๋งค ๋ Œ๋”๋งˆ๋‹ค ํ˜ธ์ถœuseMemo(() => atom(), [deps])
๊ฑฐ๋Œ€ Context ๋Œ€์ฒดํ•˜๋‚˜์˜ Context ์— ๋ชจ๋“  ์ƒํƒœ๊ด€์‹ฌ์‚ฌ๋ณ„ atom ๋ถ„๋ฆฌ

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

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

์˜์ฒ ์ด๊ฐ€ ์ด์ง ๋ฉด์ ‘์„ ๋ดค์–ด. ๋ฉด์ ‘๊ด€์ด ๋ฌผ์—ˆ์–ด:
"Jotai ์˜ atom() ํ˜ธ์ถœ ์งํ›„ store ์— ๊ฐ’์ด ์ €์žฅ๋˜๋‚˜์š”? ์ปดํฌ๋„ŒํŠธ์—์„œ ์–ด๋–ป๊ฒŒ ๊ฐ’์ด ์ดˆ๊ธฐํ™”๋˜๋Š”์ง€ ์„ค๋ช…ํ•ด๋ณด์„ธ์š”."

  • A) atom(0) ์„ ํ˜ธ์ถœํ•˜๋Š” ์ˆœ๊ฐ„ ์ „์—ญ store ์— 0 ์ด ์ฆ‰์‹œ ์ €์žฅ๋œ๋‹ค
  • B) atom(0) ์€ ์„ค๊ณ„๋„ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋ฟ์ด๋ฉฐ, ์ปดํฌ๋„ŒํŠธ์—์„œ useAtom ์„ ํ˜ธ์ถœํ•  ๋•Œ store ์— ๊ฐ’์ด ์ €์žฅ๋˜๊ณ  ๊ตฌ๋…์ด ์‹œ์ž‘๋œ๋‹ค
  • C) atom(0) ์€ React useState(0) ๊ณผ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค
  • D) Provider ๋ฅผ ์„ ์–ธํ•ด์•ผ atom ์ด ์ดˆ๊ธฐํ™”๋œ๋‹ค

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: atom() ์€ { init, read, write } ํ˜•ํƒœ์˜ ๋ถˆ๋ณ€ ์„ค๊ณ„๋„ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด. ์‹ค์ œ ๊ฐ’์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ useAtom(myAtom) ์„ ์ฒ˜์Œ ํ˜ธ์ถœํ•  ๋•Œ store ์˜ WeakMap ์— ์ €์žฅ๋ผ. ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount ๋˜๊ณ  ๋‹ค๋ฅธ ์ฐธ์กฐ๊ฐ€ ์—†์œผ๋ฉด GC ๋กœ ์ž๋™ ์ •๋ฆฌ๋ผ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A ๋Š” ํ‹€๋ ธ์–ด. atom() ํ˜ธ์ถœ ์‹œ์ ์—” store ์— ์•„๋ฌด๊ฒƒ๋„ ์—†์–ด. C ๋Š” ์‚ฌ์šฉ๊ฐ์ด ๋น„์Šทํ•˜์ง€๋งŒ useState ๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€ ์ƒํƒœ๊ณ  atom ์€ ์™ธ๋ถ€ store ๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณต์œ ํ•˜๋Š” ์ƒํƒœ์•ผ. D ๋Š” Provider-less ๋ชจ๋“œ๋กœ๋„ ๋™์ž‘ํ•ด (๋‹จ, SSR ์—์„  Provider ํ•„์ˆ˜).
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "atom ์€ ๋ ˆ์‹œํ”ผ, useAtom ์€ ์š”๋ฆฌ ์‹œ์ž‘." ๋ ˆ์‹œํ”ผ๋งŒ ์žˆ์œผ๋ฉด ์Œ์‹์ด ์•ˆ ์ƒ๊ฒจ.

Q2. ๐Ÿ”ฅ ๊ธด๊ธ‰ ๋””๋ฒ„๊น… (์˜์ˆ˜์˜ ํ˜ธํ†ต)

์˜์ˆ˜ ๋‹˜์ด ์Šฌ๋ž™์— ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒˆ์–ด: "๋ฐฐํฌํ•˜๊ณ  ๋‚˜์„œ A ์œ ์ €๋กœ ๋กœ๊ทธ์ธํ–ˆ๋Š”๋ฐ ํ™”๋ฉด์— B ์œ ์ € ์ด๋ฆ„์ด ๋ณด์ธ๋‹ค. ๋‹น์žฅ ๊ณ ์ณ."
์˜์ฒ ์ด๊ฐ€ ํ™•์ธํ•˜๋‹ˆ ์ฝ”๋“œ๋Š” ์ด๋žฌ์–ด:

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>{children}</body>  {/* Provider ์—†์Œ */}
    </html>
  )
}
 
// app/page.tsx
const userAtom = atom<User | null>(null)

์ œ์ผ ๋จผ์ € ํ•ด์•ผ ํ•  ์ˆ˜์ •์€?

  • A) userAtom ์˜ ์ดˆ๊ธฐ๊ฐ’์„ null ์—์„œ {} ๋กœ ๋ฐ”๊พผ๋‹ค
  • B) layout.tsx ์— <Provider> ๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์š”์ฒญ๋งˆ๋‹ค store ๋ฅผ ๊ฒฉ๋ฆฌํ•œ๋‹ค
  • C) userAtom ์„ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€๋กœ ์ด๋™์‹œํ‚จ๋‹ค
  • D) ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ atom ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค

โœ… ์ •๋‹ต: B

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

  • ์›๋ฆฌ ์„ค๋ช…: Next.js App Router ์˜ SSR ํ™˜๊ฒฝ์—์„œ Provider ์—†์ด ์ „์—ญ store ๋ฅผ ์“ฐ๋ฉด, ์„œ๋ฒ„ ์ธ์Šคํ„ด์Šค ๊ฐ„์— store ๊ฐ€ ๊ณต์œ ๋ผ. A ์œ ์ €์˜ ์š”์ฒญ์ด store ์— ๊ฐ’์„ ์“ฐ๋ฉด, B ์œ ์ €์˜ ์š”์ฒญ์—์„œ ๊ทธ ๊ฐ’์„ ์ฝ์„ ์ˆ˜ ์žˆ์–ด. <Provider> ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ store ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์™„์ „ํžˆ ๊ฒฉ๋ฆฌ๋ผ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: A, C, D ๋Š” ๋ฌธ์ œ์˜ ๊ทผ๋ณธ ์›์ธ(store ๊ฒฉ๋ฆฌ ์‹คํŒจ)์„ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•ด.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "SSR + Jotai = ๋ฐ˜๋“œ์‹œ Provider." ๊ณต์šฉ ๋ƒ‰์žฅ๊ณ ์— ๋‚ด ์Œ์‹์„ ๋„ฃ์œผ๋ฉด ๋ˆ„๊ตฐ๊ฐ€ ๊ฐ€์ ธ๊ฐ€.

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

Jotai atom ์ด React Context ๋ณด๋‹ค ๋ฆฌ๋ Œ๋”๋ง์ด ์ ์€ ์ด์œ ๋ฅผ ๊ฐœ๋ฐœ์ž ์นœ๊ตฌ์—๊ฒŒ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์„ค๋ช…ํ•ด๋ด.
(์ •๋‹ต ์—†์–ด, ๋ณธ์ธ๋งŒ์˜ ๋น„์œ ๋กœ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์ถฉ๋ถ„ํ•ด.)

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

"Context ๋Š” ๊ฐ’ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ทธ Context ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊นจ์šฐ๋Š” '๋‹จ์ฒด ์นดํ†ก ์•Œ๋ฆผ' ์ด๊ณ , Jotai atom ์€ ๋‚ด๊ฐ€ ํŒ”๋กœ์šฐํ•œ atom ๋งŒ ๋ฐ”๋€” ๋•Œ ๋‚˜๋ฅผ ๊นจ์šฐ๋Š” '๊ฐœ์ธ ํŒ”๋กœ์šฐ ์•Œ๋ฆผ' ์ด์•ผ."

๐Ÿ’ก ์ด ๋น„์œ ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด: Jotai ์˜ ํ•ต์‹ฌ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•œ ๊ฑฐ์•ผ. ๋‹ค์Œ ๊ฐ€์ด๋“œ๋กœ ๋„˜์–ด๊ฐ€๋„ ์ถฉ๋ถ„ํ•ด! ๐ŸŽ‰


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

ํ‡ด๊ทผ๊ธธ ์ง€ํ•˜์ฒ  ์•ˆ. ํ•ธ๋“œํฐ ๋ฉ”๋ชจ์žฅ์„ ์—ด์—ˆ๋‹ค.

์˜ค๋Š˜ ์˜ํ˜ธ ๋‹˜ํ•œํ…Œ ์ฒ˜์Œ์œผ๋กœ Jotai ๋ฅผ ์ œ๋Œ€๋กœ ๋ฐฐ์šด ๋‚ ์ด์—ˆ๋‹ค. ์†”์งํžˆ ์ฒ˜์Œ์—” "๊ทธ๋ƒฅ ์ „์—ญ useState ์•„๋‹Œ๊ฐ€?" ์‹ถ์—ˆ๋Š”๋ฐ... ์™„์ „ํžˆ ํ‹€๋ ธ๋”๋ผ๊ณ .

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "atom ์€ ๊ฐ’์ด ์•„๋‹ˆ๋ผ ์„ค๊ณ„๋„๋‹ค. useAtom ์ด ํ˜ธ์ถœ๋  ๋•Œ ๋น„๋กœ์†Œ ๊ฐ’์ด ์ƒ๊ธฐ๊ณ , ๊ตฌ๋…์ด ์‹œ์ž‘๋œ๋‹ค."

Context ๋กœ ์ „๋ถ€ ๋•Œ๋ ค๋„ฃ์—ˆ๋‹ค๊ฐ€ ์˜์ˆ™ ๋‹˜ํ•œํ…Œ "ํ•„ํ„ฐ ๋ฐ”๊ฟ€ ๋•Œ๋งˆ๋‹ค ํ™”๋ฉด ์ „์ฒด๊ฐ€ ๊นœ๋นก๊ฑฐ๋ ค์š”" ๋ผ๋Š” ํ”ผ๋“œ๋ฐฑ ๋ฐ›๊ณ ... ๊ทธ๋•Œ ์–ผ๋งˆ๋‚˜ ์ฐฝํ”ผํ–ˆ๋Š”์ง€. WeakMap ์ด ๊ตฌ๋…์ž๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค๋Š” ๊ฒƒ๋„, SSR ์—์„œ Provider ์—†์ด ์“ฐ๋ฉด ๋‹ค๋ฅธ ์œ ์ € ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์ผ ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ๋„ โ€” ์˜ค๋Š˜ ์ฒ˜์Œ ์•Œ์•˜๋‹ค.

์˜ํ˜ธ ๋‹˜์ด "์˜์ฒ  ๋‹˜, ์ด๊ฑด ๋‹จ์ˆœ ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ณด์•ˆ ์‚ฌ๊ณ ์˜ˆ์š”" ๋ผ๊ณ  ํ–ˆ์„ ๋•Œ ๋“ฑ์ค„๊ธฐ๊ฐ€ ์„œ๋Š˜ํ–ˆ๋‹ค. ๋‹ค์Œ์—๋Š” ๋‚ด๊ฐ€ ๋จผ์ € Provider ์ฑ™๊ฒจ์•ผ์ง€.

์˜ค๋Š˜ ๋ฐฐ์šด ๊ฑฐ ๊นŒ๋จน๊ธฐ ์ „์— ์ง‘ ๊ฐ€์„œ ํ•œ ๋ฒˆ ๋” ์ •๋ฆฌํ•ด์•ผ๊ฒ ๋‹ค. ์˜ค๋Š˜๋”ฐ๋ผ ์ง€ํ•˜์ฒ ์ด ์™œ ์ด๋ ‡๊ฒŒ ๋А๋ฆฌ๋ƒ... ๐Ÿš‡


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