Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support RFC 436 (Experimental) #1964

Merged
merged 1 commit into from Oct 9, 2022
Merged

feat: Support RFC 436 (Experimental) #1964

merged 1 commit into from Oct 9, 2022

Conversation

johnsoncodehk
Copy link
Member

RFC: vuejs/rfcs#436

Usage

{
  "vueCompilerOptions": {
    "jsxTemplates": true,
    "experimentalRfc436": true
  }
}

Please note that extends any is required, otherwise JSX syntax will be broken.

<script setup lang="ts" generic="T extends any">
defineProps<{ msg: T }>()
</script>

@johnsoncodehk johnsoncodehk merged commit 6aab5c4 into master Oct 9, 2022
@johnsoncodehk johnsoncodehk deleted the rfc436 branch October 9, 2022 19:57
@edimitchel
Copy link

Ping vuejs/rfcs#436

@StepanMynarik
Copy link

StepanMynarik commented Oct 10, 2022

Full of excitement I tried it right away in my multiselect component.
When passing a template into the multiselect item slot, I still see the item as the base 'Item' class.

In my multiselect component I did this: <script setup lang="ts" generic="T extends Item">
and this: const { items, selectedItem, } = defineProps<{ items: T[]; selectedItem: T[]; }>();

Template passed as an item template: <template #default="{ item }"> <div>{{ item.extraData }}</div> </template>

When using it, I made sure to pass 'class ExtendedItem extends Item' arrays to both params.
In the template however I still only see it as the base 'Item' class, hence '.extraData' field cannot be used.

Are there any extra steps required?

NOTE: I made sure to first update Volar extension and vue-tsc to 1.0.3

@johnsoncodehk
Copy link
Member Author

@StepanMynarik I think the current design is not enough to support generic typed slots, we may need a new defineSlots api like this.

const { items, selectedItem, } = defineProps<{ items: T[]; selectedItem: T[]; }>();
defineSlots<{ default(args: { item: T }): VNode[] }>();

It's better to present your use case in an RFC so peoples can discuss with it.

@StepanMynarik
Copy link

@johnsoncodehk Oh, I must have got it wrong. What is the current intended use case then?

I will make sure to post my use case to the RFC, good point.

@johnsoncodehk
Copy link
Member Author

@StepanMynarik I'm checking if this might working without add new API, will let you know later.

@johnsoncodehk
Copy link
Member Author

@StepanMynarik please track #1987

@colinj
Copy link

colinj commented Oct 13, 2022

Defining props with the defineProps macro works fine.

<script setup lang="ts" generic="T extends string | number">
const props = defineProps<{ msg: T }>();
</script>

I found that you can also define props via an interface but you need to export the interface for it to work.

<script setup lang="ts" generic="T extends string | number">
export interface Props<T> {
  msg: T;
}

const props = defineProps<Props<T>>(); 
</script>

If you don't export the interface, then you get a TypeScript error stating that it Cannot find the name 'Props'.

@johnsoncodehk
Copy link
Member Author

@colinj you should move interface to <script> block.

<script lang="ts">
interface Props<T> {
  msg: T;
}
</script>

<script setup lang="ts" generic="T extends string | number">
const props = defineProps<Props<T>>(); 
</script>

You can use Show Virtual Files command to inspect codegen result.

@colinj
Copy link

colinj commented Oct 13, 2022

I thought I'd try using withDefaults to see if that works.
It still compiles and the default value does get set but TypeScript complains. :(

<script lang="ts">
interface Props<T> {
  msg?: T;
}
</script>

<script setup lang="ts" generic="T extends string | number">
const props = withDefaults(defineProps<Props<T>>(), {
  msg: "Fred", // TS Error
  // Type 'string' is not assignable to type 'InferDefault<Readonly<Props<T>>, NotUndefined<T>>'.ts(2322)
});

@johnsoncodehk
Copy link
Member Author

johnsoncodehk commented Oct 13, 2022

This is withDefaults problem, you can use this workaround.

<script lang="ts">
interface Props<T> {
  msg?: T;
}
</script>

<script setup lang="ts" generic="T extends string | number">
const props = withDefaults(defineProps<Props<T>>() as Props<string | number>, {
  msg: "Fred",
});
</script>

@colinj
Copy link

colinj commented Oct 13, 2022

Yes, I started to look inside withDefaults to see how it works but I think this workaround is just what is needed to be done.
Thanks again @johnsoncodehk!

@colinj
Copy link

colinj commented Oct 13, 2022

I am also getting eslint (no-undef) errors for T ('T' is not defined) so I have added the following entry to my .eslintrc.cjs file to ignore T.

module.exports = {
  ...
  rules: { ... },
  globals: {
    T: "readonly",  // naming convention used to define the type on generic Vue components
  },
};

Obviously, this means I would need to use T to avoid linting errors when defining the generic type for these components.

@grindpride
Copy link

Enabling vueCompilerOptions for this feature causes template refs type errors

error TS2344: Type '() => Omit<JSX.Element, "props" | "children"> & Omit<{ $: ComponentInternalInstance; $data: {};
 $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<...>> & { ...; } & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function, cb: Function, options?: WatchOp...' does not satisfy the constraint 'abstract new (...args: any) => any'.
  Type '() => Omit<Element, "props" | "children"> & Omit<{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<ExtractPropTypes<...>> & { ...; } &
 VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function, cb: Function, options?: WatchOption...' provides no match for the signature 'new (...args: any): any'.

const dateNavigationRef = ref() as Ref<InstanceType<typeof DateNavigation>>

@johnsoncodehk
Copy link
Member Author

@grindpride See #1987 (comment)

@grindpride
Copy link

@johnsoncodehk Can you also see this issue #1405 (comment) ?

@geoffgscott
Copy link

Type checking works amazing and passes but I do get this message in VSCode anytime a generic is used inside defineProps:

The inferred type of 'default' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
<script lang="ts" setup generic="TC extends readonly string[] | string[]">
defineProps<{
    options: TC;
}>()
</script>

@ThaDaVos
Copy link

I noticed this working amazingly during dev builds - but prod builds are broken as it cannot seem to be able to resolve the file (te name seems to include the generic part) - but that may also be caused because I am using Laravel Vite

@johanninos
Copy link

johanninos commented Nov 17, 2022

@grindpride See #1987 (comment)

InstanceType<typeof BButton<T>> can pass generic.
but ReturnType<typeof BButton<T>> wasn't supported untill typescript 4.7.0

but vue has issue with this ts new feature

vuejs/core#7161

@StepanMynarik
Copy link

StepanMynarik commented Dec 1, 2022

Type checking works amazing and passes but I do get this message in VSCode anytime a generic is used inside defineProps:

The inferred type of 'default' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
<script lang="ts" setup generic="TC extends readonly string[] | string[]">
defineProps<{
    options: TC;
}>()
</script>

Same here with generic="T extends Item".
But only when I use T directly in params, as a single item.
When I declare T[] prop, then no error :D

@dajpes
Copy link

dajpes commented Dec 16, 2022

For those using vue 3, you need to paste the following in tsconfig.app.json:

"vueCompilerOptions": {
    "jsxTemplates": true,
    "experimentalRfc436": true
  }

@damafeez
Copy link

Type checking works amazing and passes but I do get this message in VSCode anytime a generic is used inside defineProps:

The inferred type of 'default' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
<script lang="ts" setup generic="TC extends readonly string[] | string[]">
defineProps<{
    options: TC;
}>()
</script>

I get the same error as well. Does anyone know how to resolve it?

@rodrigocfd
Copy link

Is it possible to use this with Vite?

@h-sigma
Copy link

h-sigma commented Feb 28, 2023

I tried this today, and the improvement in DX is genuinely amazing.

I only have two issues with this, which if they can be improved, brilliant:

  1. The cyclic type error that's been mentioned multiple times that is caused by using T directly in defineProps. Since the error goes away if you use a T[] in the props instead of a T, I've had to rely on the following really bad solution:
//DataDisplay.vue
<script setup lang="ts" generic="T extends any">
const props = defineProps<{
    data: T[] | undefined;
}>();
const realData: Ref<T> = computed(() => (props.data == null ? undefined : props.data[0]) as Exclude<T, undefined>);
</script>

//App.vue
<template>
    <!-- Only want to pass in a single `T`, but forced to wrap it in an array. -->
    <DataDisplay :data="[5]"/>
</template>
  1. Eslint does not pick up on the generic defined in the script tag. So I have to prepend every usage of T with //eslint-disable-next-line no-undef.

This is fine for simple components, but the bandages are a bit of an annoyance.

I hope this progress rapidly because it improves Vue for the way-better.

@jmoore914
Copy link

Is it possible to use this with Vite?

This is working for me with Vite.

I am running into the cyclic type error as others have mentioned though.

@dakt
Copy link

dakt commented Mar 13, 2023

This is freaking amazing. The one thing that kept React better suited for large project was this. Please, oh please make this official fast, please...I typed our ui lib with this and it boosted DX 10 fold

@Paul7Peterson
Copy link

A nice example if you want to type the modelValue of a select component based on the given options:

<script setup lang="ts" generic="VueGen extends AppSelectOption<string | number>">
  export interface AppSelectOption<T extends string | number> {
    value: T,
    text: string;
    disabled?: boolean;
  }

  export interface Props<T extends AppSelectOption<string | number>> {
    text: string;
    modelValue: T['value'] | undefined;
    options: readonly T[];
    disabled?: boolean;
  }

  defineProps<Props<VueGen>>();

  const emits = defineEmits<{
    (e: 'update:modelValue', modelValue: VueGen['value']): void;
  }>();

  function onChange (e: Event): void {
    const target = e.target as HTMLSelectElement;
    emits('update:modelValue', target.value);
  }
</script>

<template>
  <label>
    {{ text }}
    <select
      :value="modelValue"
      :disabled="disabled"
      @change="onChange"
    >
      <option
        v-for="option in options"
        :key="option.value"
        :value="option.value"
        :disabled="option.disabled"
      >
        {{ option.text }}
      </option>
    </select>
  </label>
</template>

This also allows to set the value as undefined in case you don want any pre-selected option.

But I'm also facing the The inferred type of 'default' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary. ts(5088) TypeScript error and @ts-ignore doesn't help. Any suggestion on how to address it?

@yyx990803
Copy link
Member

To everyone running into the cyclic structure cannot be trivially serialized issue, could you please provide a reproduction, or at least the Vue / Volar / TypeScript versions? I cannot reproduce this locally.

@Paul7Peterson
Copy link

@yyx990803 Here's a minimum repo with the error reproduction with the Select example I posted.

Using TypeScript 4.9.5 locally from global.

@h-sigma
Copy link

h-sigma commented Mar 24, 2023

I'm unable to provide a repro ATM. I'll at-least confirm my versions: Typescript 4.9.5, Volar v1.2.0, Vue 3.2.45, Vue-Tsc 1.2.0.

@h-sigma
Copy link

h-sigma commented Mar 24, 2023

  • Upgraded typescript to 5.0.2 : Cyclic error still occurs.
  • Switching Volar + vue-tsc to pre-release version 1.3.4: Does get rid of the cyclic error in combination with the typescript version above. (My life is now complete)

@Paul7Peterson
Copy link

Thanks! Yeah, this really solved the issue! 💚

@dakt
Copy link

dakt commented Mar 24, 2023

Types lost when compiled as library.

In project:
image

As npm lib:
image

@damafeez
Copy link

  • Upgraded typescript to 5.0.2 : Cyclic error still occurs.
  • Switching Volar + vue-tsc to pre-release version 1.3.4: Does get rid of the cyclic error in combination with the typescript version above. (My life is now complete)

I just upgraded typescript to ^5.0.2 (both globally and in-project), I upgraded volar and vue-tsc to ^1.3.4 and still get the The inferred type of 'default' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary. error, sadly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet