problem with `reactive` handling objects with inner properties that are classes
Link to minimal reproduction
Steps to reproduce
It is self-contained in the provided the SFC Playground reproduction
What is expected?
It is expected that
const test: ITest = reactive<ITest>(testObj);
works properly in regard to type matching.
What is actually happening?
I get an error from the TypeScript language server in VSC.
TypeScript types matching with reactive
are not handling properly classes. It reduces them to empty objects {}
System Info
System:
OS: macOS 11.4
CPU: (8) x64 Apple M1
Memory: 21.72 MB / 16.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 16.13.0 - /usr/local/bin/node
Yarn: 1.22.5 - ~/.yarn/bin/yarn
npm: 8.1.0 - /usr/local/bin/npm
Browsers:
Chrome: 101.0.4951.64
Chrome Canary: 104.0.5061.0
Edge: 96.0.1054.62
Firefox: 99.0
Safari: 14.1.1
npmPackages:
vue: ^3.2.33 => 3.2.33
Any additional comments?
If this is a technical limitation and we have to do some workarounds it should be mentioned in reactive doc but there is no mention.
I can imagine we should use interfaces instead of classes but again, it would be a serious drawback for various use-cases.
I have noticed that the SFC Playground doesn't handle the code properly so here it is again:
<script lang="ts" setup>
import { reactive } from "vue";
class TestGeneric<eX extends number> {
protected x!: number;
constructor(protected y: eX) {
this.x = y;
}
}
class TestClass {
protected x!: number;
constructor(protected y: number) {
this.x = y;
}
}
interface ITest {
x: number;
y: string | undefined;
testGeneric: TestGeneric<number> | undefined;
testClass: TestClass | undefined;
}
const testObj: ITest = {
x: 3,
y: undefined,
testGeneric: undefined,
testClass: undefined,
};
/** there is error on the `test` constant, as reactive<ITest> doesn't provide `ITest` type properly. classes `TestGeneric` and `TestClass` are "trimmed" to pure `{}` types and we gat an error like "`{}` doesn't contain `x` and `y`".
* If I remove `testGeneric` and `testClass` naturally, errors are gone.
*/
const test: ITest = reactive<ITest>(testObj);
console.log(`test: ${test}`);
</script>
<template>
</template>
I ran into the same issue today, the issue has to do with protected properties on classes used inside reactive
:
<script setup lang="ts">
import { reactive } from 'vue'
class Person {
constructor(public name: string){}
protected species = "HUMAN"
}
const names = [ "Jim", "Pam" ]
const people = reactive(names.map(name => new Person(name)))
function foo(people: Person[]){
for (const person of people) {
console.log(person instanceof Person) // all true
}
}
foo(people)
// ^ { name: string; }[] !== Person[]
</script>
<template>
<p v-for="person in people">{{person}} - {{person instanceof Person}}</p>
<!-- ^ both objects are instances of Person class ^ -->
</template>
@LinusBorg this was never triaged/picked up by the Vue team, it seems.
I can add: I think this is because UnwrapRefSimple<T>
depends on the keyof
operator, which according to this comment in the TS repo "intentionally does not include non-public members".
So it might not be something that we can get around, unless the type can be made without the usage of the keyof
operator, or something like fieldof
is added to TypeScript.
Edit: I see now that there are other issues pointing this out.