# Vue2의 watch에서는 감시하지만, Vue3의 watch에서는 감시하지 못하는 이유

기존에 Vue2에서 아래 코드가 존재한다고 가정해보자, 여기서 watchIocList는 업데이트되는 iocList의 value 값을 통해 추가 작업을 하는 무언가의 함수이다.

```
watch: {
    iocList: (val) {
        this.watchIocList(val);
    }
}
```

여기서 그대로 Vue3로 코드 베이스를 옮겼다고 추가로 가정을 해보자, 그러면 아래 코드와 같이 watch 코드를 옮길 수 있다.

```
watch(iocList, (val) => watchIocList(val));
```

그런데 여기서 문제가 하나가 발생한다, 아래 코드를 실행했을 때 Vue2와 Vue3에서 같은 동작을 하지만 실행 결과는 다르게 나오게 된다.

```
// vue2
this.iocList.push({ value: 'abcd' });
 
// vue3
iocList.value.push({ value: 'abcd' });
```

Vue2에서는 watchIocList를 통해 정상 동작을 하던 코드가, Vue3에서는 동작하지 않는 이슈가 발생한다, 왜 그럴까?

## Vue2의 Watch 특성

Vue2는 Vue3와 다르게 Proxy를 사용하지 않으며, Object.defineProperty를 통해 관련 상태 값들을 관리한다.

> Vue2는 인덱스 직접 접근 arr[0] = val나, length 변경은 감지하지 못하지만 Vue3는 Proxy가 배열 및 객체의 접근 자체를 추적하기 때문에 이런 것들이 가능하다.

이 방식의 단점은 배열이나 객체에 새롭게 추가되는 요소를 감지하지 못한다는 치명적인 단점을 가지고 있는데, Vue2에서는 이를 해결하기 위해 내부적으로 배열 인터셉트를 제공한다. 배열의 mutation 메서드들을 직접 래핑하여 사용하는 것이다.

총 7가지의 메서드를 래핑하여 제공을 하고있다.

- push

- pop

- shift

- unshift

- splice

- sort

- reverse

```
// Vue 2 내부 동작 (실제 구현과 유사)
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
 
methodsToPatch.forEach(method => {
  const original = Array.prototype[method]
  def(arrayProto, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    ob.dep.notify()  // ← 참조 변경 없이 직접 알림!
    return result
  })
})
```

watch는 기본적으로 참조가 변경되어야 트리거되면서 동작을 하게 되는데, 위와 같이 직접 래핑되어 사용을 하면서 Vue2에서는 this.iocList.push({ value: abcd }) 통해서도 참조 변경 없이 동작이 되는 것이다.

Vue3에서는 기본적으로 이러한 배열 메서드가 없이 오로지 Proxy로 관리되기 때문에 Vue3에서는 Vue2의 push를 통한 트리거가 동작되지 않는다.

```
watch(iocList, (val) => watchIocList(val), { deep: true });
```

그렇기 때문에 Vue3에서는 deep 메서드를 통해 배열 객체 내부까지 감시를 해줘야한다.

내가 처음부터 코드를 작성했다면 코드가 많아질 수록 watch 의존성 추적이 힘들어서 최대한 자제를 했겠지만, 마이그레이션을 하면서 사이드 이펙트를 줄이기 위해 그대로 사용하면서.. 

deep: true는 배열 및 객체 내부까지 감시를 하면서 감시 뎁스가 더 깊어져 의존성 추적이 더욱 더 어렵게 된다. 그래서 아래와 같이 Vue3의 .value 참조를 명시적으로 변화시키는 방법도 있다.

```
iocList.value = [...iocList.value, { value: 'abcd' }]
```

## 그 밖에 Vue2에서만 존재하는 반응성 특이점

Vue2에서 Vue3 코드를 변경하면서 마술같은 동작들이 생각보다 많은데, 무언가 내부 코어 개발자들이 상태 값을 위한 추가 작업들을 많이 해놓은 것처럼 보였다. 이 밖에도 Vue3에는 존재하지 않고, Vue3에만 존재하는 반응성 특이점들이 존재한다.

### 배열 인덱스 직접 접근

배열 인덱스에 직접 접근을 시도할 떄 차이를 보인다.

```
// Vue2에서는 접근이 되지 않는다.
this.list[0] = 'new value';
 
// Vue2에서는 이렇게 접근해야한다.
this.$set(this.list, 0, 'new value');
Vue.set(this.list, 0, 'new value');
 
// Vue3에서는 가능하다.
list.value[0] = 'new value';
```

### 객체 속성 추가 및 삭제

객체 속성을 추가하거나 삭제할 때 차이를 보인다.

```
// Vue2에서는 감지가 되지 않는다.
this.obj.newKey= 'value';
delete this.obj.existingKey;
 
// Vue2에서는 이렇게 추가 및 삭제해야한다.
this.$set(this.obj, 'newKey', 'value');
this.$delete(this.obj, 'existingKey');
 
// Vue3에서는 가능하다.
obj.value.newKey = 'value';
delete obj.value.existingKey;
```

### 배열의 길이를 직접 변경할 때

배열의 길이를 변경할 때 차이를 보인다.

```
// Vue2에서는 감지하지 못한다.
this.list.length = 0;
 
//
this.list.splice(0);
 
// Vue3에서는 가능하다.
list.value.length = 0;
```

### Data에 초기 선언하지 않은 속성은 반응성을 감지하지 못한다.

Vue2의 특성으로 인해 차이를 보인다.

```
// Vue 2 ❌ 나중에 추가한 속성은 반응성 없음
export default {
  data() {
    return { obj: {} }  // 처음에 없던 속성은 반응성 X
  },
  mounted() {
    this.obj.name = 'test'  // ❌ 반응성 없음!
  }
}
 
// Vue 3 ✅ 언제 추가해도 반응성 있음
```

### 반응성의 한계로 인해 강제 업데이트 메서드를 제공한다.

Vue2에서는 반응성의 한계로 인해 강제 업데이트 메서드를 제공한다.

```
// Vue 2 - 반응성 한계 때문에 강제 업데이트 메서드가 있었음
this.$forceUpdate()
```

---

Vue2에서 Vue3로 전환을 하는데 상태 처리 때문에 살짝 난관을 겪었다. 이렇게 사이드 이펙트가 종종 나오는걸 보아하니.. Vue3 다음은 React로의 전환인데 Vue의 자동 상태 처리들을 React에서 명시적으로 어떻게 변경해야할지에 대한 고민들이 보이긴하지만.. 마이그레이션을 하고나면 한층 더 성장해있는 것 같다 ^,^..

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
