Subscribe on changes!

[Type for Reactivity Transform] Add another type which indicates the value is deconstructed from `defineProps` / 为 `响应性语法糖` 添加一个可以标识变量是从 `defineProps` 中解构出来的类型

avatar
Oct 13th 2022

What problem does this feature solve?

English:

It's related to #5976 and #6578, please have a look at these two issues first.

I've investigated the types of Reactivity Transform, and I think we need to add another type to indicate whether the value is deconstructed from defineProps to solve the issue.

Currently, we have no way to know whether the developer wants to: Scenario 1: use $$(Array) for deconstructing an array, like in watch

const refA = $ref(0)
const refB = $ref(0)

watch($$([refA, refB]), () => {})

Scenario 2: use $$(Array) for keeping the reactivity of the array, like using on Arrays deconstructed from defineProps

interface CompProps {
  arr: string[]
}
const { arr } = defineProps<CompProps>()

passAsRef($$(arr)) // Type for $$(arr) here is `Ref<string>[]` instead of `Ref<string[]>`

So the current type in macros.d.ts cannot solve the issue, as we have no idea what is the purpose of using $$() on an array. If we add this

export declare function $$<T extends unknown[]>(value: T): Ref<T>

It solves Scenario 2 but breaks Scenario 1 so it's not a valid fix. (It's only an issue about the type, the runtime compilation is working as expected)

Maybe there's some other way to fix it, but I've tried my best.


中文:

请先阅读 #5976 和 #6578, 该提议和这两个问题有关.

我看了一下 响应性语法糖 的类型, 我觉得需要添加一个额外的类型来标识从 defineProps() 解构出来的响应式变量 (以和其他的通过语法糖生成的响应式变量区分开) 来解决这个问题.

目前我们没法知道一个开发者想通过哪种方式在 数组 上使用 $$() 情况1: 通过 $$(Array) 解构数组, 让其中的每一个变量都保留响应式, 比如在 watch

const refA = $ref(0)
const refB = $ref(0)

watch($$([refA, refB]), () => {})

情况2: 通过 $$(Array) 使从 defineProps() 解构得到的数组保持响应性, 比如传递给需要接收 Ref 的函数

interface CompProps {
  arr: string[]
}
const { arr } = defineProps<CompProps>()

passAsRef($$(arr)) // 这里 $$(arr) 的类型为 `Ref<string>[]`, 而正确的应该是 `Ref<string[]>`

目前在 macros.d.ts 中的类型不足以解决问题, 因为我们不知道作用在数组上的 $$() 到底是什么作用. 如果加上这一个额外定义

export declare function $$<T extends unknown[]>(value: T): Ref<T>

它确实解决了 情况2, 但同时破坏了 情况1, 所以这样是不对的. (仅仅是一个类型问题, 运行时编译一切正常)

可能有其他方法能解决这个问题, 但是我尽力了. 太菜了😭

What does the proposed API look like?

English:

My ideal way is to add a type indicating that the value is deconstructed from defineProps(), e.g. in runtime-core.d.ts

// Before
export declare function defineProps<TypeProps>(): Readonly<TypeProps>;
// After
export type ValueFromProps<T> = T & { /* some indicators here */ }
export declare function defineProps<TypeProps>(): Readonly<ValueFromProps<TypeProps>>;

and in macros.d.ts

// Something similar to this
export declare function $$<T>(value:  ValueFromProps<T>): ToRawRefsForProps<T> 
type ToRawRefsForProps<T> = {
  T extends Array<infer V> // It may be incorrect, just an idea
    ? Ref<V>
    : // handle other objects and primitive types just like `ToRawRefs<T>` and other `$$()` types declared now.

中文:

我理想的解决方法是添加一个可以标识某个变量是从 defineProps() 解构出来的类型, 比如在 runtime-core.d.ts

// 以前
export declare function defineProps<TypeProps>(): Readonly<TypeProps>;
// 现在
export type ValueFromProps<T> = T & { /* some indicators here */ }
export declare function defineProps<TypeProps>(): Readonly<ValueFromProps<TypeProps>>;

macros.d.ts

// 类似这样的东西
export declare function $$<T>(value:  ValueFromProps<T>): ToRawRefsForProps<T> 
type ToRawRefsForProps<T> = {
  T extends Array<infer V> // 只是表达一个想法
    ? Ref<V>
    : // 处理其他对象和基本类型, 就像现在 `ToRawRefs<T>` 和其他 `$$()` 类型中的那样
avatar
Oct 13th 2022

According to RFC Unresolved Questions chapter, I think we should have $defineProps or $(defineProps()) to fix it.

avatar
Oct 13th 2022

Here are the very prototype of definitions FYR:

<script lang="ts" setup>
type Props = {
  someArray: string[];
};
//      ⬇️ ReactiveVariable<string[]>
const { someArray } = $defineProps<Props>();

//     ⬇️ Ref<string[]>
const someArrayRef = $$(someArray);
</script>
/// <reference types="vue/macros-global" />

import type { RefValue } from "vue/macros";

declare global {
  function $defineProps<TypeProps>(): {
    [Key in keyof TypeProps]: RefValue<TypeProps[Key]>;
  };
}

export {};
avatar
Oct 13th 2022

Yeah seems like a valid fix. I'll keep an eye on it. Not sure if I'm going to subscribe to the RFC as it may result in too many emails.

avatar
Oct 13th 2022

I guess we should wait for Evan to make the decision on RFC. If there's any news, I'll ping you on this issue.

avatar
Oct 13th 2022

Great, thanks. Guess I can learn a lot from the official fix.

avatar
Nov 1st 2022

Here are the very prototype of definitions FYR:

<script lang="ts" setup>
type Props = {
  someArray: string[];
};
//      ⬇️ ReactiveVariable<string[]>
const { someArray } = $defineProps<Props>();

//     ⬇️ Ref<string[]>
const someArrayRef = $$(someArray);
</script>
/// <reference types="vue/macros-global" />

import type { RefValue } from "vue/macros";

declare global {
  function $defineProps<TypeProps>(): {
    [Key in keyof TypeProps]: RefValue<TypeProps[Key]>;
  };
}

export {};

Same problem here. Your suggested solution looks perfect btw!

avatar
Nov 4th 2022

Implemented in Vue Macros. Read the Docs

avatar
Nov 5th 2022

Implemented in Vue Macros. Read the Docs

Thanks, that seems good. Will have a try later on.

avatar
Nov 5th 2022

I think we should open this issue for official implementation tracking.

avatar
Mar 30th 2023

#7986 We have deprecated the Reactivity Transform and extracted the feature of reactive props destructuring to another option. This means that we no longer support $$, and users must use computed(() => foo) themselves. As a result, this issue has been bypassed.