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

Open
wants to merge 1 commit into
base: master
from

Conversation

@yyx990803
Copy link
Member

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

Akryum commented Aug 17, 2019

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

@thecrypticace

This comment has been minimized.

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

This comment has been minimized.

Copy link

ycmjason commented Aug 17, 2019

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

This comment has been minimized.

Copy link

ycmjason commented Aug 17, 2019

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

@rawrmonstar

This comment has been minimized.

Copy link

rawrmonstar commented Aug 17, 2019

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

This comment has been minimized.

Copy link
Member

liximomo commented Aug 17, 2019

@ycmjason computed can be used as a readonly ref:

const readonlyWindowWidth = computed(() => window.innerWidth);
@Aaron-Pool

This comment has been minimized.

Copy link

Aaron-Pool commented Aug 17, 2019

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

This comment has been minimized.

Copy link

skyline0705 commented Aug 17, 2019

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

@pbastowski

This comment has been minimized.

Copy link

pbastowski commented Aug 17, 2019

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

@exodusanto

This comment has been minimized.

Copy link

exodusanto commented Aug 17, 2019

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

@iNerV

This comment has been minimized.

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Member

LinusBorg commented Aug 17, 2019

@pbastowski there's no auch Plan or desire.

@afontcu

This comment has been minimized.

Copy link

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

Akryum commented Aug 17, 2019

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

@boonyasukd

This comment has been minimized.

Copy link

boonyasukd commented Aug 17, 2019

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

This comment has been minimized.

Copy link

sylvainpolletvillard commented Aug 17, 2019

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

This comment has been minimized.

Copy link

dugajean commented Aug 17, 2019

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

@LinusBorg

This comment has been minimized.

Copy link
Member

LinusBorg commented Aug 17, 2019

@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

This comment has been minimized.

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

This comment has been minimized.

Copy link

smolinari commented Aug 17, 2019

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

This comment has been minimized.

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".
@jbruni

This comment has been minimized.

Copy link

jbruni commented Sep 28, 2019

@Jexordexan - I also prefer 2️⃣

@agaluf

This comment has been minimized.

Copy link

agaluf commented Oct 2, 2019

Either 1️⃣ or 2️⃣ is fine as both vue- and v- is generally used to refer to vue packages. My vote goes to 2️⃣, as it‘s clearer.

@mgesmundo

This comment has been minimized.

Copy link

mgesmundo commented Oct 4, 2019

Me too 2️⃣

@desislavsd

This comment has been minimized.

Copy link

desislavsd commented Oct 4, 2019

A few questions about template refs:

In the new API it seems that the ref attribute expects a path to property exposed by the setup() method.

  1. If so, can I pass more complex path like ref="refs.foo" or what would be the way to scope all my refs

  2. What happens in case of ref="foo" where foo is not exposed during setup()? Will the element be accessible via this.$refs.foo wherever this points to the component instance?

  3. Won't it be cool if in case a template ref is used along with v-for and points to a property that is a ref([]) with an empty array as an initial value to automatically behave as in the example with v-for - reset the array onBeforeUpdate and assign all the elements;

@DCzajkowski

This comment has been minimized.

Copy link

DCzajkowski commented Oct 5, 2019

I am curious if this was raised anywhere, but what is the benefit of using the "setup" function inside an object instead of just function?

import { reactive } from '@vue/composition-api';

export default () => {
  const state = reactive({
    isDark: true,
  });

  const toggleTheme = () => (state.isDark = !state.isDark);

  return () => (
    <button vOn:click={toggleTheme}>
      {/*  */}
    </button>
  );
};

We also do not lose the type information:

import { reactive, Setup } from '@vue/composition-api';

const setup: Setup = () => {
  const state = reactive({
    isDark: true,
  });

  const toggleTheme = () => (state.isDark = !state.isDark);

  return () => (
    <button vOn:click={toggleTheme}>
      {/*  */}
    </button>
  );
};

export default setup;

Does it collide with functional components? If yes, can we mitigate it somehow?

@Almoullim

This comment has been minimized.

Copy link

Almoullim commented Oct 5, 2019

@DCzajkowski I believe this is what you are talking about?
Question from luwanquan #78 (comment)
And Evan's reply #78 (comment)

@DCzajkowski

This comment has been minimized.

Copy link

DCzajkowski commented Oct 6, 2019

@Almoullim So this means it will be supported? Awesome! Thank you for the answer!

@splincool

This comment has been minimized.

Copy link

splincool commented Oct 7, 2019

I have read @LinusBorg presentation slides (Vue London) and it looks very complicated. Maybe he chose bad examples to introduce new features, but when @NataliaTepluhina made her presentations months ago it was so clear and understandable.
So it is still a lot of questions where the framework goes and what we will get at the end...

@LinusBorg

This comment has been minimized.

Copy link
Member

LinusBorg commented Oct 7, 2019

@splincool I'm sorry to hear that you found my slides hard to understand.

You should keep in mind though, that slides like these are made to go with the actual talk - not every bit of information and explanation that I talked about is in the slides. Also, this talk was the "middle" one in a series of three talks going from introduction to real world examples (mine) to live coding. In that sequence and on video they may give you a different image than looking at the slides on their own.

The Feedback that I got by participants of the concerence was positive and one of excitement rather than confusion.

Have you actually played with it already?

If you wanna talk about concerns it I'm available on discord in the evenings (european time) usually.

@smolinari

This comment has been minimized.

Copy link

smolinari commented Oct 15, 2019

FYI. Good video on the composition API for those still questioning its usefulness.

https://www.youtube.com/watch?v=6HUjDKVn0e0

Scott

@lionskape

This comment has been minimized.

Copy link

lionskape commented Oct 22, 2019

@yyx990803 in Vue-next there is a solution

const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
/** awesome code */
export function ref(raw: any) {
  /** awesome code */
  raw = convert(raw)
  /** awesome code */
}

Is it possible to remove automatical reactive conversion? It will be a good solution to use refs for state management based on reference equality.

For example, if we have some stream, that yields a new object on each change. It will be good to use refs for watching on changes, but it will be a performance leak if we wrap an object on each tick with reactive proxy.

@lionskape lionskape referenced this pull request Oct 22, 2019
@desislavsd

This comment has been minimized.

Copy link

desislavsd commented Oct 23, 2019

Is it possible to make the getter function of a computed to receive as argument the previously calculated value and undefined if there is no such ( the first time ). This would be useful in the 2.x object API as well.

Here is an example

computed: {
    allUniqueValues( old ){
         old = old || [];
         return [ ...Set( [...old, ...this.dynamicArray ] ) ]
    }
}

The idea here is that allUniqueValues should store all unique values that have ever been encountered in the dynamicArray. Currently to achieve this I have to define allUniqueValues in data and then register a watcher for the dynamicArray at least if there are no other dependencies.

@nekosaur

This comment has been minimized.

Copy link

nekosaur commented Oct 23, 2019

@desislavsd That's fairly easy to encapsulate using the composition api. Something like this

function uniqueArrayValues (arr) {
  const set = ref([]);
  
  watch(arr, () => {
    set.value = [...new Set([...set.value, ...arr.value])]
  })

  return set
}
@nek4life

This comment has been minimized.

Copy link

nek4life commented Oct 24, 2019

I'm sure this has been covered, but is there a reason why both reactive and ref are needed? Reactive seems convenient, but it's confusing when I need to call value or not. Admittedly I just installed the composition api plugin, but this was the first case I ran into. I'm also currently not running typescript which probably adds insult to injury, however there will likely be users where this is the case. I'll also try typescript to get a feel of whether this helps or not when dealing with the type different object types.

@michaeldrotar

This comment has been minimized.

Copy link

michaeldrotar commented Oct 24, 2019

I'm sure this has been covered, but is there a reason why both reactive and ref are needed?

@nek4life everyone is asking this same question... I myself had.. but it's been answered a dozen different ways at this point.

There's a detailed section in the doc: https://vue-composition-api-rfc.netlify.com/#ref-vs-reactive
There are also a hundred different articles and videos on the topic.. personally, this one really helped me appreciate ref: https://dev.to/ycmjason/thought-on-vue-3-composition-api-reactive-considered-harmful-j8c

@nek4life

This comment has been minimized.

Copy link

nek4life commented Oct 24, 2019

@michaeldrotar yes I've read through the docs and I tend to agree with the article you've linked.

The first thing I tripped up on when using the new API was using the spread operator on a reactive() in order to not have to write state in my template.

<template>
    <div id="app">
        {{ message }}

        <button @click="updateMessage">Update Message</button>
    </div>
</template>

<script>
    import {createComponent, reactive} from '@vue/composition-api';

    export default createComponent({
        name: 'App',
        setup() {
            const state = reactive({
                message: "Hello World!"
            });

            function updateMessage() {
                state.message = 'Bye World!'
            }

            return {
                updateMessage,
                ...state
            }
        }
    })
</script>

Adding toRefs fixed the issue, but I guess I don't understand why the values inside reactive aren't refs to start with.

@Akryum

This comment has been minimized.

Copy link
Member

Akryum commented Oct 24, 2019

@nek4life

why the values inside reactive aren't refs to start with

The entire reason there are refs is to wrap values in object so they can be passed as references (hence the name ref) - and this is the only way in JavaScript as there are no pointers like in C. So we write myRef.value and not just myRef.

You don't really need refs inside a reactive object created with reactive because the values are already wrapped in the reactive object itsefl. In other words, you don't need myReactiveObject.someProperty.value, you just need myReactiveObject.someProperty.

Rule of thumbs: you need a dot for anything to be reactive.

@nyxtom

This comment has been minimized.

Copy link

nyxtom commented Oct 25, 2019

How do you use the vue-composition plugin and compile vue to not include the 2.x options (in order to get a smaller build output)? Is this something that is possible today?

@LinusBorg

This comment has been minimized.

Copy link
Member

LinusBorg commented Oct 25, 2019

No it's not. This will only ever be possible with the new architecture we implemented for v3

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Nov 6, 2019

This RFC is now in final comments stage. An RFC in final comments stage means that:

  • The core team has reviewed the feedback and reached consensus about the general direction of the RFC and believe that this RFC is a worthwhile addition to the framework.
  • Final comments stage does not mean the RFC's design details are final - we may still tweak the details as we implement it and discover new technical insights or constraints. It may even be further adjusted based on user feedback after it lands in an alpha/beta release.
  • If no major objections with solid supporting arguments have been presented after a week, the RFC will be merged and become an active RFC.
@jaeming

This comment has been minimized.

Copy link

jaeming commented Nov 6, 2019

@LinusBorg

This comment has been minimized.

Copy link
Member

LinusBorg commented Nov 6, 2019

Those are good frameworks and valid choices if they better suit your preferences.

@doncatnip

This comment has been minimized.

Copy link

doncatnip commented Nov 8, 2019

What is the plan for ending lifecycle hooks for keep-alive components ? Right now, if I want to trigger something once a component gets destroyed I have to use destroyed() from the object API since onUnmounted gets called every time one navigates away from the component. There is no onDestroyed in the composition API.
vuejs/composition-api#178

@Akryum

This comment has been minimized.

Copy link
Member

Akryum commented Nov 12, 2019

@doncatnip Looks like a bug with the plugin.

@jaeming

ref vs reactive

ref => single reactive value of primitive type, or object/array - easy to pass around in function arguments/returns
reactive => reactive object for simple use cases

constructor vs setup

There is no class in the core API?!?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.