λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ“  archive

[Vue.js] λ°˜μ‘ν˜• μ‹œμŠ€ν…œμ˜ λ‚΄λΆ€ λ™μž‘ 방식 μ‚΄νŽ΄λ³΄κΈ°

by HandHand 2022. 3. 7.
πŸ’‘ λ³Έ ν¬μŠ€νŠΈλŠ” Vue 2.x λ₯Ό κΈ°μ€€μœΌλ‘œ μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Vue의 λ°˜μ‘ν˜• μ‹œμŠ€ν…œμ€ μ–΄λ–»κ²Œ λ™μž‘ν• κΉŒ?

이전 ν¬μŠ€νŠΈμ—μ„œ μ œκ°€ μ‚¬μ΄λ“œ ν”„λ‘œμ νŠΈλ₯Ό μ§„ν–‰ν•˜λ©° μ‹€μ œ λ§ˆμ£Όν–ˆλ˜ μ΄μŠˆμ™€ ν•¨κ»˜

Vue 의 λ°˜μ‘ν˜• μ‹œμŠ€ν…œμ— λŒ€ν•΄μ„œ κ°„λ‹¨ν•˜κ²Œ μ•Œμ•„λ΄€λ‹€λ©΄

이번 ν¬μŠ€νŠΈμ—μ„œλŠ” λ‚΄λΆ€ λ™μž‘μ— λŒ€ν•΄ μ’€ 더 깊이 μžˆλŠ” λ‚΄μš©λ“€μ„ μ•Œμ•„λ³΄λ €κ³  ν•©λ‹ˆλ‹€.

 

πŸ“Œ Vue λŠ” μ–΄λ–»κ²Œ μƒνƒœ λ³€ν™”λ₯Ό κ°μ§€ν• κΉŒ?

λ‹€μŒμ€ data λ₯Ό 화면에 λ³΄μ—¬μ£ΌλŠ” κ°„λ‹¨ν•œ Vue μ»΄ν¬λ„ŒνŠΈ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

<template>
    <div>
      <h1 class="hello">
        {{ name }}
      </h1>
        <input v-model="name" />
    <div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      name: "HandHand",
    };
  },
};
</script>

μ—¬κΈ°μ„œ name 값이 λ³€κ²½λ˜λ©΄ 이 값이 화면에 κ·ΈλŒ€λ‘œ λ°˜μ˜λ©λ‹ˆλ‹€.

Vue λ‚΄λΆ€μ μœΌλ‘œλŠ” 이 과정이 μ–΄λ–»κ²Œ μ§„ν–‰λ˜λŠ” κ²ƒμΌκΉŒμš”?

λ‹€μŒμ€ Vue 곡식 λ¬Έμ„œμ— μ†Œκ°œλ˜μ–΄μžˆλŠ” λ°˜μ‘ν˜•μ˜ λ™μž‘ 원리에 λŒ€ν•œ κ·Έλ¦Όμž…λ‹ˆλ‹€.

 

1

 

data μ†μ„±μ˜ ν• λ‹Ή 방식

Vue λŠ” data 둜 JavaScript 객체λ₯Ό ν• λ‹Ήν•˜κ²Œ 되면 Object.defineProperty λ₯Ό ν™œμš©ν•΄μ„œ

ν•΄λ‹Ή 객체에 getter / setter λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.

이 덕뢄에 λ°μ΄ν„°μ˜ λ³€ν™” 및 μ°Έμ‘°λ₯Ό 감지할 수 μžˆλŠ” 것이죠.

 

μ»΄ν¬λ„ŒνŠΈ watcher ν• λ‹Ή

λͺ¨λ“  Vue μ»΄ν¬λ„ŒνŠΈλŠ” watcher μΈμŠ€ν„΄μŠ€λ₯Ό κ°€μ§€κ²Œ λ©λ‹ˆλ‹€.

μ΄λŠ” μ»΄ν¬λ„ŒνŠΈκ°€ λ Œλ”λ§μ— ν•„μš”ν•œ λͺ¨λ“  μ˜μ‘΄μ„±μ„ μΆ”μ ν•˜λŠ” 역할을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

watcher κ°€ λ Œλ”λ§μ— μ˜μ‘΄ν•˜κ³  μžˆλŠ” λ°μ΄ν„°μ˜ setter κ°€ μ—…λ°μ΄νŠΈλ˜λ©΄ 이λ₯Ό κ°μ§€ν•˜κ³ 

μ»΄ν¬λ„ŒνŠΈμ˜ λ¦¬λ Œλ”λ§μ„ λ°œμƒμ‹œν‚€λŠ” κ²ƒμž…λ‹ˆλ‹€.

 

μ˜ˆμ‹œλ₯Ό 톡해 μ•Œμ•„λ΄…μ‹œλ‹€

방금 μ„€λͺ…ν•œ λ‚΄μš©μ„ μ•žμ„  μ˜ˆμ‹œλ₯Ό 톡해 μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

  1. data 인 name 은 h1 κ³Ό input μš”μ†Œμ—μ„œ 참쑰되고 μžˆμŠ΅λ‹ˆλ‹€.
  2. Vue κ°€ λ Œλ”λ§μ„ μˆ˜ν–‰ν•  λ•Œ h1 κ³Ό input 에 μ°Έμ‘°λ˜μ–΄μžˆλŠ” name 을 μ½μœΌλ©΄μ„œ (touch)
    getter κ°€ μˆ˜ν–‰λ˜κ³  이 λ‘˜μ˜ μ˜μ‘΄μ„± 정보λ₯Ό watcher μ—κ²Œ μ•Œλ¦½λ‹ˆλ‹€.
  3. 이후 데이터가 μˆ˜μ •λ˜μ—ˆμ„ λ•Œ setter κ°€ μˆ˜ν–‰λ˜λ©΄μ„œ μ»΄ν¬λ„ŒνŠΈκ°€ μ˜μ‘΄ν•˜κ³  μžˆλŠ”
    데이터 λ³€ν™”λ₯Ό watcher κ°€ κ°μ§€ν•˜λ©΄μ„œ 리 λ Œλ”λ§μ„ μ§€μ‹œν•©λ‹ˆλ‹€.

 

πŸ“Œ VueλŠ” λΉ„λ™κΈ°λ‘œ DOM을 μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.

비동기 DOM μ—…λ°μ΄νŠΈ Queue

이전 ν¬μŠ€νŠΈμ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄ Vue λŠ” λΉ„λ™κΈ°λ‘œ DOM μ—…λ°μ΄νŠΈλ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€.

data 의 λ³€ν™”κ°€ κ°μ§€λ˜λ©΄ 큐λ₯Ό μƒμ„±ν•œ λ’€ 이λ₯Ό push ν•©λ‹ˆλ‹€.

μ΄λ•Œ 같은 이벀트 λ£¨ν”„μ—μ„œ λ°œμƒλœ λͺ¨λ“  데이터 λ³€ν™”λ₯Ό 버퍼링 ν•˜μ—¬

λ™μΌν•œ watcher κ°€ μ—¬λŸ¬ 번 ν˜ΈμΆœλ˜λŠ” 것을 ν•œ 번의 호좜둜 μ΅œμ ν™”ν•œ λ’€ push ν•˜κ²Œ λ©λ‹ˆλ‹€.

이후 λ‹€μŒ 이벀트 루프가 tick λœλ‹€λ©΄, Vue λŠ” queue μ—μ„œ μž‘μ—…μ„ κΊΌλ‚΄

ν•„μš”ν•œ DOM μ—…λ°μ΄νŠΈλ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.

 

Vue.nextTick

Vue λŠ” 직접적인 DOM 접근을 ν†΅ν•œ 처리λ₯Ό μ§€μ–‘ν•˜κ³  data-driven λ°©μ‹μœΌλ‘œ

λ‘œμ§μ„ μ„Έμš°λŠ” 것을 ꢌμž₯ν•˜κ³  μžˆμ§€λ§Œ, 가끔 직접 DOM에 μ ‘κ·Όν•΄μ„œ 값을 가져와야 ν•  λ•Œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

 

μ•žμ„œ λ§ν–ˆλ“―μ΄ DOM μ—…λ°μ΄νŠΈλŠ” 이벀트 루프λ₯Ό 톡해 λΉ„λ™κΈ°μ μœΌλ‘œ μˆ˜ν–‰λ˜κΈ° λ•Œλ¬Έμ—

λ§Œμ•½ DOMμ—μ„œ μƒˆλ‘œ μ—…λ°μ΄νŠΈλœ 값을 μ°Έμ‘°ν•΄μ•Ό ν•œλ‹€λ©΄ Vue.nextTick 을 ν†΅ν•΄μ„œ

DOM이 μ—…λ°μ΄νŠΈλ₯Ό μ™„μ „νžˆ 마치기λ₯Ό κΈ°λ‹€λ €μ•Ό ν•©λ‹ˆλ‹€.

 

methods: {
  async updateName() {
    this.name = "JH";
    console.log(this.$el.textContent); // HandHand
    await this.$nextTick();
    console.log(this.$el.textContent); // JH
  },
}

 

πŸ“Œ Vue λ‚΄λΆ€ κ΅¬ν˜„ 듀여닀보기

μ΄λ²ˆμ—λŠ” μ‹€μ œ Vue 의 κ΅¬ν˜„ μ½”λ“œμ—μ„œ μœ„ 방식이 μ–΄λ–»κ²Œ κ΅¬μ„±λ˜μ–΄μžˆλŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

defineReactive

reactive ν•œ 속성을 μ •μ˜ν•˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.

ν•΄λ‹Ή ν•¨μˆ˜ λ‚΄λΆ€ κ΅¬ν˜„μ„ λ³΄μ‹œλ©΄ λ‹€μŒκ³Ό 같이 λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€.

이해도λ₯Ό 높이기 μœ„ν•΄ μ€‘μš”ν•œ λΆ€λΆ„λ§Œ λͺ…μ‹œν–ˆμŠ΅λ‹ˆλ‹€.

 

export function defineReactive(/** */) {
  const dep = new Dep();

  // ...

  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    // ...
    get: function reactiveGetter() {
      // ...
      dep.depend();
      if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
      // ...
    },
    set: function reactiveSetter(newVal) {
      // ...
      childOb = !shallow && observe(newVal);
      dep.notify();
    },
  });
}

 

λ³΄μ‹œλ‹€μ‹œν”Ό defineReactive λŠ” μΈμŠ€ν„΄μŠ€ data 의 각 킀듀에 λŒ€ν•΄ getter 와 setter λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.

 

1️⃣ getter

Dep 에 ν•΄λ‹Ή 속성을 μ˜μ‘΄μ„±μœΌλ‘œ μΆ”κ°€ν•©λ‹ˆλ‹€.

μ΄λ•Œ λ°°μ—΄μ˜ 경우 μž¬κ·€μ μœΌλ‘œ μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•˜λŠ” dependArray λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

 

2️⃣ setter

μƒˆλ‘œμš΄ 값에 λŒ€ν•œ Observerλ₯Ό μƒμ„±ν•œ λ’€ Dep 에 μ˜μ‘΄ν•˜λŠ” 값에 λ³€ν™”κ°€ μƒκ²Όλ‹€λŠ” 것을 notify ν•©λ‹ˆλ‹€.

 

Observer (Dep & Watcher)

Observer λŠ” observable ν•œ 각각의 객체에 ν• λ‹Ήλ©λ‹ˆλ‹€.

이λ₯Ό 톡해 μ˜μ‘΄μ„± λ³€ν™”λ₯Ό κ°μ§€ν•˜κ³  μ—…λ°μ΄νŠΈλ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.

 

class Observer {
  constructor(value: any) {
    this.dep = new Dep();

    def(value, "__ob__", this);

    if (Array.isArray(value)) {
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }

  walk(obj: Object) {
    /**
     * defineReactive λ₯Ό 톡해 객체의 λͺ¨λ“  속성을 λ°˜μ‘ν˜•μœΌλ‘œ λ§Œλ“­λ‹ˆλ‹€.
     */
  }

  observeArray(items: Array<any>) {
    /**
     * λ°°μ—΄μ˜ 각 μš”μ†Œμ— observer λ₯Ό ν• λ‹Ήν•©λ‹ˆλ‹€.
     */
  }
}

Observer λŠ” 객체 내뢀에 __ob__ 속성을 ν• λ‹Ήν•©λ‹ˆλ‹€.

λ˜ν•œ 배열에 λŒ€ν•œ 쒅속성 검사λ₯Ό μœ„ν•΄ μž¬κ·€μ μœΌλ‘œ Observer λ₯Ό μƒμ„±ν•˜λ©°

κ·Έ μ™Έ 객체의 경우 각 속성듀을 defineReactive λ₯Ό 톡해 λ°˜μ‘ν˜•μœΌλ‘œ λ§Œλ“­λ‹ˆλ‹€.

 

⭐️ Dep

class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor() {
    this.subs = [];
  }

  addSub(sub: Watcher) {
    this.subs.push(sub);
  }

  removeSub(sub: Watcher) {
    remove(this.subs, sub);
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }

  notify() {
    const subs = this.subs.slice();
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  }
}

Dep 은 λ‚΄λΆ€μ μœΌλ‘œ Watcher 배열을 μœ μ§€ν•©λ‹ˆλ‹€.

ꡬ독 - λ°œν–‰ λͺ¨λΈλ‘œ μ˜μ‘΄μ„±μ˜ λ³€ν™”λ₯Ό notify λ°›κ²Œ 되면 update λ₯Ό 톡해 Watcher μ—κ²Œ μ•Œλ¦½λ‹ˆλ‹€.

 

⭐️ Watcher

class Watcher {
  // ...

  update() {
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  }
}

 

πŸ“Œ μ°Έκ³  자료

Reactivity in Depth - Vue.js

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€