core: Types don't allow refs to be assigned to reactive object properties

Version

3.0.7

Reproduction link

https://codesandbox.io/s/zealous-ptolemy-oierp?file=/src/index.ts

Steps to reproduce

  1. Create a reactive object, e.g. const foo = reactive({bar: 3)}
  2. Assign a ref to one of its properties, e.g. foo.bar = ref(5)

What is expected?

Typescript should be fine with assigning a ref to a reactive object. It works at runtime and is even shown in the documentation: https://v3.vuejs.org/guide/reactivity-fundamentals.html#access-in-reactive-objects (the minimal reproduction I’ve linked is literally just that example)

What is actually happening?

Typescript complains that Ref<number> isn’t compatible with number

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 15 (8 by maintainers)

Most upvoted comments

import type { UnwrapRef } from "vue";
/**
 * This function simply returns the value typed as `T` instead of `Ref<T>` so it can be assigned to a reactive object's property of type `T`.
 * In other words, the function does nothing.
 * You can assign a Ref value to a reactive object and it will be automatically unwrapped.
 * @example Without `asUnreffed`
 * ```
 * const x = reactive({someProperty: 3});
 * const y = ref(2);
 * x.someProperty = y; // This is fine, but sadly typescript does not understand this. "Can not assign Ref<number> to number".
 *                     // The getter is properly typed, this property should always return number.
 *                     // But the setter should also be able to handle Ref<number>.
 *                     // The setter and getter can not be typed differently in Typescript as of now.
 * y.value = 5;
 * console.log(x.someProperty) // expected: 5.
 * ```
 * @example With `asUnreffed`
 * ```
 * const x = reactive({someProperty: 3});
 * const y = ref(2);
 * x.someProperty = asUnreffed(y); // We lie to typescript that asUnreffed returns number, but in actuality it just returns the argument as is (Ref<number>)
 * y.value = 5;
 * console.log(x.someProperty) // expected: 5.
 * ```
 * @see {@link https://vuejs.org/api/reactivity-core.html#reactive} to learn about the Ref unwrapping a Reactive object does.
 * @see {@link https://github.com/vuejs/core/issues/3478} and {@link https://github.com/microsoft/TypeScript/issues/43826} for the github issues about this problem.
 * @param value The value to return.
 * @returns Unchanged `value`, but typed as `UnwrapRef<T>`.
 */
export const asUnreffed = <T>(value: T): UnwrapRef<T> => value as UnwrapRef<T>;

For now, I created this helper function to get around this problem. Works well but it does add a call to a useless function unfortunately.

I’m a bit confused about why this is a duplicate. That issue seems to be talking about runtime behavior (and is also related to computed, although I guess that doesn’t necessarily matter) while I’m talking about types? Like I mentioned what I’m doing works fine at runtime, it’s strictly Typescript that forbids me from doing it. I might of course just be misunderstanding the other issue though.

@LinusBorg Can vue3 make auto unwrapping ref is optional via params in the future?

It’s a source of confusion, personally i prefer no auto unwrap even it’s ugly (have to write .value everytime), at least it’s clear its a ref, and i don’t have to fight/double think when assign/use it (lowering dev experience).

example of the problem:

interface Person {
  name: string
  drinkPower: Ref<number>
}

interface State {
  searchText: string
  person: Person | null
}

const state = reactive<State>({
  searchText: '',
  person: null,
})

const cindy: Person = {
  name: 'Cindy',
  drinkPower: ref(10),
}

// Typescript complaint it's different structure.
state.person = cindy

// Have to wrap inside dummy ref to make it work...
state.person = ref(cindy).value

duplicate of #1135