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

Functional single file component with components option. #7492

Open
terrierscript opened this issue Jan 21, 2018 · 23 comments · May be fixed by #8143
Open

Functional single file component with components option. #7492

terrierscript opened this issue Jan 21, 2018 · 23 comments · May be fixed by #8143

Comments

@terrierscript
Copy link

Version

2.5.13

Reproduction link

NG pattern (functional)
https://codesandbox.io/s/004vv2onw0

OK pattern (no functional)
https://codesandbox.io/s/q9k5q8qq56

Steps to reproduce

I found can't use components option when functional single file component.

<template functional>
  <div>
    <some-children />
  </div>
</template>

<script>
import SomeChildren from "./SomeChildren"

export default {
  components: {
    SomeChildren
  }
}
</script>

It's occure Unknown custom element.

What is expected?

Not occure Unknown custom element and use child component

What is actually happening?

It's occure Unknown custom element


In workaround, it not occure when use Vue.component.

import Vue from "vue"
import SomeChildren from "./SomeChildren"
Vue.component("some-children", SomeChildren);

export default {}

// can use  <some-children />
@jingle2008
Copy link

Ran into exact same issue here, nice workaround!!!

@gregolai
Copy link

Just learning Vue and struggling with this for the past few hours! Thanks for bringing it up!

@ywwhack
Copy link

ywwhack commented Mar 1, 2018

Here is another workaround, it avoids global component, but looks not pretty

<template functional>
  <div>
    <component :is="props.components.SomeChildren"></component>
  </div>
</template>

<script>
import SomeChildren from "./SomeChildren.vue";
export default {
  props: {
    components: {
      type: Object,
      default() {
        return {
          SomeChildren
        };
      }
    }
  }
};
</script>

@alexsasharegan
Copy link

alexsasharegan commented Mar 1, 2018

I think it's worth mentioning here that the error message is quite unintuitive. The Unkown custom element error bubbles up to the first instance component. If for some reason the feature for local functional component registration does not get implemented, at least add a dev warning that says something to the effect of Invalid property "components" on functional component X.

Also, as awkward as it is, registering the unknown component in the first parent instance component clears the error without polluting the global component name scope. It's a strange coupling of components though. Choose your hack for now I suppose.

@caikan
Copy link

caikan commented Apr 8, 2018

@ywwhack Your workaround is great! I made some improvements. We can use injections instead of props, so that props will not be polluted, and the code looks a little prettier.

<template functional>
  <div>
    <component :is="injections.components.SomeChildren"></component>
  </div>
</template>

<script>
import SomeChildren from "./SomeChildren.vue";
export default {
  inject: {
    components: {
      default: {
        SomeChildren
      }
    }
  }
};
</script>

@darkylmnx
Copy link

any update on implementing components option in functional components ?

The workarounds are good but that seems pretty hacky IMO especially when mentioning that <component :is="injections.components.SomeChildren"></component> must be fore dynamic components and not for known components

privatenumber pushed a commit to privatenumber/vue that referenced this issue May 7, 2018
Adds support for the components hash to be used in functional components

fix vuejs#7492
@KumaCool
Copy link

parent:

// template functional 
    v-list
        component(
            :is='injections.components.myListItem'
            v-for='item in props.data',
            :key='item.title',
            :data='item')

// script
import myListItem from './listItem'
export default {
    name: 'myList',
    inject: {
        components: {
            default: {myListItem}
        }
    }
}

children:

// template functional
    v-list-group(v-if='Array.isArray(props.data.children)')
        v-list-tile(slot='activator')
            v-list-tile-content
                v-list-tile-title {{props.data.title}}
        my-list-item(
            v-for='item in props.data.children',
            :key='item.title',
            :data='item')
    v-list-tile(v-else)
        v-list-tile-content
            v-list-tile-title {{props.data.title}}

// script
export default {
    name: 'myListItem'
}

app:

// template
    #app
        my-list(:data='list')

// script
import myList from './list'
export default {
    name: 'App',
    components: {myList},
    data() {
        return {
            list: [
                {title: 1},
                {title: 2},
                {title: 3},
                {
                    title: 4,
                    children: [
                        {title: 41},
                        {title: 42},
                        {title: 43}
                    ]
                },
                {title: 5}
            ]
        }
    }
}

children error: Unknown custom element
multi-level functional components nesting failed.
help me!~

@maksnester
Copy link

Any ideas how to implement dynamic async components inside functional components?

E.g. functional should be simple wrapper like

<component :is="componentName" /> 

where componentName is one of the dynamically imported components?

For non-functional component it looks like this:

<template>
  <component :is="componentName" v-bind="$attrs"/>
</template>

<script>
const someCondition = Math.random() > 0.5

export default {
  name: 'PolicyRequestInfo',
  components: {
    FirstDynamic: () => import('./FirstDynamic'),
    SecondDynamic: () => import('./SecondDynamic')
  },
  computed: {
    componentName() {
      return SomeCondition ? 'FirstDynamic' : 'SecondDynamic'
    }
  }
}
</script>

For functional component I have no idea how to make it work.

@meteorlxy
Copy link
Member

meteorlxy commented Jan 7, 2019

vue/types/options.d.ts

Lines 123 to 133 in 7075408

export interface FunctionalComponentOptions<Props = DefaultProps, PropDefs = PropsDefinition<Props>> {
name?: string;
props?: PropDefs;
model?: {
prop?: string;
event?: string;
};
inject?: InjectOptions;
functional: boolean;
render?(this: undefined, createElement: CreateElement, context: RenderContext<Props>): VNode | VNode[];
}

The type declaration shows that, functional component of current version can't accept components option in fact.

Of course a possible solution is to use the render function:

<script>
import SomeChildren from './SomeChildren.vue'

export default {
  render (h) {
	return h('div', [
      h(SomeChildren),
    ])
  }
}
</script>

@maksnester
Copy link

Yep, it actually works with render functions. I use something like that now:

<script>
const component = Math.random() > 0.5 
    ? () => import('./compA') 
    : () => import('./compB')

export default {
  functional: true,
  render(h, context) {
    return h(component, context.data, context.children)
  }
}
</script>

@ycmjason
Copy link

Would really love to see components option being supported for functional components.

@dcwarwick
Copy link

dcwarwick commented Feb 13, 2019

This feature would certainly extend the usefulness of functional components in a natural way. I for one had not realised that components wasn't supported in functional components until we tried it and found out. We have a library of components as SFCs, and wanted to mark some of the simple ones with no internal state as functional.

At the moment, if a component is a SFC and has child component dependencies and we want to mark it functional, the options would seem to be:

  • convert the template to a render function
  • globally register all the child components required
  • an ingenious workaround described above by @ywwhack @caikan and others that loads the dependency child components using props or inject and then references them with a <component :is="...">

However, in a library of many interlinked components all done as SFCs it is desirable to retain the template for consistency with the other components, and undesirable to globally register names. The workaround for all its ingenuity looks rather messy/fiddly and would require explanation for maintenance. It would be much neater just to be able to declare child components with components exactly as for non-functional SFCs.

Fix #8143 looks good and ready to go. If there isn't a reason not to, can it be delivered? We've held off from making any of our components functional atm, and would use it right away :-)

@decademoon
Copy link
Contributor

I for one had not realised that components wasn't supported in functional components until we tried it and found out.

That was my experience too. I remember being quite surprised when I discovered components weren't supported in functional components. This really, really diminished the usefulness of functional components and I rarely use them because of this.

I do use JSX in my Vue projects because sometimes I need to drop down into the render function, so this isn't technically a huge issue for me, but I like to avoid JSX whenever possible.

I'm hoping Vue 3 will improve on this in some way. React already does functional components well.

an ingenious workaround described above by @ywwhack @caikan and others that loads the dependency child components using props or inject and then references them with a <component :is="...">

I'm not a fan of this hack TBH. I will just stay clear of functional components until this issue is officially resolved.

@sisou
Copy link

sisou commented Nov 28, 2019

I just made a SFC with <template functional> and a components option in the script object, and there are no complaints by the build system or during runtime.

This is with Vue 2.6.6.

Correction: After restarting the build it does not work in fact.

@tettoffensive
Copy link

Is the workaround from @ywwhack / @caikan workaround still the best way to go these days? With all the mentions at the end it's hard to tell if there's an official fix.

@Jared-Dahlke
Copy link

+1. Will not be using the work around as it decreases code readability (since it cannot be found in Vue documentation). Please implement this much needed fix. Thank you

@douira
Copy link

douira commented Mar 24, 2020

I suspect this, like some other features for functional components, will not be implemented since Vue 3 (the alpha/proposal) alleviates the whole issue by making the difference between functional and non-functional components small. See this and the following comments: #8143 (comment)

@madebycaliper
Copy link

hey @maksnester I'm working on this for a Vue 2.6.x project. I'm trying to allow the functional component to accept a name prop to make it even more dynamic. Any clue on how I can implement your solution dynamically?

Asked on Stackoverflow too (here) if you want more details about my problem. Thanks!

@privatenumber
Copy link
Contributor

As an alternative option, vue-import-loader offers support for component resolution in functional components

@darkylmnx
Copy link

Still no improvements on this till now?
I found a future-proof way to do it but still not happy with it:

<script>
import ChildComp from '@/components/ChildComp.vue';
export default {
  components: {
    ChildComp,
  },
};
</script>
<template>
  <component :is="$options.components.ChildComp" any-prop="value" />
</template>

@andreasvirkus
Copy link

@darkylmnx it's a #wontfix since Vue 3 will make functional components irrelevant, as I understood.

@darkylmnx
Copy link

darkylmnx commented Jun 18, 2020 via email

@avxkim
Copy link

avxkim commented Jun 22, 2020

There's RFC: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0007-functional-async-api-change.md

We will just rewrite our functional components as plain functions, that's all, much better, imo.

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

Successfully merging a pull request may close this issue.