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

Composition API (revised from #42 Function-based Component API) #78

Merged
merged 3 commits into from Nov 12, 2019

Conversation

yyx990803
Copy link
Member

@yyx990803 yyx990803 commented Aug 17, 2019

Rendered | Source

This is a revised version of #42.

Editorial Updates

  • Renamed the proposal to Composition API
  • Adjusted adoption strategy and position the API as an advanced, additive API that works alongside existing API
  • Reorganized flow of the document to better convey the motivation of introducing the API
  • Added separate API reference so that the RFC can focus on introducing the idea instead of technical details

Technical Updates

Even if you are familiar with the previous proposal, it is strongly recommended to read this new version in its entirety.

@ceigey
Copy link

ceigey commented Aug 17, 2019

Looks like a great iteration over the previous API. The explanation as to how it all works and comes together was also really good.

I did feel a tiny bit lost in the middle though, seeing so many hypothetical examples outside the normal intended usage, but that may have been simply due to the pace I was reading at or due to my mind wondering and wondering if I could... repurpose this API to somehow run lit-html with Vue's reactivity system...

Actually, that makes me wonder, for the examples where we see the API used outside of a component context, would anything work properly? Could I use reactive() and watch() in my index.js as is and have a mini-Vuex or Rxjs and implement the Meiosis pattern?

If this documentation is to be repurposed as a guide in the future (e.g. after release), perhaps a nice and short coloured "warning panel" above those code samples would be good to warn the user that the code is just for demonstration purposes.

I did notice there were already warnings but I couldn't guess their scope/severity.

@Akryum
Copy link
Member

Akryum commented Aug 17, 2019

@ceigey The reactivity API should work outside of Vue components.

@thecrypticace
Copy link

thecrypticace commented Aug 17, 2019

Template Refs

This makes me so incredibly happy! 🙌

The general direction of the updated proposal is excellent and much more clear. It's very well done. The addition of toRefs is also a welcome one. The names of reactive and ref are rather good as well.

@ycmjason
Copy link

Great stuff.

May I ask again why ref is preferable over reactive when returned from a composition function?

Also, is there anyway to set ref to become readonly?

@ycmjason
Copy link

Would there be an official naming guidelines for refs? E.g. prepending with Ref?

@rawrmonstar
Copy link

This looks really good! I wonder, besides from taking inspiration from React hooks, is there a reason the functions follow the use* naming convention? IIRC the naming convention is to remind the user that the rules of hooks apply, but it seems like Vue doesn’t have any such rules.

@liximomo
Copy link
Member

liximomo commented Aug 17, 2019

@ycmjason computed can be used as a readonly ref:

const readonlyWindowWidth = computed(() => window.innerWidth);

@Aaron-Pool
Copy link

Super well written! And, as someone who followed all the crazy ups and downs of the original RFC, I think this revision did an excellent job of addressing most of the overarching themes of that discussion 👌

@skyline0705
Copy link

maybe there should have a lifecycle hook called onServerPrefetch in the document……😂

@pbastowski
Copy link

Is there a plan to retire the V2 api in the future, perhaps in V4 or later, in favor of this new proposal?

@exodusanto
Copy link

exodusanto commented Aug 17, 2019

vue-functional-api plugin will be updated or renamed in order to try these new features? 💪🏻

@iNerV
Copy link

iNerV commented Aug 17, 2019

i love this api, but i need more example with props, template refs, $listeners, $attrs, and etc. without this how i can get props or template ref inside function which outside conponent object / setup func? $listeners and $attrs wil be merged?

@afontcu
Copy link
Member

afontcu commented Aug 17, 2019

vue-functional-api plugin will be updated or renamed in order to try these new features? 💪🏻

"[...] It has been made available as a 2.x plugin via the vue-function-api library. Note that the library is still reflecting a previous version of this RFC - we are working on updating it to match the latest API specs."

(source)

@LinusBorg
Copy link
Member

@pbastowski there's no auch Plan or desire.

@afontcu
Copy link
Member

afontcu commented Aug 17, 2019

Here's some (hopefully actionable) feedback for the docs:

  • Moving from "Here double is an object that we call a 'ref'" to Ref unwrapping felt... abrupt. I was reading about a topic, and I felt like another one came up out of the blue while I was still trying to get my head around .value (<--- this is me being a bit dramatic, but still 😇)
  • The useX is used in the create folder examples, and it is suggested as a best practice afterwards, but there's no explanation or reasoning about the naming. React introduced it so that their linter rules can detect Hooks, but AFAIK this is not needed in Vue land.
  • Even though the new API "will be positioned as an advanced feature", I also feel that dropping this is a huge win for newcomers. I believe this could be stated more clearly.

Apart from that, I feel that whe way the new composition API proposal is presented is great. Kudos to the team.

@Akryum
Copy link
Member

Akryum commented Aug 17, 2019

useX could be a convention to make it clear it's a composition function and that it belongs in setup.

@beeplin
Copy link

beeplin commented Aug 17, 2019

@Akryum I sometimes also feels it awkward to name a composition function as something like “useCreateFolder”. “use” and “create” are both verbs, making it not valid English.

@Akryum
Copy link
Member

Akryum commented Aug 17, 2019

To me it reads like "use create folder feature".

@boonyasukd
Copy link

I like the new API name, since the previous name made people to mistook the API to be FP. Once they actually start using the API it should become clear to them that there's no paradigm shift to be made.

What is unclear to me now, though, is about provide/inject. From the doc, it seems that inject() always returns a ref, but the doc doesn't mention about reactive at all. Currently in my test project, I have a function that provides a deeply nested object extracted from a reactive store. So my questions are:

  • When I inject a reactive object, would that object be inside .value of a ref?
  • When I inject an object extracted from a reactive object, would it also be in .value of a ref?

If possible, I would prefer not having to unwrap reactive (or its nested object) out of a ref when using provide/inject since both of these are already reactive on their own.

@sylvainpolletvillard
Copy link

I am confused by the way you introduce the Ref-like container with computed and .value.

The trade-off is that in order to retrieve the latest value, we now need to access it via .value:

but then you introduce automatic ref unwrapping when used in reactive objects, which gets rid of this trade-off:

when a ref is nested as a property under a reactive object, it is also automatically unwrapped on access (...) no need to use state.double.value

So it looks like these Ref-like containers, while being unavoidable, could just be an implementation detail that users would not have to deal with directly if they are just using reactive() objects.

We have discussed whether it is possible to completely avoid the Ref concept and use only reactive objects, however:
Computed getters can return primitive types, so a Ref-like container is unavoidable.
Composition functions expecting or returning only primitive types also need to wrap the value in an object just for reactivity's sake. It's very likely that users will end up inventing their own Ref like patterns (and causing ecosystem fragmentation) if there is not a standard implementation provided by the framework.

I would like to understand why you think reactive objects could not be the standard pattern returned by composition functions. What is the usecase for a composition function that is required to only return a primitive, and not a reactive object instead ?

It is also still not clear which one between ref() and reactive() is the recommended method. reactive() is introduced first, but starting from "Code organization" section, it is not used anymore, and ref() is used everywhere instead. Why ? What is the thinking behind this ?

@dugajean
Copy link

I'm also facing the same confusion on when to use ref vs reactive. What's the recommended way and why?

@LinusBorg
Copy link
Member

@boonyasukd

  • When I inject a reactive object, would that object be inside .value of a ref?

Yes. inject always returns a ref

  • When I inject an object extracted from a reactive object, would it also be in .value of a ref?

Yes. inject always returns a ref.

@LinusBorg
Copy link
Member

LinusBorg commented Aug 17, 2019

So it looks like these Ref-like containers, while being unavoidable, could just be an implementation detail that users would not have to deal with directly if they are just using reactive() objects.

Not entirely, as the section that you quote right after demonstrates. Template refs are another example.

I would like to understand why you think reactive objects could not be the standard pattern returned by composition functions. What is the usecase for a composition function that is required to only return a primitive, and not a reactive object instead ?

Like the quote says being said, you of course can use reactive to build your own "ref", by doing this:

useDragOver(elementRef) {
  const state = reactive({ isDraggingOver: false })

  watch(() => elementRef,
    (el) => {
      // pseudocode, don't do this, won't work reliably because DnD API sucks.
      el.addEventListener('dragenter', () => state.isDraggingOver = true)
      el.addEventListener('dragleave', () => state.isDraggingOver = false)
    }
  )

  return state

But then people might want do this:

const { isDraggingOver } = useDragOver(elementRef) 

...and it won't work, the connetion to the function's internal state will be lost, and with it, the reactivity. isDraggingOver will never update.

So People have to remember do:

const { isDraggingOver } = toRefs(useDragOver(elementRef)) 

Or, you simply use a ref in the first place:

useDragOver(elementRef) {
  const isDraggingOver = ref(null)

  watch(() => elementRef,
    (el) => {
      // pseudocode, don't do this, won't work reliably because DnD API sucks.
      el.addEventListener('dragenter', () =>isDraggingOver.value = true)
      el.addEventListener('dragleave', () => isDraggingOver.value = false)
    }
  )

  return isDraggingOver

Then consumers can simply do this:

const isDraggingOver = useDragOver(elementRef)

or, as an Alternative:

useDragOver(elementRef) {
  const state = reactive({
   isDraggingOver: ref(false) 
  })

  watch(() => elementRef,
    (el) => {
      // pseudocode, don't do this, won't work reliably because DnD API sucks.
      el.addEventListener('dragenter', () => state.isDraggingOver = true)
      el.addEventListener('dragleave', () => state.isDraggingOver = false)
    }
  )

  return state

Now destructuring works as expected - but isDraggingOver is a ref.

It is also still not clear which one between ref() and reactive() is the recommended method.

Well, you kind of need both to make full use of the API, so there's no single recommended method.

that being said, we think that it makes more sense to introduce these new concepts with reactive() first as it's familiar, wherease ref() is something people have to wrap their head around first.

@smolinari
Copy link
Contributor

I just want to say, I think this is a great "next version" of the original API RFC. The name is much better too. This new RFC shows very much the willingness of the Vue team to listen and compromise with the community, which is a golden advantage Vue has. Please keep that up!

I must admit, I too am now somewhat confused with how inject is supposed to work, especially after the comments just made here. But, I'm going to chalk my confusion up to my own lack of understanding of much more advanced JS programming and will be waiting for it to "click" at some point, as it did with the initial RFC and the whole concept of the (now called) Composition API.

Scott

@LinusBorg
Copy link
Member

LinusBorg commented Aug 17, 2019

I must admit, I too am now somewhat confused with how inject is supposed to work

  1. No matter what you pass down with provide, you will always get it wrapped in a ref from inject.
  2. Possible footgun: When you pass a primitive value to provide, it will be wrapped in a ref, but it won't be reactive (We can/will probably warn you about this in development mode). But that's the general challenge of refs to "get".

@smolinari
Copy link
Contributor

Well, I''d say generally create a new issue with the suggestion, so it gets the proper attention.

Scott

@mesqueeb
Copy link
Contributor

Should this issue be labeled 3.x ?

@aphex
Copy link

aphex commented Jan 27, 2020

hey @mesqueeb I am not sure if your talking about the issue I brought up or not. @smolinari thanks for chiming in. Before I created an issue I was just trying to understand if this even was an issue. Is there already a way to do this? Does it fit within the overall concept of this API? Does it even make sense? :)

@mesqueeb
Copy link
Contributor

I meant this entire thread should get the label 3.x attached. Only a team member can add tags to issue threads though.

@customautosys
Copy link

Dear Evan,

As a matter of syntax, I would like to suggest that instead of using an object with a setup function, we simply export a function instead. I think it would look cleaner.

I.e. instead of

export default{
    setup(){
        let var=ref(0);
        ...
        return {var};
    }
};

We can simply have:

export default function(){
    let var=ref(0);
    ...
    return {var};
};

This would save a bit of typing and look a bit nicer.

Or perhaps, this could be an alternate syntax and the setup function be preserved for those who wish to mix object syntax and composition syntax.

@bekalshenoy
Copy link

bekalshenoy commented Jun 11, 2020

Dear Evan,

As a matter of syntax, I would like to suggest that instead of using an object with a setup function, we simply export a function instead. I think it would look cleaner.

I.e. instead of

export default{
    setup(){
        let var=ref(0);
        ...
        return {var};
    }
};

We can simply have:

export default function(){
    let var=ref(0);
    ...
    return {var};
};

This would save a bit of typing and look a bit nicer.

Or perhaps, this could be an alternate syntax and the setup function be preserved for those who wish to mix object syntax and composition syntax.

How this syntax can support props and components

@customautosys
Copy link

Props: same as React functional components, pass as argument.
Components: return it in the return value like refs and reactives.

@smolinari
Copy link
Contributor

smolinari commented Jun 11, 2020

@siauderman - I believe there is a necessity of having the setup() function and that is, it should only be called once, then Vue's reactivity system should take over, simplifying our side of the logic (unlike, and I think, a step above React). It also allows the Options API to work in parallel in the same component. At least that is my understanding. :)

Scott

@ggedde
Copy link

ggedde commented Jun 11, 2020

@siauderman In the beginning there was mention of a short-hand method that I think was the same format you were suggesting, but I am having trouble finding any documentation on it. Maybe it got removed or maybe it still exists, but hasn't been implemented yet in the beta or maybe I am just remembering wrong.

I believe it was something like:

import { ref } from 'vue';
export default (props, context) => {
  const items = ref([]);
  return {
    items
  }
}

Can anyone remember if this was going to be implemented, but was removed for some reason. Maybe because it wouldn't allow the Options Api to continue to work. Or maybe I am missing something.

@customautosys
Copy link

It could easily be detected, typeof ...==='function', if it is an object then you can use options API as well, but if not you can't.

It's also possible to call it once only if it's a function that's exported. It can be an alternative syntax so those who need to combine composition with options API can continue to use setup().

Anyway, not crucial, a minor suggestion, just syntactic sugar that saves (a very small bit of) typing and a level of indentation.

@SilentDepth
Copy link

@ggedde I also remember the API you are talking about. I recall it was saying (somewhere in RFCs) something like "you can also directly return the setup function as the component definition...". But I failed to find those words too.

The closest one I found:

https://github.com/vuejs/rfcs/blob/master/active-rfcs/0008-render-function-api-change.md#functional-component-signature

@leopiccionia
Copy link

@ggedde Perhaps you're thinking about the revamped functional components, that indeed have the signature (props, { attrs, emit, slots }) => any, but very different semantics from setup functions.

(Composition API, however, allows setup to return render functions, what's a hybrid from both approaches.)


Directly exporting functions would clash with functional components, and I don't know if Vue could distinguish both at runtime without calling the function at least once.

It'd be easy, however, to create a helper at userland:

function setup (setupFn) {
    return defineComponent({ setup: setupFn })
}

That could be used inside a SFC like this:

export default setup((props) => {
    const foo = ref(props.foo)
    return { foo }
})

@SilentDepth
Copy link

@leopiccionia Technically it's possible to distinguish those two cases:

const FunctionalFoo = (props, context) => { ... }
FunctionalFoo.functional = true // Mark it a functional component

export default FunctionalFoo

It's not much clear and convenient though.

I really do remember I ever saw an RFC saying that you can directly export setup function if you don't need to define other component options (components, props, etc.). However, I tried this approach few minutes ago with latest vue-next and failed. Maybe it was revoked.

@ggedde
Copy link

ggedde commented Jun 12, 2020

@SilentDepth Yeah, I remember seeing that too and even remember watching a few videos with that in it. My guess it that it got removed.

@desislavsd
Copy link

I have just noticed that we now have watchEffect. Very nice feature! Only, I wish it was named just effect.

@pikax
Copy link
Member

pikax commented Jun 12, 2020

I have just noticed that we now have watchEffect. Very nice feature! Only, I wish it was named just effect.

There's already effect

@customautosys
Copy link

@leopiccionia Technically it's possible to distinguish those two cases:

const FunctionalFoo = (props, context) => { ... }
FunctionalFoo.functional = true // Mark it a functional component

export default FunctionalFoo

It's not much clear and convenient though.

I really do remember I ever saw an RFC saying that you can directly export setup function if you don't need to define other component options (components, props, etc.). However, I tried this approach few minutes ago with latest vue-next and failed. Maybe it was revoked.

Actually, are the current functional components even necessary in Vue 3?

My understanding is that in Vue 3, they will not offer much of a performance improvement if at all. There is little to distinguish functional components from composition API components (I mean, to my understanding a functional component is just one without reactive state, but reactive states are now merely local variables anyway).

@jods4
Copy link

jods4 commented Jun 15, 2020

Actually, are the current functional components even necessary in Vue 3?

In edge cases, they are. I think this example is found in the docs: a heading whose level can be bound by a prop, something like:

export const Header = (props) => h("h" + props.level, props.title)

Header.props = { level: Number, title: String }

You can't manipulate tag names in a template.

@jacekkarczmarczyk
Copy link

You can do

<component :is="`h${level}`">

you can also create a render function in non-functional component, maybe some more options, anyway your example doesn't seem to be the case where functional component was NECESSARY

@jods4
Copy link

jods4 commented Jun 15, 2020

@jacekkarczmarczyk
Right, insofar as you can create functional component by returning a function from setup(), you can argue that the syntax where a plain function is a component is redundant.

@jods4
Copy link

jods4 commented Jun 15, 2020

@jacekkarczmarczyk and as for why functional components are needed at all:

You can do anything in a functional component. Like looking at your slots contents and manipulating them.

Look no further than Vue core itself for usage:
https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/src/components/TransitionGroup.ts#L99

Good luck porting that code to a template.

@jacekkarczmarczyk
Copy link

component with render function !== functional component

So one case is when you want to have simpler code (your previous h1-6 example)

Another possible advantage would be performance (cpu/memory), and would be nice to see an example where functional ones are significanlty better than normal. Sure you'll save few bytes and cpu cycles when you have component defined as () => h('div') instead of { setup () { return () => h('div') } }` - the question is in what situations gains would more than just a 0.004% improvement

@jbnv
Copy link

jbnv commented Jul 24, 2020

What happens to vm.$options in the Composition API?

I've been trying to write loaders for custom blocks in the Composition API. Vue Loader's page on custom blocks presents a fairly simple loader pattern for injecting options into a component:

module.exports = function (source, map) {
  this.callback(
    null,
    `export default function (Component) { Component.options.docs = ${JSON.stringify(source)} }`,
    map
  )
}

If you inject a console.log(Component) into that exported function, you'll see that the raw component has two properties, options and export, which are apparently identical. I can confirm that this code adds content to Component.options.

However, when I return to my component and its setup method, I can't find any trace of the options I added. The context argument doesn't have an options property, and I haven't found anything to indicate where my custom options are supposed to be.

It makes sense that hey, this is the Composition API you're using, not the Options API. I understand that. But I'd still like to know how I can get the content that I read from custom blocks into the component. I feel like it defeats a bit of the purpose of Vue to not be able to add content to a component through custom blocks or other means.

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

Successfully merging this pull request may close these issues.

None yet