Skip to content

Latest commit

ย 

History

History
489 lines (343 loc) ยท 17.6 KB

vue3-reactivity.md

File metadata and controls

489 lines (343 loc) ยท 17.6 KB

Vue3์˜ ๋ฐ˜์‘ํ˜• ์•Œ์•„๋ณด๊ธฐ

Vue3์˜ ๋ฐ˜์‘ํ˜• ์•Œ์•„๋ณด๊ธฐ

๐Ÿ“• Reactivity In Depth

Models๋Š” proxied Javascript Object์ด๋‹ค. ๋ชจ๋ธ์„ ๋ณ€๊ฒฝํ•˜๋ฉด, ๋ทฐ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค. ์ด๊ฒŒ ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š”์ง€๋ฅผ ๋ทฐ์˜ ๋ฐ˜์‘ํ˜• ์‹œ์Šคํ…œ์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ํŒŒ์•…ํ•˜๋ฉด์„œ ์ดํ•ดํ•ด๋ณด์ž.

๐Ÿ“ Vue๋Š” ์–ด๋–ป๊ฒŒ ์ฝ”๋“œ๊ฐ€ Runningํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ์•Œ๊นŒ? : effect

value๊ฐ€ ๋ฐ”๋€” ๋•Œ, sum์ด ๋ฐ”๋€Œ๊ธฐ ์œ„ํ•ด์„œ๋Š” sum์„ ํ•จ์ˆ˜ ์•ˆ์— sum์„ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

const updateSum = () => {
  sum = val1 + val2
}

Vue์—๊ฒŒ ์–ด๋–ป๊ฒŒ ์ด ํ•จ์ˆ˜๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์„๊นŒ?

Vue๋Š” effect๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์ถ”์ ํ•œ๋‹ค. effect๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ์‹ธ๋Š” wrapper์ธ๋ฐ, ์ด๋Š” ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— tracking์„ ์‹œ์ž‘ํ•œ๋‹ค. ๋ทฐ๋Š” ์–ด๋– ํ•œ effect๊ฐ€ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ์–ด๋Š ์‹œ์ ์—์„œ๋“ ์ง€ ์•Œ ์ˆ˜ ์žˆ๊ณ , ํ•„์š”ํ•  ๋•Œ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด, ๋ทฐ๊ฐ€ ํ•˜๋Š” ์—ญํ• ๊ณผ ๋น„์Šทํ•œ ๊ฒƒ์„ ํ‰๋‚ด๋‚ด๋ณด์ž.

createEffect(() => {
  sum = val1 + val2
})

sum์„ ๊ฐ์‹ธ๋Š” createEffect๋ฅผ ๋งŒ๋“ ๋‹ค.

  • createEffect๋Š” sum์ด ์–ธ์ œ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ์ถ”์ ํ•ด์•ผ ํ•œ๋‹ค.
    1. ํ˜„์žฌ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š” effect๋“ค์„ ์ €์žฅํ•˜๋Š” runningEffects๋ผ๋Š” ๋ฐฐ์—ด์„ ๋‘”๋‹ค.

    2. effect๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ง์ „์— ์ž๊ธฐ ์ž์‹ ์„ runningEffects ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•œ๋‹ค.

      => ์–ด๋– ํ•œ effect๊ฐ€ ํ˜„์žฌ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š”์ง€๋Š” runningEffects ๋ฐฐ์—ด์„ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค.

// Maintain a stack of running effects
const runningEffects = []

const createEffect = fn => {
  // Wrap the passed fn in an effect function
  const effect = () => {
    runningEffects.push(effect)
    fn()
    runningEffects.pop()
  }

  // Automatically run the effect immediately
  effect()
}

Effects๋Š” ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๋“ค์˜ starting point๊ฐ€ ๋œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, component rendering๊ณผ computed property๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ effect๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์–ด๋–ค ๋ฐ์ดํƒ€๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ, ๋ฐ˜์‘ํ•˜๋Š” ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด, ๊ทธ๊ฒƒ์ด effect๋กœ ๊ฐ์‹ธ์ ธ์žˆ๊ตฌ๋‚˜๋ฅผ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ“ Vue๋Š” ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”๋ฅผ ์ถ”์ ํ• ๊นŒ

ํ˜„์žฌ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋Š” ํ•จ์ˆ˜๋Š” effect๋กœ ๊ฐ์‹ธ์„œ ์•Œ ์ˆ˜ ์žˆ์Œ์„ ์œ„์—์„œ ์‚ดํŽด๋ณด์•˜๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” 1. ๋ทฐ๊ฐ€ ์–ด๋–ป๊ฒŒ effect์™€ Data ๊ฐ„์˜ ์˜์กด๊ด€๊ณ„๋ฅผ ์•Œ์•„๋‚ด๋Š”์ง€ 2. ์–ด๋–ป๊ฒŒ Reactivity๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š”์ง€๋ฅผ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค.

local ๋ณ€์ˆ˜๋ฅผ ์žฌํ• ๋‹นํ•˜๋Š” ๊ฒƒ์€ ์ถ”์ ํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋Ÿฌํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—๋Š” ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๊ฒฝ์šฐ์ด๋‹ค.

์ปดํฌ๋„ŒํŠธ์˜ data ํ•จ์ˆ˜๊ฐ€ plain js object๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๋ทฐ๋Š” ๊ทธ object์™€ get, set handler๋ฅผ Proxy๋กœ ๊ฐ์‹ผ๋‹ค.

์ฐธ๊ณ : Proxy๋Š” ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ interceptํ•˜์—ฌ ์›ํ•˜๋Š” operation์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด์ด๋‹ค. Reflect๋Š” this binding์ด Proxy๋กœ ๋˜๋„๋ก ํ•ด์ค€๋‹ค.

๋น ๋ฅด๊ฒŒ ์œ„์˜ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต์„ ๋‚ด๋ฆฌ์ž๋ฉด, 1. ๋ทฐ๊ฐ€ ์–ด๋–ป๊ฒŒ effect์™€ Data ๊ฐ„์˜ ์˜์กด๊ด€๊ณ„๋ฅผ ์•Œ์•„๋‚ด๋Š”๊ฐ€

  • ํ”„๋ก์‹œ๋กœ get์„ interceptํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ์™€ effect๊ฐ„์˜ ์˜์กด๊ด€๊ณ„๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  1. ์–ด๋–ป๊ฒŒ Reactivity๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š”๊ฐ€
    • ํ”„๋ก์‹œ๋กœ set์„ interceptํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ effect๋ฅผ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, property, receiver) {
    track(target, property)
    return Reflect.get(...arguments)
  },
  set(target, property, value, receiver) {
    trigger(target, property)
    return Reflect.set(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// tacos

1. data์™€ effect ๊ฐ„์˜ ์˜์กด ๊ด€๊ณ„ ํŒŒ์•…ํ•˜๊ธฐ : intercept get

const handler = {
  get(target, property, receiver) {
    track(target, property)
    return Reflect.get(...arguments)
  },
  ...
}

Proxy๋กœ Reactivity๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ Property๊ฐ€ ์ฝํ˜€์งˆ ๋•Œ, ์–ด๋– ํ•œ effect๊ฐ€ ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•˜์˜€๋Š”์ง€๋ฅผ ์•Œ์•„๋‚ด๋Š” ๊ฒƒ์ด๋‹ค.

์ด๋ฅผ ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ˜„์žฌ ์ด ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•œ ๊ฒƒ์ด ๋ฌด์—‡์ธ์ง€๋ฅผ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” getter๋ฅผ interceptํ•ด์•ผ ํ•œ๋‹ค.

ํ”„๋ก์‹œ์˜ get handler๋Š” track์ด๋ผ๋Š” ํ•จ์ˆ˜์— argument๋กœ target๊ณผ property๋ฅผ ๋„ฃ์–ด ํ˜ธ์ถœํ•œ๋‹ค.

track ํ•จ์ˆ˜๋Š” ํ˜„์žฌ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š” effect๊ฐ€ ๋ฌด์—‡์ธ์ง€ ํ™•์ธํ•˜๊ณ , target, property์™€ ๊ฐ™์ด ๊ธฐ๋กํ•œ๋‹ค.

์ด๋ ‡๊ฒŒํ•˜์—ฌ ๋ทฐ๋Š” ํ•ด๋‹น effect๊ฐ€ target์˜ property์— ์˜์กดํ•˜๊ณ  ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

2. data๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ, ๊ทธ ๊ฐ’์— ์˜์กดํ•˜๋Š” effect ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ค๊ธฐ : intercept set

const handler = {
  set(target, property, value, receiver) {
    trigger(target, property)
    return Reflect.set(...arguments)
  },
  ...
}

์–ด๋– ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, setter๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.

Proxy์˜ ํ•ธ๋“ค๋Ÿฌ๋Š” set์„ ๊ฐ€๋กœ์ฑ„์„œ, ํ˜„์žฌ target์˜ property์— ์˜์กดํ•˜๊ณ  ์žˆ๋Š” effects๋“ค์„ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚จ๋‹ค.

์ •๋ฆฌ

  • dependency-tracking์€ handler.get์—์„œ ์ฒ˜๋ฆฌ
  • change-notification์€ handler.set์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

Proxied Objects

Vue๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ reactiveํ•˜๊ฒŒ ๋งŒ๋“  ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ์ถ”์ ํ•˜๊ณ  ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ์ฒด์— ๋Œ€ํ•ด ๊ฐ™์€ ํ”„๋ก์‹œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๋งŒ์•ฝ reactive proxy์— ์žˆ๋Š” ์ค‘์ฒฉ๋œ ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•œ๋‹ค๋ฉด, ๊ทธ ์ค‘์ฒฉ๋œ ๊ฐ์ฒด ๋˜ํ•œ ํ”„๋ก์‹œ๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

const handler = {
  get(target, property, receiver) {
    track(target, property)
    const value = Reflect.get(...arguments)
    if (isObject(value)) {
      // Wrap the nested object in its own reactive proxy
      return reactive(value)
    } else {
      return value
    }
  }
  // ...
}

Proxy vs original identity

Proxy ๊ฐ์ฒด์™€ ์˜ค๋ฆฌ์ง€๋‚  ๊ฐ์ฒด๋Š” ===๋กœ ํŒ๋‹จํ–ˆ์„ ๋•Œ ๊ฐ™์ง€ ์•Š๋‹ค. .includes()๋‚˜ .indexOf()์™€ ๊ฐ™์ด strict equality comparison์„ ํ•˜๋Š” ์—ฐ์‚ฐ์—๋„ ์ด๋Ÿฌํ•œ ํŠน์„ฑ์ด ์˜ํ–ฅ์„ ๋ผ์น˜๊ฒŒ ๋œ๋‹ค.

const obj = {}
const wrapped = new Proxy(obj, handlers)

console.log(obj === wrapped) // false

๊ฐ€์žฅ ์ข‹์€ ๊ฒƒ์€ ์˜ค๋ฆฌ์ง€๋‚  ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐ(Reference)ํ•˜๋Š” ๊ฒƒ์„ ๋‘์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค. ์˜ค๋กœ์ง€ reactiveํ•œ ๊ฐ์ฒด๋กœ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์•ผ equality comparison๊ณผ reactivity๊ฐ€ ์›ํ•˜๋˜๋Œ€๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค.

const obj = reactive({
  count: 0
}) // no reference to original

vue๋Š” primitive value์— ๋Œ€ํ•ด์„œ๋Š” Proxy๋กœ ๊ฐ์‹ธ์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ primitive value๋Š” === ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

const obj = reactive({
  count: 0
})

console.log(obj.count === 0) // true

๐Ÿ“ ๋ Œ๋”๋ง์€ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”์— ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ์„๊นŒ

์ปดํฌ๋„ŒํŠธ์˜ ํ…œํ”Œ๋ฆฟ์€ render ํ•จ์ˆ˜๋กœ ์ปดํŒŒ์ผ๋œ๋‹ค. render ํ•จ์ˆ˜๋Š” VNodes๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. VNode๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋Š”์ง€๋ฅผ ์„ค๋ช…ํ•œ๋‹ค. ๋ทฐ๋Š” VNodes๋ฅผ effect๋กœ ๊ฐ์‹ธ์„œ, ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์–ด๋– ํ•œ ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•˜์˜€๋Š”์ง€๋ฅผ ์•Œ์•„๋‚ธ๋‹ค.

render ํ•จ์ˆ˜๋Š” ๊ฐœ๋…์ ์œผ๋กœ computed ํ”„๋กœํผํ‹ฐ์™€ ๋งค์šฐ ์œ ์‚ฌํ•˜๋‹ค. ๋ทฐ๋Š” ์ •ํ™•ํžˆ ์–ด๋–ป๊ฒŒ dependency๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€๋ฅผ ์ถ”์ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค์ง ๊ทธ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์–ด๋– ํ•œ ์‹œ์ ์— dependency๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€๋งŒ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ์ค‘ ์–ด๋– ํ•œ ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋‚˜์ค‘์— ๋ณ€๊ฒฝ์ด๋˜๋ฉด, effect๋ฅผ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ค๊ฒŒ๋”ํ•œ๋‹ค. render ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‹คํ–‰์‹œ์ผœ์„œ ์ƒˆ๋กœ์šด VNode๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋‹ค. ์ด VNode๋Š” DOM์— ํ•„์š”ํ•œ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

๐Ÿ“• Reactivity Fundamentals

๐Ÿ“ Reactive State ์„ ์–ธํ•˜๊ธฐ : reactive

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด์—์„œ reactive state๋ฅผ ๋งŒ๋“œ๋ ค๋ฉด, reactive method๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

import { reactive } from 'vue';

const state = reactive({
    count: 0,
})

reactive๋Š” vue2์˜ Vue.observable() API์™€ ๊ฐ™๊ณ , ์ด๋ฆ„๋งŒ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’์€ ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด์ด๋‹ค. ๋ฐ˜์‘ํ˜•์œผ๋กœ ์ „ํ™˜๋˜๋Š” ๊ฒƒ์€ "deep"ํ•˜๊ฒŒ ์ด๋ค„์ง„๋‹ค. ์ฆ‰, ๋ชจ๋“  ์ค‘์ฒฉ๋œ ํ”„๋กœํผํ‹ฐ๋“ค๋„ ๋‹ค ๋ฐ˜์‘ํ˜•์ด๋‹ค.

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

data() ํ•จ์ˆ˜์—์„œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๊ทธ ๊ฐ์ฒด๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ reactive()๋กœ ์ธํ•˜์—ฌ ๋ฐ˜์‘ํ˜•์ด ๋œ๋‹ค. render ํ•จ์ˆ˜๋กœ ์ปดํŒŒ์ผ ๋˜๋Š” ํ…œํ”Œ๋ฆฟ์ด ๋ฐ˜์‘ํ˜• ํ”„๋กœํผํ‹ฐ๋“ค์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

๐Ÿ“ ๋…๋ฆฝ์ ์ธ ๋ฐ˜์‘ํ˜• ๊ฐ’ ๋งŒ๋“ค๊ธฐ : ref

๋…๋ฆฝ์ ์ธ primitive value์ธ String์„ ๋ฐ˜์‘ํ˜•์œผ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž. ๋ฌผ๋ก  ํ•˜๋‚˜์˜ String ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ–๋Š” ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ reactive์— ๋„˜๊ธฐ๋ฉด ๋œ๋‹ค. ๋ทฐ์—๋Š” ์ด์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ ref๋ผ๊ณ  ํ•œ๋‹ค.

ref๋Š” value๋ผ๋Š” ํ”„๋กœํผํ‹ฐ ํ•˜๋‚˜๋งŒ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ฐ˜์‘ํ˜•์ด์ž ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

Ref Unwrapping

ref๊ฐ€ render context(setup()์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ์ฒด)์—์„œ ๋ฆฌํ„ด๋˜๊ณ , ํ…œํ”Œ๋ฆฟ์—์„œ ์ ‘๊ทผํ•˜๊ฒŒ ๋  ๋•Œ, ์ž๋™์œผ๋กœ ๋‚ด๋ถ€์˜ ๊ฐ’์„ shallow unwrapํ•œ๋‹ค. ๋”ฐ๋ผ์„œ, ์ค‘์ฒฉ๋œ ref์—๋งŒ .value๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

<template>
  <div>
    <span>{{ count }}</span>
    <button @click="count++">Increment count</button>
    <button @click="nested.count.value++">Nested Increment count</button>
  </div>
</template>

<script>
  import { ref } from 'vue'
  export default {
    setup() {
      const count = ref(0)
      return {
        count,

        nested: {
          count
        }
      }
    }
  }
</script>

TIP ์‹ค์ œ ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋ฉด, reactive๋กœ ๊ฐ์Œ€ ์ˆ˜ ์žˆ๋‹ค.

nested: reactive({
    count
})
<div>nested: {{nested.count}}</div>

๋ฐ˜์‘ํ˜• ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๊ธฐ

ref๊ฐ€ ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ๋กœ์„œ ์ ‘๊ทผ๋˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝ๋  ๋•Œ, ์ž๋™์œผ๋กœ ๋‚ด๋ถ€์˜ ๊ฐ’์„ unwrapํ•˜์—ฌ ์ผ๋ฐ˜์ ์ธ ํ”„๋กœํผํ‹ฐ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋„๋ก ํ•œ๋‹ค.

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0 -> ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ๋กœ ์ ‘๊ทผ

state.count = 1
console.log(count.value) // 1 -> ref๋Š” .value๋กœ ๋‚ด๋ถ€ ๊ฐ’์— ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค.

Collection Type์—์„œ์˜ ๋ฐ˜์‘ํ˜• ์ ‘๊ทผ -> unwrapping์ด ๋˜์ง€ ์•Š์Œ.

  • Ref unwrapping์€ ๋ฐ˜์‘ํ˜• Object ๋‚ด๋ถ€์—์„œ ์ค‘์ฒฉ๋  ๊ฒฝ์šฐ์—๋งŒ ์ด๋ฃจ์–ด์ง„๋‹ค.
  • ref๊ฐ€ Array๋‚˜ Map์ฒ˜๋Ÿผ collection type์ธ ๊ฒƒ๋“ค์—์„œ ์ ‘๊ทผ๋  ๋•Œ๋Š” unwrapping์ด ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋•Œ๋Š” .value๋กœ ๋‚ด๋ถ€์˜ ๊ฐ’์— ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค.
const books = reactive([ref('value~')]);
console.log(books[0].value); // value~

const map = reactive(new Map([['count', ref(0)]]));
console.log(map.get('count').value); // 0

๐Ÿ“ Destructing Reative State : toRefs

๋‹ค์Œ๊ณผ ๊ฐ™์ด reactive object๋ฅผ ES6 destructing์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜๋ฉด, reactivity๊ฐ€ ์‚ฌ๋ผ์ง€๊ฒŒ ๋œ๋‹ค.

import { reactive } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = book // reactivity๊ฐ€ ์‚ฌ๋ผ์ง€๊ฒŒ ๋จ

Reactivity๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด๋ฅผ set of refs๋กœ ๋ณ€๊ฒฝ์‹œ์ผœ์•ผ ํ•œ๋‹ค.

set of refs๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” toRefs API๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

toRefs๋Š” ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด๋ฅผ ๊ฐ ํ”„๋กœํผํ‹ฐ๊ฐ€ ref์ธ plain object๋กœ ๋ณ€๊ฒฝ์‹œํ‚จ๋‹ค.

์ด ref๋“ค์€ source object์™€ reative connection์„ ์œ ์ง€ํ•˜๊ฒŒ ๋œ๋‹ค.

toRefs๋กœ ๊ฐ ํ”„๋กœํผํ‹ฐ๋“ค์€ ref๊ฐ€ ๋˜์—ˆ์œผ๋ฏ€๋กœ, ์ด ๋‚ด๋ถ€ ๊ฐ’์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” .value๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // we need to use .value as title is a ref now
console.log(book.title) // 'Vue 3 Detailed Guide'

๐Ÿ“ readonly๋กœ ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ

ref, reactive๋กœ ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ณ€๊ฒฝ์„ ์ถ”์ ํ•˜๊ฒŒ ๋œ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๊ฐ€๋”์€ ์–ด๋– ํ•œ ์ง€์ ์—์„œ ๋ณ€ํ™”๋ฅผ ๋ง‰์•„์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธด๋‹ค.

๋ฐ์ดํƒ€๋ฅผ provideํ•  ๋•Œ, injectํ•œ ์ชฝ์—์„œ ๊ทธ ๊ฐ’์„ ๋ณ€๊ฒฝ ๋ชป์‹œํ‚ค๊ฒŒ ํ•ด์•ผํ•  ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์— readonly๋กœ ๊ฐ์‹ธ์„œ ๋ณ€๊ฒฝ์‹œํ‚ค์ง€ ๋ชปํ•˜๊ฒŒ ํ•ด์•ผ ํ•œ๋‹ค.

<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    provide('location', readonly(location))
  }
}
</script>

๐Ÿ“• Computed and Watch

๐Ÿ“ Computed Values

computed property๋Š” state์— ์˜์กดํ•˜๊ณ  ์žˆ๋Š” state์ด๋‹ค. computed๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด, computed method๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

computed computed method๋Š” getter ํ•จ์ˆ˜๋ฅผ ๋ฐ›์•„์„œ, getter๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ฐ’์—์„œ ๋ถˆ๋ณ€ ๋ฐ˜์‘ํ˜• ref ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•œ๋‹ค.

computed๊ฐ’์€ readonly๋‹ค. ๋”ฐ๋ผ์„œ, ์ด ๊ฐ’์„ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ๊ฒƒ์€ ํ•  ์ˆ˜ ์—†๋‹ค.

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // Write operation failed: computed value is readonly
  • computed์—์„œ Writable ref object ๋ฐ˜ํ™˜๋ฐ›๊ธฐ

computed๊ฐ€ get, set ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ›์œผ๋ฉด, writable ref ๊ฐ์ฒด๊ฐ€ ๋œ๋‹ค.

const count = ref(1);
const plusOne = computed(() => count.value + 1);
const plusTen = computed({
  get: () => count.value + 10,
  set: (val) => {
    count.value = val;
  },
});
console.log(plusOne.value); //2
console.log(plusTen.value); //11
plusTen.value = 100;
console.log(count.value); //100

๐Ÿ“ watchEffect

๋ฐ˜์‘ํ˜• state์— ๋”ฐ๋ผ side effect๋ฅผ ์ž๋™์œผ๋กœ ์‹คํ–‰์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ, watchEffect ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. watchEffect๋Š” dependency๋ฅผ trackingํ•  ๋•Œ ์ฆ‰์‹œ ์‹คํ–‰๋˜๊ณ , dependency๊ฐ€ ๋ณ€๊ฒฝ๋ ๋•Œ๋งˆ๋‹ค ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚จ๋‹ค.

watchEffect()๋Š” DOM์ด ๋งˆ์šดํŠธ๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค.

const count = ref(0);
watchEffect(() => console.log(count.value)); // logs 0 : dependency trackingํ•˜๋ฉฐ ์ฆ‰์‹œ ์‹คํ–‰
setTimeout(() => {
  count.value++; // logs 1 -> dependency ๋ณ€๊ฒฝ๋˜๋ฉฐ ๋‹ค์‹œ ์‹คํ–‰
}, 100);

Stopping the Watcher

watchEffect๊ฐ€ setup()์ด๋‚˜ lifecycle hooks์—์„œ ํ˜ธ์ถœ๋  ๋–„, watcher๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋ผ์ดํ”„์‚ฌ์ดํด๊ณผ ์—ฐ๊ฒฐ๋˜๋ฉฐ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋  ๋•Œ ์ž๋™์œผ๋กœ ๋ฉˆ์ถฐ์ง„๋‹ค.

๋ช…์‹œ์ ์œผ๋กœ Watcher๋ฅผ ๋ฉˆ์ถ”๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ, watchEffect์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ด ๋ฐ˜ํ™˜๊ฐ’์€ stop handler์ธ๋ฐ, ์ด๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ watcher๋ฅผ ๋ฉˆ์ถœ ์ˆ˜ ์žˆ๋‹ค.

  setup() {
    const count = ref(0);
    const stop = watchEffect(() => console.log(count.value)); // logs 0
    stop(); // stop watcher
    setTimeout(() => {
      count.value++; // Watcher๊ฐ€ ๋ฉˆ์ถฐ์กŒ๊ธฐ์—, ๋กœ๊ทธ 1์ด ์ฐํžˆ์ง€ ์•Š๋Š”๋‹ค. 
    }, 100);
  },

Side Effect Invalidation

watched effect ํ•จ์ˆ˜๊ฐ€ ๋น„๋™๊ธฐ side effect๋ฅผ ์ˆ˜ํ–‰ํ•  ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. ๊ทธ ๋น„๋™๊ธฐ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋Š” invalidateํ•œ ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธฐ๋ฉด, ํด๋ฆฐ์—…๋˜์–ด์•ผ ํ•œ๋‹ค.

Effect ํ•จ์ˆ˜๋Š” onInvalidate ํ•จ์ˆ˜๋ฅผ ๋ฐ›๋Š”๋ฐ, ์ด ํ•จ์ˆ˜๋Š” invalidation callback์œผ๋กœ ๋“ฑ๋ก๋œ๋‹ค.

invalidation callback์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์— ํ˜ธ์ถœ๋œ๋‹ค.

  • effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๋Š” ์‹œ์ 
  • watcher๊ฐ€ ๋ฉˆ์ถ”๋Š” ์‹œ์  (i.e. watchEffect๊ฐ€ setup์ด๋‚˜ lifecycle hook์— ๋“ฑ๋ก๋˜์—ˆ์„ ๋•Œ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋˜๋Š” ์‹œ์ )

invalidation callback์„ ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ๋„˜๊ฒจ์„œ ๋“ฑ๋กํ•˜์˜€๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋ฆฌํ„ด๊ฐ’์ด async ์—๋Ÿฌ ํ•ธ๋“ค๋ง์— ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋‹ค์Œ ์˜ˆ๊ฐ€, data fetchingํ•  ๋•Œ, async effect function์˜ ํ”ํ•œ ์˜ˆ์ด๋‹ค.

const data = ref(null)
watchEffect(async (onInvalidate) => {
  onInvalidate(() => { /* ... */ }) // we register cleanup function before Promise resolves
  data.value = await fetchData(props.id)
})

async ํ•จ์ˆ˜๋Š” ๋‚ด์žฌ์ ์œผ๋กœ ํ”„๋ผ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ํด๋ฆฐ์—… ํ•จ์ˆ˜๋Š” ํ”„๋ผ๋ฏธ์Šค๊ฐ€ resolve๋˜๊ธฐ ์ „์— ๋“ฑ๋ก๋˜์–ด์•ผ ํ•œ๋‹ค. ๋˜ํ•œ, ๋ทฐ๋Š” ํ”„๋ผ๋ฏธ์Šค ์ฒด์ธ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์—๋Ÿฌ๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ˜ํ™˜๋˜๋Š” ํ”„๋ผ๋ฏธ์Šค์— ์˜์กดํ•œ๋‹ค.

In addition, Vue relies on the returned Promise to automatically handle potential errors in the Promise chain.

๐Ÿ–ค ์ถœ์ฒ˜