์›๋ฌธ

https://tkdodo.eu/blog/deriving-client-state-from-server-state

 

Deriving Client State from Server State

How to use derived state in React to keep client state and server data aligned without manual sync or effects.

tkdodo.eu

 

 

ํœด๊ฐ€์—์„œ ๋ง‰ ๋Œ์•„์™”๋Š”๋ฐ, Zustand๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ€์žฅ ํฐ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„์— ๋Œ€ํ•œ Reddit ์งˆ๋ฌธ์„ ๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค.
์ฝ”๋“œ๋Š” ๋Œ€๋žต ์ด๋ ‡๋‹ค(์ตœ์‹  ๋ฌธ๋ฒ•์œผ๋กœ ์กฐ๊ธˆ ์ˆ˜์ •ํ•˜๊ณ , ์ปค์Šคํ…€ ํ›… ์•ˆ์— ๋„ฃ์—ˆ๋‹ค).

 

Manual-sync

const useSelectedUser = () => {
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })
  const { selectedUserId, setSelectedUserId } = useUserStore()

  // If the selected user gets deleted from the server,
  // Zustand won't automatically clear selectedUserId
  // You have to manually handle this:
  useEffect(() => {
    if (!users?.some((u) => u.id === selectedUserId)) {
      setSelectedUserId(null) // Manual sync required
    }
  }, [users, selectedUserId])

  return [selectedUserId, selectedUserId]
}

 

๋ฌผ๋ก , useEffect—ํŠนํžˆ ๊ทธ ์•ˆ์—์„œ setState๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ—๋ฅผ ๋ณผ ๋•Œ๋งˆ๋‹ค ๋‚˜๋Š” ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์ด ์—†์„๊นŒ ์ƒ๊ฐํ•˜๊ฒŒ ๋œ๋‹ค.
๋‚ด ๊ฒฝํ—˜์ƒ, ๊ฑฐ์˜ ํ•ญ์ƒ ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•˜๋ฉฐ, ๋ณดํ†ต ๊ทธ ๋ฐฉ๋ฒ•์„ ์ฐพ๋Š” ๊ฒƒ์ด ๊ฐ€์น˜ ์žˆ๋Š” ์ผ์ด๋‹ค.
๊ทธ๋Ÿฌ๋‹ˆ ํ•œ ๊ฑธ์Œ ๋ฌผ๋Ÿฌ์„œ์„œ ์šฐ๋ฆฌ๊ฐ€ ๋จผ์ € ๋ฌด์—‡์„ ๋‹ฌ์„ฑํ•˜๊ณ  ์‹ถ์€์ง€๋ฅผ ๋จผ์ € ์‚ดํŽด๋ณด์ž.

 

 

์ƒํƒœ ๋™๊ธฐํ™” ์œ ์ง€ํ•˜๊ธฐ (Keeping State in Sync)

๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด, ์šฐ๋ฆฌ๋Š” ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(Client State)์ธ selectedUserId๋ฅผ ์„œ๋ฒ„ ์ƒํƒœ(Server State)์ธ ์‚ฌ์šฉ์ž ๋ชฉ๋ก(users)๊ณผ ๋™๊ธฐํ™”ํ•˜๊ณ  ์‹ถ๋‹ค.
์ด๊ฒƒ์€ ๋‹น์—ฐํ•œ ์ผ์ด๋‹ค.


์˜ˆ๋ฅผ ๋“ค์–ด, useQuery๋ฅผ ํ†ตํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋‹ค์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๋Š”๋ฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋ชฉ๋ก์—์„œ ์‚ญ์ œ๋˜์—ˆ์Œ์—๋„ ์—ฌ์ „ํžˆ ์ƒํƒœ์— ์ €์žฅ๋˜์–ด ์žˆ๋‹ค๋ฉด, ๊ทธ ์„ ํƒ์€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค.

 

 

ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(Client State)

์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ Zustand์—๋งŒ ๊ตญํ•œ๋œ ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค.
useUserStore()๋ฅผ ๋‹จ์ˆœํžˆ ๋กœ์ปฌ ์ƒํƒœ๋กœ ๋ฐ”๊ฟ”๋„ ๋˜‘๊ฐ™์ด ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค:

const [selectedUserId, setSelectedUserId] = useState()

๋˜๋Š” URL ์ƒํƒœ(nuqs ๊ฐ™์€)๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค:

const [selectedUserId, setSelectedUserId] = useQueryState('userId')

์ค‘์š”ํ•œ ๊ฑด ์ƒํƒœ๊ฐ€ ์–ด๋””์— ์ €์žฅ๋˜๋А๋ƒ๊ฐ€ ์•„๋‹ˆ๋ผ,
์„œ๋ฒ„ ์ƒํƒœ(Server State)๊ฐ€ ๋ฐ”๋€” ๋•Œ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(Client State)๋ผ๋Š” ์ ์ด๋‹ค.

 

 

์ฟผ๋ฆฌ(Query)์—๋Š” onSuccess ์ฝœ๋ฐฑ์ด ์—†๊ณ ,
๋ Œ๋” ์ค‘์— setState๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํŠธ๋ฆญ์€ React์˜ ๋‚ด์žฅ ์ƒํƒœ์—์„œ๋งŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—,
์‚ฌ์‹ค์ƒ ๋‚จ์€ ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ๋ฌด์‹œ๋ฌด์‹œํ•œ useEffect๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๊ฒฐ๊ตญ user selection์„ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ?

 

์ƒํƒœ๋ฅผ ๋™๊ธฐํ™”ํ•˜์ง€ ๋ง๊ณ , ํŒŒ์ƒํ•˜๋ผ(Don’t Sync State – Derive It)

Kent C. Dodds๊ฐ€ ์“ด ๊ธ€์„ ๊ธฐ์–ตํ•˜๋Š”๊ฐ€?
๊ทธ๋Š” ๋„ค ๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ useState๋ฅผ ํ•˜๋‚˜๋กœ ์ค„์ด๊ณ , ๋‚˜๋จธ์ง€๋Š” ๋‹จ์ผ ์ง„์‹ค์˜ ์ถœ์ฒ˜(source of truth)์—์„œ ํŒŒ์ƒํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ์—ˆ๋‹ค.

์šฐ๋ฆฌ ์ƒํ™ฉ์—์„œ๋„ ๋น„์Šทํ•œ ์ ‘๊ทผ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
useEffect๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์†Œ ๋ช…๋ นํ˜•(imperative) ์‚ฌ๊ณ  ๋ฐฉ์‹์ด๋‹ค:

์‚ฌ์šฉ์ž๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ณ , ํ˜„์žฌ ์„ ํƒ์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด, ์„ ํƒ์„ null๋กœ ์žฌ์„ค์ •ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ์‚ฌ๊ณ  ๋ฐฉ์‹์„ ์กฐ๊ธˆ ๋” ์„ ์–ธํ˜•(declarative)์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค:

์—ฌ๊ธฐ ๋ฐฑ์—”๋“œ์—์„œ ๊ฐ€์ ธ์˜จ Users์™€ Current selection์ด ์žˆ๋‹ค.
์ด ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹ค์ œ ์ƒํƒœ(real state)๋ฅผ ๋‹ฌ๋ผ.

 

 

const useSelectedUser = () => {
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })
  const { selectedUserId, setSelectedUserId } = useUserStore()

  const selectedId = users?.some((u) => u.id === selectedUserId)
    ? selectedUserId
    : null

  return [selectedId, setSelectedUserId]
}

 

์ด ์ฝ”๋“œ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜๋‹ค.
์Šคํ† ์–ด ๊ฐ’์„ ์ง์ ‘ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋Œ€์‹ , Selection ์ž์ฒด๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ ,
์„œ๋ฒ„ ์ƒํƒœ(Server State)์—์„œ ๋” ์ด์ƒ ํ•ด๋‹น id๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ์ปค์Šคํ…€ ํ›…์—์„œ ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•œ๋‹ค.


์ฆ‰, useSelectedUser()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ณณ์—์„œ๋Š” ์ด์ „์ฒ˜๋Ÿผ null์„ ๋ฐ›๊ฒŒ ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์Šคํ† ์–ด๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ์ด์ ๋„ ์žˆ๋‹ค:

  • ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์‹œ ๋ชฉ๋ก์— ์ถ”๊ฐ€๋˜๋ฉด, ์„ ํƒ(selection)์ด ์ž๋™์œผ๋กœ ๋ณต์›๋œ๋‹ค.
  • UX ์š”๊ตฌ์‚ฌํ•ญ์ด ๋ฐ”๋€Œ์–ด ์„ ํƒ์„ ์ œ๊ฑฐํ•˜๊ณ  ์‹ถ์ง€ ์•Š๊ณ , ๋‹จ์ง€ ์„ ํƒ์ด ์œ ํšจํ•˜์ง€ ์•Š์Œ์„ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.
    → ์›๋ž˜ ๊ฐ’์„ ํ•ญ์ƒ ์œ ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

isSelectionValid

const useSelectedUser = () => {
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })
  const { selectedUserId, setSelectedUserId } = useUserStore()
  const isSelectionValid = users?.some((u) => u.id === selectedUserId)

  return [selectedUserId, setSelectedUserId, isSelectionValid]
}

 

ํ•จ์ •์€ ์–ด๋””์— ์žˆ์„๊นŒ์š”?

"์ƒํƒœ ํŒŒ์ƒ ์†”๋ฃจ์…˜"์˜ ๋ช…๋ฐฑํ•œ ๋‹จ์  ์ค‘ ํ•˜๋‚˜๋Š” ์‚ฌ์šฉ์ž ์ €์žฅ์†Œ์— ์ €์žฅ๋œ ๋‚ด์šฉ์„ ๋” ์ด์ƒ "์‹ ๋ขฐ"ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. useUserStore์—์„œ ๋‹ค๋ฅธ ๊ณณ์˜ selectedUserId๋ฅผ ์ฝ์–ด์˜ค๋ฉด ์ถ”๊ฐ€ ๊ฒ€์‚ฌ๋ฅผ ๋ฐ›์ง€ ๋ชปํ•˜๋ฏ€๋กœ ํ•ญ์ƒ ์‚ฌ์šฉ์ž ์ •์˜ hook์—์„œ ์ฝ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ €๋Š” ์ €์žฅ์†Œ๋ฅผ ์ตœ์ข… ๊ฒ€์ฆ๋œ ๊ฐ’์˜ ์†Œ์Šค๋ผ๊ธฐ๋ณด๋‹ค๋Š” UI์—์„œ ์‹ค์ œ๋กœ ์„ ํƒ๋œ ํ•ญ๋ชฉ์˜ ๊ธฐ๋ก์œผ๋กœ ๋ณด๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ ์— ๋Œ€ํ•ด์„œ๋Š” ์ „ํ˜€ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋˜, Reddit ์งˆ๋ฌธ์—์„œ Redux Toolkit์ด “์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค”๊ณ  ์–ธ๊ธ‰ํ–ˆ์ง€๋งŒ, ์‹ค์ œ๋กœ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.
๋ณดํ†ต API ์Šฌ๋ผ์ด์Šค์™€ ์‚ฌ์šฉ์ž ์„ ํƒ ์Šฌ๋ผ์ด์Šค๋ฅผ ์ฝ์–ด ๋‘ ๊ฐ’์„ ๊ฒฐํ•ฉํ•˜๋Š” selector๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜๋Š”๋ฐ,
์ด๊ฒƒ์ด ๋ฐ”๋กœ ์šฐ๋ฆฌ๊ฐ€ ์ปค์Šคํ…€ ํ›…์—์„œ ํ•˜๊ณ  ์žˆ๋Š” ๋ฐฉ์‹๊ณผ ๋™์ผํ•˜๋‹ค.

 

์˜คํžˆ๋ ค ์ด ๋ฐฉ์‹์€ ์ƒํƒœ๋ฅผ ํŒŒ์ƒ(derive)ํ•˜๋„๋ก ์œ ๋„ํ•˜๋ฏ€๋กœ, ์ข‹์€ ์ ‘๊ทผ์ž…๋‹ˆ๋‹ค.

 

๋‹ค๋ฅธ ์˜ˆ์‹œ

์„œ๋ฒ„ ์ƒํƒœ(Server State)๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(Client State)๋ฅผ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š๋Š” ๊ฐœ๋…์€ ์—ฌ๋Ÿฌ ๊ฒฝ์šฐ์— ์œ ์šฉํ•˜๋‹ค.
ํ”ํ•œ ์˜ˆ์‹œ๋Š” ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜จ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํผ์„ ๋ฏธ๋ฆฌ ์ฑ„์šธ ๋•Œ(prefilling forms)์ด๋‹ค.

 

default-value-effect

function UserSelection() {
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })
  const [selection, setSelection] = useState()

  // use the first value as default selection
  useEffect(() => {
    if (users?.[0]) {
      setSelection(users[0])
    }
  }, [users])

  // return markup
}

 

์ด ํšจ๊ณผ๋Š” ์žฅํ™ฉํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์ฟผ๋ฆฌ์—์„œ ์ƒˆ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ฌ ๋•Œ ํ˜„์žฌ ์„ ํƒ ํ•ญ๋ชฉ์„ ๋ฎ์–ด์“ฐ๋Š” ๋ฒ„๊ทธ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ›

์ด ๋ฌธ์ œ๋Š” ๋‹ค๋ฅธ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์€ ์—ฌ์ „ํžˆ โ€‹โ€‹์ƒํƒœ๋ฅผ ํŒŒ์ƒํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

 

derived-default-value

function UserSelection() {
  const { data: users } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })
  const [selection, setSelection] = useState()

  const derivedSelection = selection ?? users?.[0]

  // return markup
}

 

 

์ง€๊ธˆ ์šฐ๋ฆฌ๊ฐ€ ํ•ด์•ผ ํ•  ์ผ์€ selection ๋Œ€์‹  derivedSelection์„ ๊ณ„์† ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ฟ์ด๋ฉฐ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ญ์ƒ ์›ํ•˜๋Š” ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿš€

 

 

ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด, ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ณ  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ๋ฐ˜์˜ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•œ ๊ณผ์ œ๊ฐ€ ๋œ๋‹ค.



์ด๋ฅผ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๋ฉฐ, ๋Œ€ํ‘œ์ ์œผ๋กœ Redux, Recoil, MobX ๋“ฑ์ด ์žˆ๋‹ค. ์ตœ๊ทผ์—๋Š” ๋ณด๋‹ค ๋‹จ์ˆœํ•˜๊ณ  ๊ฐ€๋ฒผ์šด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ œ๊ณตํ•˜๋Š” Zustand๊ฐ€ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ๋‹ค.


 

1. Zustand๋ž€ ๋ฌด์—‡์ธ๊ฐ€


https://zustand-demo.pmnd.rs/

 

Zustand

 

zustand-demo.pmnd.rs

 

 

 


Zustand์˜ ๊ณต์‹๋ฌธ์„œ์ด๋‹ค.
๊ณต์‹๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด,

์ž‘๊ณ , ๋น ๋ฅด๋ฉฐ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œํ•œ์˜ ์ƒํƒœ ๊ด€๋ฆฌ ์†”๋ฃจ์…˜์ž…๋‹ˆ๋‹ค.
Zustand๋Š” ํ›…(Hooks)์— ๊ธฐ๋ฐ˜ํ•œ ํŽธ์•ˆํ•œ API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๊ฐ€ ๋งŽ๊ฑฐ๋‚˜ ํŠน์ •ํ•œ ์˜๊ฒฌ(opinion)์— ์น˜์šฐ์น˜์ง€ ์•Š์ง€๋งŒ, ๋ช…์‹œ์ ์ด๊ณ  Flux์™€ ์œ ์‚ฌํ•œ ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ์ถฉ๋ถ„ํ•œ ๊ทœ์น™์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๊ท€์—ฝ๋‹ค๊ณ  ๋ฌด์‹œํ•˜์ง€ ๋งˆ์„ธ์š”. ๋ฐœํ†ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค!
๋งŽ์€ ์‹œ๊ฐ„์„ ๋“ค์—ฌ ํ”ํžˆ ๊ฒช๋Š” ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•…๋ช… ๋†’์€ ์ข€๋น„ ์ž์‹(zombie child) ๋ฌธ์ œ, React์˜ ๋™์‹œ์„ฑ(concurrency), ์„œ๋กœ ๋‹ค๋ฅธ ๋ Œ๋”๋Ÿฌ ๊ฐ„์˜ ์ปจํ…์ŠคํŠธ ์†์‹ค(context loss) ๋“ฑ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
React ์ƒํƒœ๊ณ„์—์„œ ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑฐ์˜ ์œ ์ผํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ผ์ง€๋„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค.


๋ผ๊ณ  ํ•œ๋‹ค.

โœ”๏ธ์ข…ํ•ฉํ•ด๋ณด๋ฉด


1. Zustand๋Š” ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋งŒ ์ œ๊ณตํ•˜๋Š” ๊ฐ€๋ฒผ์šด ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

2. Redux์ฒ˜๋Ÿผ ๋ณต์žกํ•œ ์„ค์ •์ด๋‚˜ ํŒŒ์ผ ๊ตฌ์กฐ ์—†์ด๋„ ์“ธ ์ˆ˜ ์žˆ๊ณ , ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๊ฐ€ ์ปค์ ธ๋„ ๋ฌด๋ฆฌ ์—†์ด ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ 

3. React์˜ useState, useEffect ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ

4. ๋ณต์žกํ•œ action, reducer, dispatch ๊ฐ™์€ ๊ฐœ๋…์„ ๊ฐ•์ œ๋กœ ์“ฐ๊ฒŒ ํ•˜์ง€ ์•Š์•„์„œ ์ง„์ž… ์žฅ๋ฒฝ์ด ๋‚ฎ์Œ

5. ํŠน์ • ํŒจํ„ด์„ ๊ฐ•์ œํ•˜์ง€ ์•Š์ง€๋งŒ, Flux ์•„ํ‚คํ…์ฒ˜์ฒ˜๋Ÿผ ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ๋‹จ๋ฐฉํ–ฅ์ด๋ผ ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.

6. ์ข€๋น„ ์ž์‹ ๋ฌธ์ œ(zombie child problem)
→ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋๋Š”๋ฐ ์ด๋ฏธ ์–ธ๋งˆ์šดํŠธ๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜› ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋ฒ„๊ทธ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

7. React ๋™์‹œ์„ฑ(Concurrency)
→ React 18์˜ concurrent mode์—์„œ๋„ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ์„ค๊ณ„๋จ.

8. ์ปจํ…์ŠคํŠธ ์†์‹ค(Context loss)
→ React Native, Web, Three.js ๊ฐ™์ด ๋ Œ๋”๋Ÿฌ๊ฐ€ ํ˜ผํ•ฉ๋œ ํ™˜๊ฒฝ์—์„œ๋„ ์ƒํƒœ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ณต์œ  ๊ฐ€๋Šฅ.


์š”์•ฝ: Zustand๋Š” ๋‹จ์ˆœํ•˜์ง€๋งŒ ๊นŠ์ด ์žˆ๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๊ณ , React์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊นŒ๋‹ค๋กœ์šด ๋ฒ„๊ทธ ์ƒํ™ฉ๋„ ๋Œ€๋ถ€๋ถ„ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ๊ฑฐ์˜ ์œ ์ผํ•œ ์„ ํƒ์ง€์ด๋‹ค



2. ๊ธฐ๋ณธ์‚ฌ์šฉ๋ฐฉ๋ฒ•

 

import { create } from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
  updateBears: (newBears) => set({ bears: newBears }),
}))

 

function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} bears around here...</h1>
}

function Controls() {
  const increasePopulation = useStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

 

1. create()
• Zustand ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“œ๋Š” ์ตœ์ดˆ ํ•จ์ˆ˜.
• ์ธ์ž๋กœ ์ „๋‹ฌํ•˜๋Š” ํ•จ์ˆ˜ ์•ˆ์—์„œ ์ƒํƒœ(state)์™€ ์ƒํƒœ ๋ณ€๊ฒฝ ๋กœ์ง(setter)์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.


2. set()
• ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜.
• ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ(merge ๋™์ž‘).

3. get()
• ์Šคํ† ์–ด์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ ์ฝ๋Š” ํ•จ์ˆ˜.
• React ํ›… ์—†์ด๋„ ์™ธ๋ถ€์—์„œ ์ƒํƒœ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Œ.

4. subscribe()
• ํŠน์ • ์ƒํƒœ ๋ณ€ํ™”์— ๋Œ€ํ•ด ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก.
• React ์ปดํฌ๋„ŒํŠธ ๋ฐ”๊นฅ์—์„œ๋„ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ ๊ฐ€๋Šฅ.

const unsubscribe = useStore.subscribe(state => {
  console.log('count changed:', state.count);
});

unsubscribe(); // ๊ตฌ๋… ํ•ด์ œ

 

 

 

๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค๊ณผ์˜ ์ฐจ์ด์  (Zustand vs ๋‹ค๋ฅธ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋น„๊ต)

 

  Zustand Redux Recoil Jotai
์ฒ ํ•™ ์ตœ์†Œํ•œ์˜ API, ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ํŒจํ„ด ์„ค๊ณ„ Flux ์•„ํ‚คํ…์ฒ˜ ๊ธฐ๋ฐ˜, ๊ฐ•๋ ฅํ•œ ๊ทœ์น™๊ณผ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ React ์ƒํƒœ๋ฅผ ์ „์—ญ์ ์œผ๋กœ ๋‹ค๋ฃจ๋Š” Declarative ๋ชจ๋ธ ์›์ž(atom) ๋‹จ์œ„๋กœ ์ƒํƒœ ๊ด€๋ฆฌ, React Hooks ์นœํ™”์ 
๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ๊ฑฐ์˜ ์—†์Œ ๋งŽ์Œ(์•ก์…˜, ๋ฆฌ๋“€์„œ, ์Šคํ† ์–ด ์„ค์ • ๋“ฑ) ์ ์Œ ๊ฑฐ์˜ ์—†์Œ
ํ•™์Šต ๊ณก์„  ๋‚ฎ์Œ ์ค‘๊ฐ„~๋†’์Œ ์ค‘๊ฐ„ ๋‚ฎ์Œ
๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ž์œ ๋กญ๊ฒŒ ๊ตฌํ˜„(๋ฏธ๋“ค์›จ์–ด ํŒจํ„ด ์•„๋‹˜) ๋ฏธ๋“ค์›จ์–ด(thunk, saga ๋“ฑ) ํ•„์š” Suspense ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ ์ง€์› Suspense ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ ์ง€์›
๋ฆฌ๋ Œ๋”๋ง ์ œ์–ด selector ๊ธฐ๋ฐ˜์œผ๋กœ ์ปดํฌ๋„ŒํŠธ ๋ฆฌ๋ Œ๋” ์ตœ์†Œํ™” connect ๋˜๋Š” hooks์—์„œ memoization ํ•„์š” ํŒŒํŽธํ™”๋œ atom ๋‹จ์œ„๋กœ ์ตœ์†Œํ™” atom ๋‹จ์œ„๋กœ ์ตœ์†Œํ™”
React ์˜์กด์„ฑ ๋‚ฎ์Œ(React ์—†์ด๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) ๋†’์Œ ๋†’์Œ ๋†’์Œ
์Šคํ† ์–ด ๊ตฌ์กฐ ๋‹จ์ผ ์Šคํ† ์–ด or ๋‹ค์ค‘ ์Šคํ† ์–ด ๋ชจ๋‘ ๊ฐ€๋Šฅ ์ฃผ๋กœ ๋‹จ์ผ ์Šคํ† ์–ด atom๋“ค์˜ ์ง‘ํ•ฉ atom๋“ค์˜ ์ง‘ํ•ฉ
SSR ์ง€์› ์‰ฌ์›€ ์‰ฌ์›€ ์ผ๋ถ€ ์ผ€์ด์Šค ๊ณ ๋ ค ํ•„์š” ์‰ฌ์›€

 

์šฐ๋ฆฌ ํŒ€์€ ํ˜„์žฌ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ณผ๋„๊ธฐ๋ฅผ ๊ฒช๊ณ  ์žˆ๋‹ค.
๊ณผ๊ฑฐ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ํŒ€์›์˜ ์„ ํ˜ธ๋„ ์ฐจ์ด๋กœ ์ธํ•ด Jotai, Redux, Recoil์ด ํ˜ผํ•ฉ๋œ ๊ตฌ์กฐ๊ฐ€ ํ˜•์„ฑ๋˜์—ˆ๊ณ , ๊ทธ ๊ฒฐ๊ณผ ์ „์—ญ ์ƒํƒœ์˜ ํ๋ฆ„์ด ๋ณต์žกํ•ด์ง€๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์ ์  ์–ด๋ ค์›Œ์กŒ๋‹ค.

์ด์— ๋”ฐ๋ผ ์šฐ๋ฆฌ๋Š” ์ƒํƒœ๊ด€๋ฆฌ ๋ฐฉ์‹์„ Zustand๋กœ ํ†ตํ•ฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

 


Zustand๋Š” ๋น„๊ต์  ๊ฐ€๋ฒผ์šฐ๋ฉด์„œ๋„ ๋ช…์‹œ์ ์ด๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ, ๊ธฐ์กด์— ๋ถ„์‚ฐ๋œ ์ƒํƒœ๋ฅผ ํ•œ ๊ณณ์œผ๋กœ ๋ชจ์œผ๊ธฐ์— ์ ํ•ฉํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

๋‹ค๋งŒ, ํ†ตํ•ฉ ๊ณผ์ •์—์„œ ๊ณ ๋ คํ•ด์•ผ ํ•  ์ค‘์š”ํ•œ ์ ์ด ์žˆ๋‹ค.

 


ํ˜„์žฌ Redux์—์„œ๋Š” ๋น„๋™๊ธฐ์  ๋ผ์šฐํŒ… ์ฒ˜๋ฆฌ ๋กœ์ง์ด ์ „์—ญ์—์„œ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋Š”๋ฐ, ์ด ๊ธฐ๋Šฅ์„ ๋‹จ์ˆœํžˆ Zustand๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋™์ผํ•œ ์œ ์—ฐ์„ฑ์„ ํ™•๋ณดํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
Zustand์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋น„๋™๊ธฐ ํ๋ฆ„์„ ๋ฏธ๋“ค์›จ์–ด ๋˜๋Š” subscribe ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋กœ ์žฌ๊ตฌ์„ฑํ•ด์•ผ ํ•˜๋ฉฐ, ์ƒํƒœ ๋ณ€๊ฒฝ๊ณผ ๋ผ์šฐํŒ… ์ „ํ™˜์ด ๋Š๊น€ ์—†์ด ๋™์ž‘ํ•˜๋„๋ก ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ ๊ณผ์ œ์˜€๋‹ค.

 

 

๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ์ด๋Ÿฌํ•œ ๊ณผ์ œ๋“ค์„ ์ ์šฉ์‹œ์ผฐ๋Š”์ง€ ์จ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค!

 

 

+ Recent posts