vuejs / core

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

Problem with `defineProps` and generic components #11487

Open zumm opened 3 months ago

zumm commented 3 months ago

Vue version

3.4.35

Link to minimal reproduction

https://play.vuejs.org/#eNp9VNtu2zAM/RXOL2mBJMYuT4bToduKYRuwFVuwlzhAjJhJ3dqSIcm5IMu/j9TFcdAgfpLIQ+oc6siH6L5pxpsWoyRK9VKVjQGNpm2gysV6kkVGZ9FdJsq6kcrAVxSoyiWslKxhMI79nhsMMpGJpRTagGxMSQuYwOyQCQCR15jA4JcNw9vBkIObvGo5KgWXHodwAfruHGq2kqHzTKSx40rMaGOwbqrcIO0A0sAx8TRIhF9lkac2MrgzFOfTKBhTYRr3ukRD0k1aVuV6/KyloOFYdlm0lHVTVqgcQeqYON6cy6tKbr/bmFEtWuq25gmXLxfiz3rHsSx6VKhRbYhKlzO5WqNx6Yc/P4luL1nLoq0IfSX5G7WsWuboYJ9aURDtHs6y/WavtRTrqX7YGRQ6iGKijDxafBbRBX++Iv1E9/34g62je6Ip9vxxzWBcsXZQirimU+8BUoei0HCA2Q8oRYhPKQ7/wu4vO2SegDaKxHjWXQ+LDX08ZEJ2opAzWIe0fS5ArQMdlvmRRZzTGyUb9vm2NE9fcJW3ldE3jCpwVQp85HTqp+RNmMC9Uvk+9Sdaz4YkE/2Y9Gn3s5bcKW23dtZ3N7eW2tlBXM1vxoqEOAazb0iaUlK9ako4pxByfdbeFuIGBWgZ7HB7/fkV5cZrSt+MRr1TNd/ewk9hAbkows4etYDRKBRSD7eitz9aSdU9YW7RPeYASV5w3yFmvZbzgPF9aUJ+RPyRMiH7AzkNZWbvdXwaJP1yXP5IxrYU46AzrM5/IMf/aqjG3A==

Steps to reproduce

<script setup lang="ts"
  generic="
    TOption extends { [K in TOptionText | TOptionValue]: string },
    TOptionText extends string = 'text',
    TOptionValue extends string = 'value',
  "
>
const props = withDefaults(
  defineProps<{
    options: Array<TOption>
    optionText?: TOptionText
    optionValue?: TOptionValue
  }>(),
  {
    optionText: 'text', // type error
    optionValue: 'value' as TOptionValue, // even so
  },
)
</script>

<template>
  <div>
    <!-- type errors in `options` and `optionValue` -->
    <div
      v-for="option in options"
      :key="option[optionValue]"
    >
      {{
        // no error
        option[props.optionText]
      }}
    </div>
  </div>
</template>

What is expected?

Defining defaults and accessing props in template should be possible with generics.

What is actually happening?

This particular component works perfectly fine outside (see App.vue), but inside there is multiple problems.

At first, there is no way to specify defaults by withDefaults. Forcing type by as or redeclaring generic types like that TOptionText extends 'text' = 'text' can't help.

At second, accessing to props in template ends up with type errors. This is happening even without withDefaults.

System Info

No response

Any additional comments?

You can get rid of errors in template by using props.propName like access. But sometimes you can't.

jh-leong commented 3 months ago

In your case, the type errors you're encountering with withDefaults and generics in defineProps are expected behavior, see TS Playground

zumm commented 3 months ago

In your case, the type errors you're encountering with withDefaults and generics in defineProps are expected behavior, see TS Playground

I understand why typescript complains about this code, but i still can suppress error by casting type with as, see. I can't do the same with withDefaults.

offtop: but i don't understand why typescript complains about something like that too:

function bar<T extends 'text'>(text: T = 'text') {}
jh-leong commented 3 months ago

offtop: but i don't understand why typescript complains about something like that too: function bar(text: T = 'text') {}

A simple counterexample is when T is never, which also satisfies the generic constraint. This results in the error "text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text"'.(2322).

You can get rid of errors in template by using props.propName like access. But sometimes you can't.

As a workaround, check out this playground. It seems that something unexpected happens when using option[optionValue]...

k983551019 commented 2 months ago

https://play.vuejs.org/#eNrdU01v2zAM/SuELm6BIMbW7eK5HfZRDNthK7Zgl6oYDIdx1dmSIMlOhiL/faTkpM7QFgN668ki+UjwPT7finfWzoceRSFKXztlA3gMvYW20s2pFMFLcSa16qxxAT6hRqdqWDnTQTbPx5gHZHvQB9PZPYKDsSx1bbQPYGxQ9IBTuLyVGkBXHRaQfYtpeJHNODlUbc9Zo7l1O4N7oC8PoWFtGHoldZknKrQ4BQE721YBKQIodxSKcQ3iOL6kAMgTKFJ4GFHmk5liRiIRs5Vq5jfeaFIy7ipFTVNUiy6tS91FYsG1qm3N+kvMBddjJBJ7rrH+fU/+xm84J8WFQ49uQCn2tVC5BkMqn//4iht674udWfYtoR8pfkdv2p53TLD3vV7S2hNc3PZzPK/SzcKfbwJqvyPFizJyG/FS0LlZwIeo3617Mn8V++hqpOLETHdu5Ho0JD8mnuSwSQ2UWXCCbpEMZp2xbK+1CtcfcVX1bfBH3LDEldJ4weVyXGe87NsCFpdXb6a5BUlVgA+OGB8Ufia/TSrbs6PjyP1gaBqQBfoko/4zIIvGTSWS4JhGPWbcpRrigyTkME/xxIok4e5v+x/9nihXpf88A8F+DejYxyQYmXF+8lps/wKFs7Aw How to deal with this? definePropswrites the default values, but using generic invalidates the default values

zumm commented 2 months ago

@k983551019 You should mark props as optional, withDefaults doesn't do it for you in case of generic for some reason. Don't know why tho.