value of the callback of the `watch` is incorrect
Vue version
latest
Link to minimal reproduction
Steps to reproduce
- Open the link
- Open the devtools and look for the console
- You will see [ref(0), ref(1), ref(2)] in console, but the type is number[]
What is expected?
I am not sure if the type is wrong or the value of the callback of the watch
is wrong
What is actually happening?
I am not sure, you can see the above
System Info
Nothing
Any additional comments?
I would like to send a PR to fix this problem, but I need to know which solution is right first
The type of numbers
is essentially still Ref<number>[]
, but it is possible that plugins like Volar have inferred the type and directly destructure the .value
member of Ref to obtain the original value, thus inferring the type as number[]. From my understanding, this should not be considered an error in the Vue mechanism, but rather a mistake in type inference. From the perspective of JavaScript itself, it should indeed output [ref(0), ref(1), ref(2)].
You can perform an experiment by using forEach
to iterate through the array members and try to access their .value
field:
<script lang="ts" setup>
import { watch, ref, reactive } from "vue";
const numbers = reactive([ref(0), ref(1), ref(2)]);
watch(
numbers,
(value) => {
// The type hint may indicate that there is no .value field, but the console output is still correct.
value.forEach((e) => console.log(e.value));
},
{
immediate: true,
}
);
</script>
@Dunqing, I apologize, I've found that, as @Okysu mentioned, this is a reactive
type of issue.
When we use watch([ref(0), ref(1), ref(2)], callback)
, we do not encounter this issue. However, reactive([ref(0), ref(1), ref(2)])
is inferred as the same type. Perhaps we should avoid using reactive
to create reactive arrays; we do not encounter this problem when using ref
.
This seems to be working as expected:
describe('should unwrap tuple correctly', () => {
const readonlyTuple = [ref(0)] as const
const reactiveReadonlyTuple = reactive(readonlyTuple)
expectType<Ref<number>>(reactiveReadonlyTuple[0])
const tuple: [Ref<number>] = [ref(0)]
const reactiveTuple = reactive(tuple)
expectType<Ref<number>>(reactiveTuple[0])
})
https://github.com/vuejs/core/blob/main/packages/dts-test/reactivity.test-d.ts#L55-L64
const numbers = reactive([ref(0), ref(1), ref(2)]); // [ ref(0), ...]
When is used in conjunction with watch
it will monitor the array changes, so making it not the same as watch([ref(0)]
because you're watching the reactive and not passing an array to watch
https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts#L211-L214
I have a try with your example, If you write a getter function
in the watch
like the follow code:
<script setup lang="ts">
import { watch, ref, reactive } from 'vue'
const numbers = reactive([ref(0), ref(1), ref(2)])
watch(() => numbers, (value) => {
// The type of the value is number[], but the value is [ref(0), ref(1), ref(2)]
console.log(value)
}, {
immediate: true,
})
</script>
<template>
<div>
{{ numbers }}
</div>
</template>
The type is right.