<component> is not replaced when using v-bind={is: ...}
Version
3.0.0
Reproduction link
https://codesandbox.io/s/kind-fire-2p58g?file=/src/App.vue
Steps to reproduce
Open the Link, see the underlined error / Its not running
What is expected?
To be able to bind is
with the v-bind="obj"
syntax
What is actually happening?
It only works with a separate is
attribute
Replacement can only work reliably if the presence of the is
attribute is guaranteed. That is not the case when you are using the define object syntax, as that by nature means that there is in dynamic JavaScript expression involved, which during runtime, may or may not return an object with an is
property.
While we could make the compiler create the necessary dynamic component resolution code whether or not this risk exists, I don't think it makes sense to support this kind of foot gun.
Right now, the compiler expects the presence of that is attribute in order to successfully transform this piece of template into a dynamic component resolution. What we could do, is to warn in the console when the is
property is not statically defined.
This problem lost me an entire afternoon. I was trying the same in the vue 2 codepen for the same feature and it was working, so it ended up being a really frustrating day.
It might help someone who will have the same problem in the future:
Because I was iterating an array (json schema to generate inputs) and calling a method on its elements to determine the component name and its props, I really needed this feature, because I was not going to process the same algorithm for every property needed for the component.
In the end I created a new component with a sole
fixed this problem this way:
<component
v-bind="(({ is, ...o }) => o)(componentOptions)"
:is="componentOptions.is"
</component>
yes, it looks hacky but it works and this way is
won't show up in the rendered HTML as an attribute on the rendered component.
I think you ate killing some performance there because that function will lead to unnecessary rerendering. Why not just v-bind="{...obj, is: undefined}"
?
that will still render on the html is="undefined"
but yeah didn't check its performance, if it's true what you say then I suppose the best option is to create another computed property without the is
attribute
@kepi0809 you sure? Afaik, undefined props are skipped
weird... this was the first approach I tried to fix this problem and it didn't seem to work then, but now I tested it again and it works with both is: null
and is: undefined
.
Thank you for for pointing again in that direction :+1:
This problem lost me an entire afternoon. I was trying the same in the vue 2 codepen for the same feature and it was working, so it ended up being a really frustrating day.
It might help someone who will have the same problem in the future:
Because I was iterating an array (json schema to generate inputs) and calling a method on its elements to determine the component name and its props, I really needed this feature, because I was not going to process the same algorithm for every property needed for the component.
In the end I created a new component with a sole tag with an explicit "is" and the rest of the properties separated. I then use a computed prop to run my algorithm passing the prop (json schema object) as the algorithm starting point.
met the same problem, and is there any solution, my friend? try several ways and only fixed the props as a computed data could meet the demand.
<template>
<KeepAlive>
<!-- it works -->
<Component :is="currentTab.component" v-bind="props" />
<!-- it does not work -->
<Component :is="currentTab.component" v-bind="currentTab.props" />
</KeepAlive>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { ITabType } from "@/components/Tabs.vue";
import { Channel, Complay } from "@/UnionTask";
enum ETab {
channel,
complay,
}
interface ITabConfig extends ITabType<ETab> {
component?: typeof Vue;
props?: Record<string, any>;
}
@Component({
components: {
Channel,
Complay,
},
})
export default class ChannelUnion extends Vue {
unionInfo: Record<string, string> = {}; // union info
channelListInUnion: Record<string, string>[] = []; // channel list
currentTab: ITabConfig = this.tabConfig?.[0];
/**
* @description
* Dynamic parameters of dynamic components
* Because tabConfig is an array props cannot be declared directly in the object,
* an additional dynamic props must be maintained independently
* @see https://github.com/vuejs/core/issues/2279#issuecomment-800608949
*/
get props() {
if (this.currentTab.value === ETab.channel) {
return {
unionInfo: this.unionInfo,
channelListInUnion: this.channelListInUnion,
};
}
return {};
}
get tabConfig(): ITabConfig[] {
return [
{
value: ETab.channel,
label: "channel task",
component: Channel,
props: {
unionInfo: this.unionInfo,
channelListInUnion: this.channelListInUnion,
},
},
{
value: ETab.complay,
label: "complay task",
component: Channel,
},
];
}
onTabChange(tab: ITabConfig) {
this.currentTab = tab;
}
}
</script>
<style lang="scss" scoped></style>