vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
208.04k stars 33.69k forks source link

Functional single file component with components option. #7492

Open terrierscript opened 6 years ago

terrierscript commented 6 years ago

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 commented 6 years ago

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

gregolai commented 6 years ago

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

ywwhack commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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

KumaCool commented 6 years ago

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 commented 5 years ago

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 commented 5 years ago

https://github.com/vuejs/vue/blob/70754084ec2d84e44effeb8f82efda3397a08b0d/types/options.d.ts#L123-L133

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 commented 5 years ago

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 commented 5 years ago

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

dcwarwick commented 5 years ago

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:

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 commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

+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 commented 4 years ago

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: https://github.com/vuejs/vue/pull/8143#issuecomment-482481117

madebycaliper commented 4 years ago

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 commented 4 years ago

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

darkylmnx commented 4 years ago

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 commented 4 years ago

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

darkylmnx commented 4 years ago

Oh... I see.

Le jeu. 18 juin 2020 à 22:30, andreas notifications@github.com a écrit :

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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/vuejs/vue/issues/7492#issuecomment-646289731, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAK4HNNSMXAU3KXBWHRMYKTRXJ2NNANCNFSM4EMWWNEA .

avxkim commented 4 years ago

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.