vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.42k stars 8.3k forks source link

Nested array refs type errors #11548

Open cnzgray opened 2 months ago

cnzgray commented 2 months ago

Vue version

3.4.35

Link to minimal reproduction

https://play.vuejs.org/#eNp9kcFuwjAMhl/FyoVVQi2U7VI6pG3isE0a0+BG0FSKYWVtEiVph4T67nNSDThMXKLY32/rt31kD0qFTY0sYanJdaEsGLS1mnBRVEpqC0fQuIUWtlpW0CNpjwsucimMhQzuHb05cgGwTvx/EPRdlHfR0j3DoO+DOFgFXLTB2HXIwiYrawzX1GM4hiiC2es5my8HKwKxB4t5AvEojl1ZGnU2ySAFFitVZhYpAkg3RTNJI/c63QVjfWYNWd4Wu3BvpKBpvWXOclmpokQ9U7agkThLwBPHsrKUPy8+Z3WNfixf84X59z/5vTm4HGfvGg3qBjk7MZvpHdoOT+dveKD/CVZyU5ekvgI/0Miydh472WMtNmT7QufdPvubFWK3MNODRWH+hnJGnbL1es7ojk9XRj/bHYW3vo7uRlv8bFC7nrRAAuHojrW/e2S5SQ==

Steps to reproduce

PR #11442 is not just a TS>5.1 issue; it also requires additional testing for nested Ref arrays.

What is expected?

a.value.b = 1; // OK

a.value.c[0] = 2; // OK

What is actually happening?

a.value.b = 1; // OK

a.value.c[0] = 2; // TS Error 2322

System Info

System:
    OS: macOS 13.6.7
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 24.11 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.16.0 - ~/.nvs/node/20.16.0/x64/bin/node
    Yarn: 1.22.22 - ~/.nvs/node/20.16.0/x64/bin/yarn
    npm: 10.8.2 - ~/.nvs/node/20.16.0/x64/bin/npm
    pnpm: 9.6.0 - ~/.nvs/node/20.16.0/x64/bin/pnpm
  Browsers:
    Chrome: 127.0.6533.90
    Safari: 17.5

Any additional comments?

https://vuejs.org/api/reactivity-core.html#ref

If an object is assigned as a ref's value, the object is made deeply reactive with reactive(). This also means if the object contains nested refs, they will be deeply unwrapped.

jh-leong commented 2 months ago

Thank you for reporting this issue.

FYI: this issue has existed prior to the changes merged in v3.4.35. However, I agree that it requires further investigation and testing.

cnzgray commented 2 months ago

Sorry, you’re right; this issue has existed in previous versions.

cnzgray commented 2 months ago

I just checked the type definition of UnwrapRef and found it strange. Why is it defined as UnwrapRefSimple instead of the following UnwrapRef?

      T extends ReadonlyArray<any>
            ? {
                [K in keyof T]: UnwrapRef<T[K]>;
              }

Refer to the playground below.

I hope this helps you.

mefcorvi commented 2 months ago

It seems that types are correct here since this is actually intended behavior: https://github.com/vuejs/core/blob/b1abac06cdb198bd72f8e614b1f68b92e1c78339/packages/reactivity/src/baseHandlers.ts#L152-L155

You can find reasoning for this behavior here: https://github.com/vuejs/core/commit/775a7c2b414ca44d4684badb29e8e80ff6b5d3dd

This is reflected in the documentation as well: https://vuejs.org/api/reactivity-core.html#reactive

It should also be noted that there is no ref unwrapping performed when the ref is accessed as an element of a reactive array or a native collection type like Map.

cnzgray commented 2 months ago

I apologize for revisiting the responsive implementation code. The current type definition is reasonable. The issue lies with TypeScript arrays; you can check this playground.

https://www.typescriptlang.org/play/?ts=5.6.0-dev.20240807#code/JYOwLgpgTgZghgYwgAgKoihANnSATANTiwFcIAeAFQBpkBlAXkoD5kBvAWAChlkBzCGGQA3YmQAUASgBcySgG5uvAM6CRYiOOGy6kxVwC+3bggD2IZULjIGyANoBdZHGXIAglChwAnuXSYcfCJSChASAFsAI2haSyhQPgAfMKjoZmZ9OAA6AAcSZQALcTZ1ENkARmQDPW5svMLi0rJZACJylqq9ZAB6buQwZXEAJgBmIaHJWrsABgcs0RCbZCHMmbmFsiWWoZb5IA

interface UnrelatedValue<T, S=T> {
  get value(): T;
  set value(v: S);
}

const a = [] as Array<UnrelatedValue<number, string|number>>;
a.push({ value: 1 });
a.push({ value: "1" }); // ts(2322)
a[0].value = 2;
a[0].value = "2";
cnzgray commented 2 months ago

I've discovered a new test case In the environment of ts 5.5.4 + vue 3.4.34, the following code can correctly infer the type. Playground for 3.4.34

import { ref } from "vue";
const g = { g: 0, h: "h" }
const e = {
  f: ref(g), 
};
const c = {
  d: ref([e]), 
};
const a = ref({
  b: ref([c]), 
});
a.value.b[0].d[0].f = g; // f type is { g: number; h: string; }

However, in the environment of ts 5.5.4 + vue 3.4.35, there is an error in type inference. Playground for 3.4.35

a.value.b[0].d[0].f = g; // error 7053

I think the problem is related to the behavior of "Unrelated Types for Getters and Setters" under array type inference, so if TypeScript does not correctly address the issue with arrays, it might not be suitable to incorporate "Unrelated Types for Getters and Setters" into Vue.

mefcorvi commented 2 months ago

Thank you for the test case; it's an interesting example. As far as I can tell, this was caused by the result of the ref function being declared as Ref<UnwrapRef<T>, UnwrapRef<T> | T>. After the fixes in https://github.com/vuejs/core/pull/11536, the types in this example work correctly.