vuejs / core

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

Vue TSC & computed-refs #10913

Closed louis49 closed 5 months ago

louis49 commented 5 months ago

Vue version

3.4.27

Link to minimal reproduction

https://github.com/louis49/demo-vue-ts-computed-refs.git

Steps to reproduce

import {computed, ComputedRef, reactive} from "vue";

interface Register {
    id: string;
    val: ComputedRef<string>;
}

interface Store {
    registers: Register[];
}

class StoreManager {
    public store: Store = reactive({
        registers: []
    });

    addRegisters(){
        const cmp = {val : computed(() => { return "aa" }), id:"0"};
        const result0 = parseInt(cmp.val.value, 16); // No compilation error
        console.log(result0); // Ok : 170
        this.store.registers.push(cmp);
        storeManager.compute(storeManager.store.registers);
    }

    compute(registers : Register[]){
        const register = registers.find(reg => reg.id === "0");
        const result1 = parseInt(register.val, 16); // error TS2345: Argument of type 'ComputedRef<string>' is not assignable to parameter of type 'string'.
        console.log(result1); // Ok : 170

        const result2 = parseInt(register.val.value, 16); // No compilation error
        console.log(result2); // Ko : NaN
    }
}

const storeManager = new StoreManager();
export default storeManager;

What is expected?

I do not know what is really expected : access to value with .value or not

What is actually happening?

Problem Summary I am encountering two primary issues in my Vue 3 and TypeScript project when working with ComputedRef from Vue's reactivity API in a class managing state:

TypeScript Compilation Error:

const result1 = parseInt(register.val, 16); // error TS2345: Argument of type 'ComputedRef' is not assignable to parameter of type 'string'. console.log(result1); // Ok : 170 Issue: This line throws a TypeScript error (TS2345) because parseInt expects a string as its parameter, but register.val, which is a ComputedRef, is passed instead. The ComputedRef type does not directly expose its value; instead, its actual string value is wrapped inside and must be accessed via .value. Runtime Issue (NaN Result):

const result2 = parseInt(register.val.value); // No compilation error console.log(result2); // Outputs NaN Issue: While the TypeScript error is resolved by correctly accessing the value property of the ComputedRef, the output at runtime is NaN. This issue suggests that the actual value held by register.val.value may not be in a format that parseInt can successfully parse.

System Info

System:
    OS: macOS 14.4
    CPU: (8) arm64 Apple M2
    Memory: 124.52 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.11.0 - /usr/local/bin/node
    npm: 10.2.4 - /usr/local/bin/npm
  Browsers:
    Chrome: 124.0.6367.158
    Safari: 17.4

Any additional comments?

No response

jh-leong commented 5 months ago
  1. When executing this.store.registers.push(cmp);, since registers is a reactive array, cmp will be automatically proxied by reactive.

  2. Accessing a key of a reactive object triggers the proxy's get. In the get logic, unRef operation is performed if isRef(res) === true. The source code location is at: https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts#L152~L155

You can observe the difference by removing reactive from store, refer to playground.

louis49 commented 5 months ago

Humm ok, but TSC seems not aware of that : it concludes to a wrong type

LinusBorg commented 5 months ago

Because you defined the argument that you pass into compute() to be Register[] - and the Register interface defines val to be a ComputedRef<string>, so that's what TS expects it to be, but it's not, because of unwrapping.

Here's a working approach:

https://play.vuejs.org/#eNp9Vd9P2zAQ/ldOeSGVskA3tkmlrcQQD0waTMCeCBJuck0MqR3ZDqWq+r/vbCdpwq++ND3ffffdd1/cbXBaVfFzjcEkmOpU8cqARlNXUDKRz5LA6CSYJ4KvKqkMbFO5qmqDWQRnzdM1LiP4J9aKVZeofUBHoJClhj/jDpZKriAJqEcSnCSCsIRBtWQpwjXmnEoUbBMB9OHZBLRRXOSUaAPPrJz0O0396ZyOdxbKbCqL4lt1aLM3fKbtGY3SJ3BjpMK2u2py9OQN5N192zEtmda+7g8TLN+Tr+pFyVPiT0eTBnnW6RA2Wa8a3d378G7kpLGPLCPKTUI46pUdHkKGS070czAFQo4CFXV0IhRI7bgGIQ0ITFFrpjYRLGraZyHXGjayHkBREIykxRjFhC6ZofX4XTECEV9a4hFYKSvMfJ8F2vYViUARLiwEI5A2mzBs2qATZa0LnhbEy+6Dxl9qYES3dluy0B5VkfGUsD838NAJN3rYo6VSaAPpquopu99tuCW/wISyvGHCcASzOWwbYKLJGBHcjSLrtCQ4SgKn+xBdoa5Lc0QdKqY0XggTUsOYoCMY/xid2JEupWvCSTYuBaBSUg1xZIlxKfOwQfNlV0/EbvzzaJ9qCq5jZ5m4c0Vc1bqwPfvcXE7juLgZMBwEX6G0xc61npUv6jKIy1uf9w3X6tG9VnuKZMPMIll96SvmGcxmM1KYNP1I0nFf0hZqqKsTEm5vvn47/j6BU5XXKxQG5NKb7+Cdq+CgdT1ZkueCLUq0pqRGbIWWdlfsCw7izxY1fm9RJKF/990wfdFpIoHrwXUQ2vEHixm+0ImYHvqL1t1FU4Oryr599AtgWoznZwWmTy216SFFbEkvLYjoVqbzJc/jRy0FXd1uaUngPYnqqrK2pJt70l5OZP2ylOvfLmZUjVEbT227d+KP+sXGkuAvKYPKvtrdmWEqR+OPz28u8YWeu8OVzOqSsj85vEaarbYcfdqvWmREu5fn2F64Px3a2a0+fzEodDuUJWozdy7f/bdYZ3w0+p7ut/jY1dFCg91/WVZWZA==