We have been using the new Composition API with Vue 2 for some time now and recently experimented with porting some projects to Vue 3. However we have a significant issue with the changes to reactivity in Vue 3.
The fundamental issue we have with the re-write of reactivity in Vue 3 is that changes to raw objects are not tracked - only changes to the proxies generated by ref/reactive are now tracked. This means the same code in Vue 2 with Compoistion API does not work the same in Vue 3.
With Vue 2 reactivity, it was an excellent feature that you could have objects created by business logic shared between Vue components and when the business logic changed properties of these objects, the UI reacted accordingly. However, with Vue 3, this no longer works unless you generate a proxy using ref/reactive in the UI code and somehow pass that proxy back to the business logic layer (or get Vue to wrap the object in a proxy within the business logic code which is otherwise independent/unaware of Vue).
As a simple example, the following Vue component works completely differently in Vue 2 and Vue 3.
<template>
<div>
<button v-on:click="onClick('raw')">rawObj</button>
<button v-on:click="onClick('ref1')">ref1</button>
<button v-on:click="onClick('ref2')">ref2</button>
<button v-on:click="onClick('reactive')">reactiveObj</button>
<div>objRef1: {{ref1.counter}}</div>
<div>objRef2: {{ref2.counter}}</div>
<div>reactiveObj: {{reactiveObj.obj.counter}}</div>
</div>
</template>
<script lang="ts">
// Vue 2
import { defineComponent, ref, reactive, watch } from '@vue/composition-api';
// Vue 3
// import { defineComponent, ref, reactive, watch } from 'vue'
export default defineComponent({
name: 'CodeSample',
setup() {
const rawObject = {
counter: 1
}
const ref1 = ref(rawObject);
const ref2 = ref(rawObject);
const reactiveObj = reactive({
obj: rawObject
});
watch(
() => rawObject.counter,
(newValue,oldValue) => {
console.log('watch raw', `${oldValue} -> ${newValue}`);
}
);
watch(
() => ref1.value.counter,
(newValue,oldValue) => {
console.log('watch ref1', `${oldValue} -> ${newValue}`);
}
);
function onClick(what:string) {
switch (what) {
case 'raw':
rawObject.counter++;
break;
case 'ref1':
ref1.value.counter++;
break;
case 'ref2':
ref2.value.counter++;
break;
case 'reactive':
reactiveObj.obj.counter++;
break;
}
console.log('raw', rawObject.counter);
console.log('ref1', ref1.value.counter);
console.log('ref2', ref2.value.counter);
console.log('reactive', reactiveObj.obj.counter);
}
return {
ref1,
ref2,
reactiveObj,
onClick,
}
}
});
</script>
In Vue 2, the raw object used to create the two refs and the reactive object can be manipulated and everything reacts including both watches as can be seen by clicking the first button.
In Vue 3, manipulating the raw object does change the value in the proxies but none of the reactivity works including the raw watch. However, modifying either of the refs or the reactive object works the same as with Vue 2.
For me, this is a big backwards step because essentially with Vue 3, only objects within the UI code can be reactive - objects from non-UI code (business logic) cannot be updated outside of the UI without polluting the non-UI code.
In addition, we have found that the proxy mechanism used by Vue 3 reactivity can break business logic objects that themselves already had some form of proxying whereas Vue.Observable from Vue 2 worked and didn’t break anything. In particular, we have projects using BreezeJS which creates entity objects for records in a database and these entity objects break when made reactive by Vue 3 and are no longer able to track property changes themselves (i.e. they only work if the properties are changed on the raw unproxied objects).
Finally, the fact that watch only works for reactive objects and their properties means there is no easy way round this problem because you cant create a watch on a property of the unwrapped object to pick up changes made outside the UI code.
What might be good would be the option to use Vue 2’s Observable as an alternative for proxied refs in Vue 3 (e.g. an observableRef function to create a reactive object using Vue 2’s mechanism).