I am trying to achieve something quite tricky regarding usage of multiple inter-dependent computed properties.
My initial question is the following : We have a computed value A and a computed value B. B has A as a dependency. Now, the dependencies of A changes, but the output value is still the same, so it's recomputed but returns the same value.
From what I've found out on my investigation, even if the output value of A does not change, if A is re-computed, then B will abso be re-computed. I want to know if there is a way to achieve this behaviour
To provide you context, let me first give you a concrete example that's close enough to my real use case :
<script setup>
import { ref, reactive, computed } from 'vue';
const formState = reactive({
field1: '',
field2: {
field2Child: 'MyChildVal',
},
});
const mapDependencies = (arrayDependencies) => {
let dependencies = {};
for (const { key, value } of arrayDependencies) dependencies[key] = value;
return dependencies;
};
const resolveFromString = (path, obj, separator = '.') => {
var properties = Array.isArray(path) ? path : path.split(separator);
return properties.reduce((prev, curr) => prev && prev[curr], obj);
};
const listDependencies = ['field2.field2Child'];
// Will re-render every time formState changes
const dependencies = computed(() =>
mapDependencies(
listDependencies.map((key) => ({
key,
value: resolveFromString(key, Object.assign({}, formState)),
}))
)
);
const dependenciesAsString = (dependencies) => {
return 'The dependencies are' Object.entries(dependencies).map(([key, value]) => `${key} : ${value}`);
}
// Expected to re-render only when the output of "dependencies" changes. But on real usage
const computedBasedOnDependencies = computed(() => dependenciesAsString(Object.assign({}, dependencies.value)));
const ChangeState = () => formState.field1 = new Date().toString();
</script>
I created a live reproduction of this of stackblitz : https://stackblitz.com/edit/vue-svtsu2?file=src/App.vue
The behaviour I expected from this : If the output of "dependencies" does not change, then the timestamp at the begining of "computedBasedOnDependencies" should not change
The behaviour I get : Even when "dependencies" output is not changing, when it's re-computed "computedBasedOnDependencies" is recomputed as well
If you have any insight about something I don't understand correctly from Vue reactivity, or any other way to achieve this behaviour, I welcome any help whatsoever.
Thanks by advance !
CodePudding user response:
Computed property functions are supposed to be pure functions, i.e. they don't make side effects, and function output depends only on input.
For any other logic, a watcher should be used. This is the case for a watcher, where can be throttled with either source function and/or a comparison of old and new value:
const computedBasedOnDependencies = ref(null);
watch(
() => stringifyWithoutTimestamp(dependencies.value)),
(val, oldVal) => {
if (val !== oldVal)
computedBasedOnDependencies.value = stringifyWithTimestamp(dependencies.value);
},
{ immediate: true }
);
CodePudding user response:
Even when "dependencies" output is not changing...
Well problem is it is changing - even if it returns the object with the same content as before, it is still a new object
function getObject() {
return { value: "A" }
}
const a = getObject()
const b = getObject()
console.log(a === b)
Vue does not do "deep" comparison as that would be a major performance hit (the value returned from computed
can be anything - deeply nested object for example). SO if you need this, you must implement it yourself presumably using watch
instead of computed
But before going that route you should be sure that what you are doing is not just a premature optimization