prop sugar #394
Replies: 16 comments 20 replies
-
I really like the prop label, what if we wanna declare many props like this: <script setup>
prop: num= {
type:Boolean,
default:4}
prop: msg= {
type:String,
required:true
validator:(val) => ![""].includes(val)
}
</script> |
Beta Was this translation helpful? Give feedback.
-
@dajpes now that the <script setup> RFC uses <script setup>
import { defineProp } from 'vue'
prop: num = 4
prop: msg = defineProp({
type: String,
required: true
validator: (val) => ![""].includes(val)
})
</script> So we can still define each People could also use <script setup>
import { defineProps } from 'vue'
prop: ({ num, msg } = defineProps({
num: {
type: Number,
default: 4
},
msg: {
type: String,
required: true
validator: (val) => ![""].includes(val)
}
}))
</script> |
Beta Was this translation helpful? Give feedback.
-
A supplement* |
Beta Was this translation helpful? Give feedback.
-
For reference to this discussion, the |
Beta Was this translation helpful? Give feedback.
-
Sorry forgot to reply... I already added IDE support for defineProps. Please use volar if want to try. |
Beta Was this translation helpful? Give feedback.
-
import { defineProps } from 'vue'
prop: ({ num, msg } = defineProps({
num: {
type: Number,
default: 4,
},
msg: {
type: String,
required: true,
},
})) I wrote like above and reported an error |
Beta Was this translation helpful? Give feedback.
-
the way I am declaring props with <script setup>
const props = defineProps({
num1: {
type: Number,
default: 6
},
num2 {
type: Number,
default: 8
}
})
</script> This seems to work fine, and you can call those values on the Hope it helps |
Beta Was this translation helpful? Give feedback.
-
I am having a thought about the fact that one advantage of composition API is that you can group codes together by feature, but currently with <script setup>
const props = defineProps({
prop1: { type: String, default: 'default' },
prop2: { type: Number, required: true },
prop3: String,
prop4: { type: Boolean, default: false }
})
</script> Then from #369, we have <script setup>
const prop1 = $prop({ type: String, default: 'default' })
const prop2 = $prop({ type: Number, required: true })
const prop3 = $prop(String)
const prop4 = $prop({ type: Boolean, default: false })
</script> this way we can put our props anywhere we want. |
Beta Was this translation helpful? Give feedback.
-
I didn't update this issue so far because I was waiting for ref sugar to stabilize. I still think that this would be good for the reasons you state here (ability to group together logic for related features). There was a comment from Evan in another issue saying that he is already thinking about allowing sugary destructuring for const { prop1, prop2, prop3, prop4 } = defineProps({
prop1: { type: String, default: 'default' },
prop2: { type: Number, required: true },
prop3: String,
prop4: { type: Boolean, default: false }
})
</script> I think this would already be a big improvement. A problem I see is that with this, we end up repeating a lot the variable names, and more with TS and interface Props {
msg?: string
labels?: string[]
}
const { msg, labels } = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
}) I think that a <script setup>
const prop1 = $prop<string?>({ default: 'default' })
const prop2 = $prop<number>({ required: true })
const prop3 = $prop<string>()
const prop4 = $prop<boolean?>({ default: false })
</script> |
Beta Was this translation helpful? Give feedback.
-
vs Why not just allow I am in strong support of adding defaults and validators to
|
Beta Was this translation helpful? Give feedback.
-
FYI vuejs/core#4690 With destructuring you already get default values + local aliasing for free: const {
foo,
bar = 123, // default value
baz: qux = 234, // aliasing + default value
...rest // rest spread also supported
} = defineProps<{ foo: string, bar?: number, baz?: number }>()
watchEffect(() => {
console.log(foo) // will log every time foo changes
}) CaveatsPassing a destructured prop to a functionThis is similar to ref sugar - essentially you need to wrap it in a getter or computed if you want to pass it and retain reactivity: watch(() => destructuredProp, prop => {})
useSomeFeatureThatExpectsRef(computed(() => destructuredProp)) |
Beta Was this translation helpful? Give feedback.
-
As a voice of a user without the knowledge and history the rest of you have, and someone who hasn't used typescript, |
Beta Was this translation helpful? Give feedback.
-
The thing I'm trying to point out here is that we need to type each prop name two times: one for the type and the other for the default value. Is this really necessary? (since TS can infer the type from the default value) const { foo = '', bar = 0 } = defineProps() // can the exposed props be inferred from the destructuring?
const props = defineDefaultProps({ foo: '', bar: 0 }) // { foo: { type: String, default: '' }, bar: { type: Number, default: 0 } } |
Beta Was this translation helpful? Give feedback.
-
@yyx990803 Regarding the new destructuring syntax, it seems be unusable with object that have default values.. const {
foo = {
a: "a in child",
b: "b in child",
},
c
} = defineProps<{ foo: { a?: string, b?: string }, c?: string }>() The parent sends: <Test :foo="{b: 'b from parent component'}"/> When I print foo: I hope Vue can define a clean syntax that allow the simple and common needs that is: to have props that are objects and that (at least some) keys have default values. |
Beta Was this translation helpful? Give feedback.
-
Actually @LifeIsStrange I don't even know how to make this behaviour works using withDefault(). If anyone knows I would like to know how. //Component Foo
interface Props {
mainPayload: {
color: string,
alt: string,
size?: "small" | "medium" | "big"
}
}
const props = withDefaults(defineProps<Props>(), {
mainPayload: (it) => {
return {
...it.mainPayload,
size: "small",
}
}
});
console.log("props: ", props) If I call foo component in Home like below: <!-- Home -->
<Foo :mainPayload="{color: 'red', alt: 'I will sadly never see about size:('}"/> The console.log of props in the Foo component will sadly only show |
Beta Was this translation helpful? Give feedback.
-
Great discussion here and regardless of the syntax, the ability to split up individual props into individual definitions is going to be great for DX and to match the rest of the new composition stuff. It would be great if individual props could be extracted into other files / composition functions as well (ie Forgive me if what I'm writing below is a little off topic, but it seems there is a lot of related discussion here. I'm curious what the reason is for focusing on defining props using the type-only syntax? I get the impression the core team may be pushing for this to be the preferred / idiomatic way when using TS. While obviously no one likes the This isn't great for readability when you have a lot of props, it forces you to destructure the prop, and it's also more awkward to extract sets of shared props. With the object based syntax, I can extract a set of related props (including default values) that may be reused into an object and use it in several components. I can even use functions that generate a set of props. For example: const props = defineProps({
...commonSizeProps,
...getCommonColorProps('dark'),
propForJustThisComponent: { type: String as PropType<'foo' | 'bar'>, default: 'foo' },
}); This plus the fact that it supports all features (including arbitrary validation functions) makes me think the object syntax should just be the standard way of doing things (even in TS) and that the type-only syntax should be discouraged or even deprecated entirely. I'll admit it's slightly more verbose, but not prohibitively so and I think quite clear. |
Beta Was this translation helpful? Give feedback.
-
I want to avoid commenting on the Ref Sugar RFC about this because it may interfere in the discussion about
ref:
. I tried to find if the following proposal was discussed before but I couldn't find public discussions about it. Sorry for the noise if this was already analyzed.Context
In the RFC it is stated that
I think that it is on point, but there is also an asymmetry in the usage of props in the template and in the <script>. For simple components, we would end up with:
With ref sugar, we can move the onClick handler to the script without the
.value
. But if we want to move the multiplication out of the template, we can not directly copy it in a computed, because we need to add theprops.
We could convert the prop to a ref, taking care not to lose reactivity while destructuring (this is also something that we need also when passing the props to composables). We also need to explain what
toRefs
is achieving and the prop name needs to be repeated.The DX also suffers from extra boilerplate needed in this case. If
ref:
ends up being adopted, I think that having better ergonomics for props could also outweighs the cost of adding new syntax.Proposal
Following the same model as
ref:
, we introduce a new labelprop:
to declare components props:When a prop is defined in this way, you can directly use its value in the template and in the script, and reactivity works properly.
Edited:
We could add a
defineProp
type-only helper to support complex validation of props:Implementation
prop:
, it is aggregated to the props block of the component.props.num
.ref:
, if you use$num
it is replaced withtoRef(props,'num')
, so you get a ref that you can pass to composables. If a different syntax is pushed to get the reference fromref:
, that will be the syntax forprop:
too (the important part is that they work in the same way).Benefits
ref:
withprop:
and refactoring is easier because you do not need to addprops.
andtoRef
in all the places the prop is being used.prop:
is optional, if you want a custom validator for example you can declare that props in the normal props blockCons
ref:
).ref:
Alternative
prop:
wants to be avoided,export let
could be used to declare props as svelte does. But I think thatprop:
plays better withref:
for Vue.Beta Was this translation helpful? Give feedback.
All reactions