์›๋ฌธ

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์„ ๊ณ„์† ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ฟ์ด๋ฉฐ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ญ์ƒ ์›ํ•˜๋Š” ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿš€

 

 

๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๊ตฌํ˜„: Web Worker์™€ WebAssembly

๋น„๋™๊ธฐ๋กœ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค? ๋งŽ์ด๋“ค async/await๋งŒ ์“ฐ๋ฉด ์ž‘์—…์ด โ€œ๋™์‹œ์—โ€ ๋ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ๋Š” ํ•˜๋‚˜์˜ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ผ์„ ์ž˜๊ฒŒ ์ชผ๊ฐœ ์ˆœ์„œ ์—†์ด ์ฒ˜๋ฆฌํ•ด ๋ณด์ด๋Š” ๊ธฐ๋ฒ•์ด์ง€, ์‹ค์ œ๋กœ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ๊ณ„์‚ฐํ•˜๋Š” ๊ฑด ์•„๋‹™๋‹ˆ๋‹ค.

UI๊ฐ€ ๋ฉˆ์ถ”์ง€ ์•Š๊ฒŒ ํ•˜๋Š” ๋ฐ๋Š” ๋น„๋™๊ธฐ๋กœ๋„ ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋‚˜ ์ด๋ฏธ์ง€/์˜์ƒ ๋ณ€ํ™˜์ฒ˜๋Ÿผ CPU๋ฅผ ์žก์•„๋จน๋Š” ์ž‘์—…์€ ๊ฒฐ๊ตญ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๋ฌถ์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.

1. ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๊ฐ€ ํ•„์š”ํ•œ ์ด์œ 

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ ์–ธ์–ด์ž…๋‹ˆ๋‹ค. ์ฆ‰, ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ž‘์—…๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ณต์žกํ•œ ๊ณ„์‚ฐ, ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ์ด๋ฏธ์ง€ ๋ณ€ํ™˜ ๊ฐ™์€ ์ž‘์—…์€ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ ค UI๊ฐ€ ๋ฉˆ์ถœ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๊ฐœ๋…์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ์š”.

2. Web Worker๋กœ ๋™์‹œ์„ฑ ๊ตฌํ˜„

Web Worker๋Š” ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ(UI)์™€ ๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์„ ๋งก๊ธฐ๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค.

// worker.js (๋ณ„๋„์˜ ํŒŒ์ผ)
self.onmessage = function(event) {
  let num = event.data;
  let result = 0;
  for (let i = 0; i < num; i++) {
    result += i;
  }
  self.postMessage(result);
};
// main.js
const worker = new Worker("worker.js");
worker.postMessage(1000000); // Worker์—๊ฒŒ ์ž‘์—… ์š”์ฒญ

worker.onmessage = (e) => {
  console.log("๊ฒฐ๊ณผ:", e.data);
};



Web Worker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด UI ๋ฉˆ์ถค ์—†์ด ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. WebAssembly๋ฅผ ํ™œ์šฉํ•œ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ

WebAssembly(WASM)๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋™์ž‘ํ•˜๋Š” ์ €์ˆ˜์ค€ ์ด์ง„ ์ฝ”๋“œ ํฌ๋งท์ž…๋‹ˆ๋‹ค. ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ์—ฐ์‚ฐ์„ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋Œ€์‹  C/C++/Rust ๊ฐ™์€ ์–ธ์–ด๋กœ ์ž‘์„ฑํ•œ ๋’ค, WebAssembly๋กœ ์ปดํŒŒ์ผํ•ด์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// ์˜ˆ์‹œ: C ์ฝ”๋“œ (sum.c)
int sum(int n) {
  int result = 0;
  for (int i = 0; i < n; i++) {
    result += i;
  }
  return result;
}

์ด ์ฝ”๋“œ๋ฅผ emscripten ๊ฐ™์€ ๋„๊ตฌ๋กœ WebAssembly๋กœ ์ปดํŒŒ์ผํ•˜๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ๊ณ ์„ฑ๋Šฅ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. Web Worker vs WebAssembly ๋น„๊ต

๊ตฌ๋ถ„ Web Worker WebAssembly
์ฃผ์š” ๋ชฉ์  UI์™€ ๋ถ„๋ฆฌ๋œ ๋น„๋™๊ธฐ ์‹คํ–‰ ๊ณ ์„ฑ๋Šฅ ์—ฐ์‚ฐ (C/C++/Rust ํ™œ์šฉ)
์žฅ์  JS๋งŒ์œผ๋กœ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ ๊ฐ€๋Šฅ ๋„ค์ดํ‹ฐ๋ธŒ์— ๊ฐ€๊นŒ์šด ์„ฑ๋Šฅ
๋‹จ์  ์„ฑ๋Šฅ์€ JS ํ•œ๊ณ„ ๋‚ด ํ•™์Šต/๋นŒ๋“œ ๊ณผ์ •์ด ํ•„์š”

์›น ํ™˜๊ฒฝ์—์„œ๋„ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๋Š” Web Worker, ๊ณ ์„ฑ๋Šฅ ์—ฐ์‚ฐ์€ WebAssembly๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ์„ฑ๊ฒฉ์— ๋งž์ถฐ ๋‘ ๊ธฐ์ˆ ์„ ์ ์ ˆํžˆ ์กฐํ•ฉํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์€ ์ง€๋‚œ ๋ช‡ ๋…„ ๋™์•ˆ ๋ˆˆ๋ถ€์‹œ๊ฒŒ ๋ฐœ์ „ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ณผ๊ฑฐ์—๋Š” ๋‹จ์ˆœํžˆ <script> ํƒœ๊ทธ๋กœ JS ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ค๋ฉด ๋˜์—ˆ์ง€๋งŒ, ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๊ณ  ๋ชจ๋“ˆ ๋‹จ์œ„ ๊ฐœ๋ฐœ์ด ๋ณดํŽธํ™”๋˜๋ฉด์„œ โ€œ๋ชจ๋“ˆ ๋ฒˆ๋“ค๋Ÿฌ(Module Bundler)โ€๊ฐ€ ํ•„์ˆ˜ ๋„๊ตฌ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ชจ๋“ˆ ๋ฒˆ๋“ค๋Ÿฌ์ธ Webpack, Rollup.js, ๊ทธ๋ฆฌ๊ณ  ์ฐจ์„ธ๋Œ€ ๋ฒˆ๋“ค๋ง ๋„๊ตฌ๋กœ ๊ฐ๊ด‘๋ฐ›๋Š” Vite์— ๋Œ€ํ•ด ๋น„๊ตํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


1. ๋ชจ๋“ˆ ๋ฒˆ๋“ค๋Ÿฌ๋ž€

๋ชจ๋“ˆ ๋ฒˆ๋“ค๋Ÿฌ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ JavaScript, CSS, ์ด๋ฏธ์ง€ ๋“ฑ์˜ ํŒŒ์ผ์„ ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„(Dependency Graph)๋กœ ๋ถ„์„ํ•ด ํ•˜๋‚˜์˜ ํŒŒ์ผ(๋˜๋Š” ์ตœ์ ํ™”๋œ ์—ฌ๋Ÿฌ ํŒŒ์ผ)๋กœ ๋ฌถ์–ด์ฃผ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๋ชฉ์ : ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํšจ์œจ์ ์œผ๋กœ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์ตœ์ ํ™”๋œ ๋ฒˆ๋“ค์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฒƒ.

์™œ ํ•„์š”ํ•œ๊ฐ€?

๐Ÿ‘€์†๋„ ๊ฐœ์„ : ํŒŒ์ผ์ด ๋งŽ์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ•˜๋‚˜์”ฉ ์š”์ฒญํ•ด์•ผ ํ•ด์„œ ๋А๋ ค์š”. ๋ฒˆ๋“ค๋Ÿฌ๊ฐ€ ํŒŒ์ผ์„ ํ•ฉ์ณ์„œ ์š”์ฒญ์„ ์ค„์—ฌ์ค๋‹ˆ๋‹ค.
๐Ÿ‘€์ตœ์ ํ™”: ์“ฐ์ด์ง€ ์•Š๋Š” ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐ(ํŠธ๋ฆฌ ์‰์ดํ‚น)ํ•˜๊ฑฐ๋‚˜, ์ฝ”๋“œ๋ฅผ ์••์ถ•ํ•ด ์šฉ๋Ÿ‰์„ ์ค„์—ฌ์ค๋‹ˆ๋‹ค.
๐Ÿ‘€๊ฐœ๋ฐœ ํŽธ์˜์„ฑ: ์ตœ์‹  ๋ฌธ๋ฒ•(ES6, TypeScript ๋“ฑ)์„ ๊ตฌ๋ฒ„์ „ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ๋ณ€ํ™˜ํ•ด ์ค๋‹ˆ๋‹ค.


2. ์ฃผ์š” ๋ชจ๋“ˆ ๋ฒˆ๋“ค๋Ÿฌ

์ด์ œ ๋งŽ์ด ์“ฐ์ด๋Š” 3๊ฐ€์ง€ ๋„๊ตฌ๋ฅผ ๋น„๊ตํ•ด๋ณผ๊ฒŒ์š”

Webpack

๊ฐ€์žฅ ์œ ๋ช…ํ•˜๊ณ  ์˜ค๋ž˜๋œ ๋ฒˆ๋“ค๋Ÿฌ
์„ค์ •์ด ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ๋ณต์žกํ•˜๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค
entry โ†’ loader โ†’ plugin โ†’ output ๊ตฌ์กฐ๋กœ ๋™์ž‘.

๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ์•ˆ์ •์ ์ด๊ณ  ํ™•์žฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ
React, Vue ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๊ธฐ๋ณธ ๋ฒˆ๋“ค๋Ÿฌ๋กœ ๋งŽ์ด ์“ฐ์˜€์Šต๋‹ˆ๋‹ค.
๋ณต์žกํ•œ ๋นŒ๋“œ(์ด๋ฏธ์ง€, CSS, JS, TS ๋‹ค ํ•ฉ์น˜๊ธฐ)์— ์œ ๋ฆฌ.

Rollup.js

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ์ž‘์— ๊ฐ•์ .
Webpack๋ณด๋‹ค ๋‹จ์ˆœํ•˜๊ณ  ๊ฐ€๋ณ์Šต๋‹ˆ๋‹ค.
ํŠธ๋ฆฌ ์‰์ดํ‚น(tree shaking) ๊ธฐ๋Šฅ์ด ๊ฐ•๋ ฅํ•ด์„œ, ์“ฐ์ง€ ์•Š๋Š” ์ฝ”๋“œ๊ฐ€ ๊ฑฐ์˜ ๋‚จ์ง€ ์•Š์•„์š”.

์˜ˆ์‹œ:
Lodash, D3.js ๊ฐ™์€ ์œ ๋ช…ํ•œ JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ Rollup์œผ๋กœ ๋นŒ๋“œ๋ฉ๋‹ˆ๋‹ค.
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ์ž‘ํ•  ๋•Œ๋Š” Webpack๋ณด๋‹ค Rollup์ด ๋” ์„ ํ˜ธ๋ฉ๋‹ˆ๋‹ค.


Vite

์ฐจ์„ธ๋Œ€ ๋ฒˆ๋“ค๋Ÿฌ๋กœ, ์ตœ๊ทผ ๊ฐ€์žฅ ํ•ซํ•ฉ๋‹ˆ๋‹ค.

๋‚ด๋ถ€์ ์œผ๋กœ Rollup์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜์ง€๋งŒ, ๊ฐœ๋ฐœ ์„œ๋ฒ„๋Š” ESBuild(Go ์–ธ์–ด๋กœ ๋งŒ๋“  ์ดˆ๊ณ ์† ๋ฒˆ๋“ค๋Ÿฌ)๋ฅผ ์‚ฌ์šฉ


๐Ÿ”ด์žฅ์ : ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์†๋„๊ฐ€ ์—„์ฒญ ๋น ๋ฆ„ (๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ์ฆ‰์‹œ ๋ฐ˜์˜ โ†’ HMR)

๐Ÿ”ด๋‹จ์ : ์•„์ง์€ Webpack๋งŒํผ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒํƒœ๊ณ„๊ฐ€ ๋ฐฉ๋Œ€ํ•˜์ง€ ์•Š์Œ


์˜ˆ์‹œ:
Vue 3, React ์ตœ์‹  ํ…œํ”Œ๋ฆฟ์—์„œ ๊ธฐ๋ณธ ๋นŒ๋“œ ๋„๊ตฌ๋กœ ์ฑ„ํƒ๋จ
์Šคํƒ€ํŠธ์—…์ด๋‚˜ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ์—์„œ ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•  ๋•Œ ์ตœ์ 


ํšŒ์‚ฌ์—์„œ๋„ ๊ธฐ์กด์— React + Webpack ๊ธฐ๋ฐ˜์œผ๋กœ ์šด์˜ํ•˜๋˜ ์„œ๋น„์Šค๋ฅผ Vite๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•œ ๊ฒฝํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ฌด๊ฑฐ์›Œ์ง€๋ฉด์„œ Webpack ํ™˜๊ฒฝ์—์„œ ๋นŒ๋“œ ์†๋„๊ฐ€ ์˜ค๋ž˜๊ฑธ๋ ค ์ดˆ๊ธฐ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•˜๋Š”๋ฐ์— 10๋ถ„ ์ •๋„๊ฐ€ ๊ฑธ๋ฆฌ๋Š” ์•„์ฃผ ํž˜๋“  ์ƒํ™ฉ์ด ์ƒ๊ฒจ๋ฒ„๋ฆฐ๊ฑฐ์ฃ ! HMR ์†๋„๋„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•จ๊ณผ ์ตœ์‹  ์ƒํƒœ๊ณ„์™€ ํ˜ธํ™˜์„ฑ์„ ๊ณ ๋ คํ•ด์„œ ๋…ผ์˜ ๊ฒฐ๊ณผ vite๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •์„ ํ–ˆ์Šต๋‹ˆ๋‹ค



๊ทธ ๊ฒฐ๊ณผ, ๊ฐœ๋ฐœ ์„œ๋ฒ„ ๊ตฌ๋™ ์†๋„๊ฐ€ 1๋ถ„ ๋ฏธ๋งŒ์œผ๋กœ ๋‹จ์ถ•, HMR์†๋„๋Š” ๋งํ•  ๊ฒƒ๋„ ์—†์ด ํ˜„์ €ํžˆ ๋นจ๋ผ์กŒ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก ์ ์œผ๋กœ, Vite๋Š” Webpack ๋Œ€๋น„ ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ์ œ๊ณตํ–ˆ๊ณ , ํŒ€์˜ ์ƒ์‚ฐ์„ฑ์—๋„ ๊ธ์ •์ ์ธ ํšจ๊ณผ๋ฅผ ์คฌ์Šต๋‹ˆ๋‹ค.


+ Recent posts