Subscribe on changes!

value of the callback of the `watch` is incorrect

avatar
Oct 17th 2023

Vue version

latest

Link to minimal reproduction

https://play.vuejs.org/#eNp9UstOwzAQ/JWVL6RS1PK4VQUJEAc4AAJumEOabluXxLbsdVoU5d9ZO6GABL34sTPjGa/diktrx01AMRUzXzplCTxSsBdSq9oaR9DCtqBynYPDZRyKklSD0MHSmRqOWHsktdSl0Z5Ah3qOzsP5npi9si47HiV9djLMp6O3kdTp4GzQ5JA1RRVwBOcX0EoNMJnAyxqBPiyCWQLxOjFA+cHo9S2HeaDf0H+G8ciY0lQ4rsxqcJO6y3s7Vde4UAXhFMgFzBlhdDbp28IN4Q1hbSum8G62UA1PbTvk77pI7ouzyZ4ockGebZdqNd54o7nRyU2K0tRWVegeLCmOJcW0zxGxoqrM9i7Vhix9vVxj+f5HfeN3sSbFo0OPrkEp9hgVboXUwzfP97jj9R6szSJUzD4APiG3LMSMPe0q6AXH/sFLaW/Td1F69eJvdoTaf10qBo3MLvGl4B9zfeDq33HPxmdJxw8huk88WeOl

Steps to reproduce

  1. Open the link
  2. Open the devtools and look for the console
  3. 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

avatar
Oct 17th 2023

By design, rawValue should be obtained, but it is a breaking change.

avatar
Oct 17th 2023

By design, rawValue should be obtained, but it is a breaking change.

Yes, I had a PR do this

avatar
Oct 17th 2023

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>
avatar
Oct 17th 2023

@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.

avatar
Oct 17th 2023

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

avatar
Oct 28th 2023

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>

image The type is right.

https://play.vuejs.org/#__DEV__eNp9UsFO3DAQ/ZWRL2SlaNOW22oXqa04tIcWATfMIWRnswbHtuxxWBTl3xk7SwAJUKR4PO/NzPOzB/HTuWUfUazEOjReOYKAFB3o2rQbKShIcSaN6pz1BAM81tTsS/C4S7+6IdUjjLDztoMT7nMiTfoaawKBid0d+gCbmVrccGXxbZE7FN+P64/F7UKa3LooFrA5e6ksoehrHTHnBmkAqgqu9wj05BDsDojjzAAVjkU3tyXcRXoPfTY2tUxarcaltu1xmjRjOY1TXYdbVROugHzEkhFG19VkFRvDG8LOaabwDmC9VX0OAIZhNmAcM1ZN4LqaS0TJDrOAnWqX98EavoY8V4rGdk5p9P8dKRYoxWpSlLBaa/v4N+eOqqZ8s8fm4YP8fTiknBQXHgP6HqWYMap9izTB51f/8MDxDHZ2GzWzvwAvkc2LSeNE+xXNlmW/4WW1f/IDUqa9DucHQhNeDpWEJuaY+VLwG/r9xdFf5Z4uT3MdX4kYnwGWr+qd