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

Difficulty documenting Vue 3 and avoiding a schism #41

Closed
chrisvfritz opened this issue Jul 23, 2019 · 31 comments
Closed

Difficulty documenting Vue 3 and avoiding a schism #41

chrisvfritz opened this issue Jul 23, 2019 · 31 comments

Comments

@chrisvfritz
Copy link
Member

chrisvfritz commented Jul 23, 2019

The Problem

Many on the team would like the functions API to become the new recommended way of writing components in Vue 3. And even if we did not officially recommend it as the standard, many users would still gravitate toward it for its organizational and compositional advantages. That means a schism in the community is inevitable with the current API.

I've also been experimenting with how we might document Vue 3 and have been really struggling. I think I finally have to admit that with the current, planned API, making Vue feel as simple and elegant as it does now is simply beyond my abilities.

Proposed solution

I've been experimenting with potential changes to the API, outlining affected examples to create a gentler and more intuitive learning path. My goals are to:

  • Make the recommended API feel intuitive and familiar.
  • Make changes we're making feel like a logical simplification or extension of our current strategy, rather than a radical change in direction.
  • Reduce the feeling of there being two different ways of writing components.
  • Reduce the number of concepts users have to learn.
  • Reduce the frequency and severity of context switching.

Finally, I think have a potential learning path that is worth attention from the team, though you may want to read my explanations for the proposed API changes before checking it out.

Proposed API changes

1. Rename setup to create

Example

Vue.component('button-counter', {
  props: ['initialCount'],
  create(props) {
    return {
      count: props.initialCount
    }
  },
  template: `
    <button v-on:click="count++">
      You clicked me {{ count }} times.
    </button>
  `
})

Advantages

  • Avoid introducing a new concept with setup, since we already have a concept of instance creation with the beforeCreate and created lifecycle functions.

  • With create, it's more obvious and easier to remember when in the lifecycle the function is called.

  • Since this is first available in created, it will make more intuitive sense that it's not yet available in the create function.

Disadvantages

  • Downsides related to autocomplete and confusion with lifecycle functions can be resolved with proposal 2.

2. Rename lifecycle function options to begin with on

Example

new Vue({
  el: '#app',
  onCreated() {
    console.log(`I've been created!`)
  }
})

Advantages

  • Removes any confusion or autocomplete conflicts if setup is renamed to create (see proposal 1).

  • Removes the only naming inconsistency between the option and function versions of an API. For example, computed and watch correspond to Vue.computed and Vue.watch, but created and mounted correspond to Vue.onCreated and Vue.onMounted.

  • Users only have to make the context switch once, when going from Vue 2 to Vue 3, rather than every time they move between options and functions.

  • Better intellisense for lifecycle functions, because when users type the on in import { on } from 'vue', they'll see a list of all available lifecycle functions.

Disadvantages

  • None that I can see.

3. Consolidate 2.x data/computed/methods into create and allow its value to be an object just like data currently

Example

const app = new Vue({
  el: '#app',
  create: {
    count: 0,
    doubleCount: Vue.computed(() =>
      return app.count * 2
    ),
    incrementCount() {
      app.count++
    }
  }
})
Vue.component('button-counter', {
  props: ['initialCount'],
  create(props) {
    const state = {
      count: props.initialCount,
      countIncrease: Vue.computed(
        () => state.count - props.initialCount
      ),
      incrementCount() {
        state.count++
      }
    }

    return state
  },
  template: `
    <button v-on:click="incrementCount">
      You clicked me {{ count }}
      ({{ initialCount }} + {{ countIncrease }})
      times.
    </button>
  `
})

Advantages

  • It's easier for users to remember which options add properties to the instance, since there would only be one: create.

  • Users don't need to be more advanced to better organize their properties. This one change provides the vast majority of the organizational benefit, without the complexity that can arise once you get into advanced composition.

  • New users won't have to learn methods as a separate concept - they're just functions.

  • It's even less code and fewer concepts than the current status quo.

  • Prevents the larger rift of people using create vs data/computed/methods, by having everyone start with create from the beginning. With everyone already familiar with and using the create function, sometimes moving more options there for organization purposes (e.g. watch, onMounted, etc) will be a dramatically smaller leap.

  • Makes the transition to a create function feel more natural, both for current users ("oh, it's just like data - when it's a function, I just return the object") and new users ("oh, this is just like what I was doing before, except I return the object").

  • Although Vue.computed will return a binding, users won't have to worry about learning the concept of bindings for the entirety of Essentials. Only once we get to advanced composition that splits features into reusable functions will it become relevant, because then you have to worry about whether you're passing a value or binding.

Disadvantages

  • If users decided to log a computed property (e.g. console.log(state.countIncrease)) inside the create function, they would see an object with a value rather than the value directly. They won't understand exactly why Vue.computed returns this until they're introduced to bindings, but I don't see it as a significant problem because it won't stop them from getting their work done.

  • When doing something very strange, like trying to immediately use the setter on a computed property inside create, the abstraction of a binding would leak. However, if we think this is likely to actually happen, I believe it could be resolved by emitting a warning on the setter of bindings, since I believe that's always likely to be a mistake.

    const state = {
      count: 0,
      doubleCount: Vue.computed(
        () => state.count * 2,
        newValue => {
          state.count = newValue / 2
        }
      )
    }
    
    // This will not work, because `doubleCount` has not yet
    // been normalized to a reactive property on `state`.
    state.doubleCount = 2
    
    return state

4. Make context the same exact object as this in other options

Example

Vue.component('button-counter', {
  create(props, context) {
    return {
      map: context.$parent.map
    }
  },
  onCreated() {
    console.log(this.$parent.map)
  },
  template: '...'
})
Vue.component('username-input', {
  create(props, context) {
    return {
      focus() {
        context.$refs.input.focus()
      }
    }
  },
  onMounted() {
    console.log(this.$refs.input)
  },
  template: '...'
})

Advantages

  • Avoids a context switch (no pun intended 😄) when moving between options and the create function, because properties are accessed under the same name (e.g. this.$refs/context.$refs instead of this.$refs/context.refs).

  • When users/plugins add properties to the prototype or to this in onBeforeCreate, users can rely on those properties being available on the context object in create.

Disadvantages

  • Requires extra documentation to help people understand that this === context, and that properties are added/populated at different points in the lifecycle (e.g. props and state added in onCreated, $refs populated in onMounted, etc). We'll probably need a more detailed version of the lifecycle diagram with these details (or whatever the reality ends up being).

  • For TypeScript users, every plugin that adds properties to the prototype (e.g. $router, $vuex) would require extending the interface of the Vue instance. I think they probably want to do this already for render functions though, right? Don't they still have access to this?

5. Reconsider staying consistent with object-based syntax in the arguments for the function versions of the API?

This one is more of a question than an argument. We have a lot of inconsistencies between the object-based and function-based APIs. For example, when a computed property takes a setter, it's a second argument:

Vue.computed(
  () => {}, // getter
  () => {} // setter
)

rather than providing an object with get and set, like this:

Vue.computed({
  get() {},
  set() {}
})

It's my understanding that these changes were made for the performance benefits of monomorphism. However, they have some significant disadvantages from a human perspective:

  • They force users to learn two versions of every API, rather than being able to mostly copy/paste when refactoring between options and functions, creating more work and making them feel like a significant context switch.

  • They create code that's less explicit and less readable, since either intellisense or comments are necessary to provide more information on what these arguments actually do.

As a starting place, could we create some benchmarks from realistic use cases so we can see exactly how much extra performance we're getting from monomorphism? That could make it easier to judge the pros and cons.

Thoughts?

@vuejs/collaborators What does everyone think about these? They include some big changes, but I think they would vastly simplify the experience of learning and using Vue. I'm also very open to alternatives I may have missed!

@chrisvfritz chrisvfritz changed the title Difficulty documenting Vue 3 Difficulty documenting Vue 3 and avoiding a schism Jul 23, 2019
@johnleider
Copy link

johnleider commented Jul 23, 2019

Absolute fan of this.

@gustojs
Copy link
Member

gustojs commented Jul 23, 2019

Point 3) is fine as long as it's an addition and not a replacement.

If it is a replacement of the current API, it will be treated as a betrayal of the "we won't drop Object API" promise, regardless of the changes making sense or not. Users want to keep using computed and methods as separate options.

Even making it the default learning version will be taken badly.

Point 4) will touch $router and $vuex too and users won't like it either, but it's easier to swallow than 3).

@Akryum
Copy link
Member

Akryum commented Jul 23, 2019

I like it. 🐈

@gustojs
Copy link
Member

gustojs commented Jul 23, 2019

What I like about 3) is that it offers a good middle step for those who want to rewrite their components from Object API to Composition API.

I also like 1) a lot.

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 24, 2019

@gustojs The two core concerns I've seen about the new API are:

  • it's too complex and especially won't be friendly to beginners
  • it will cause a schism

So I think as long as we address both of those concerns, people will be happy in the long run.

But either way, I think some people will be upset in the short term - I don't think we can get around that. The difference is with a replacement, some people being upset is a temporary problem until we convince them it's for the best (which I'm confident we can do) and they realize it's actually a simplification of the current API, rather than more complex. Or worst case, they just get used to it.

If we don't replace data/computed/methods, then we get a choice between two permanent problems:

  • We can choose not to feature data/computed/methods prominently in the documentation, in which case the user reaction might be even worse than removing these options altogether, because it'll come across as actively dishonest because those options are obviously deprecated but we're pretending that's not the case. We'll also increase the pain for users who choose to stick with those options, because all the examples in the community will use the syntax we recommend in our documentation.

  • If we do feature data/computed/methods prominently in the documentation, then we not only fail in our objective to avoid a schism, but we also make the documentation problem worse because now there's not only data, computed, methods, and create, but also an object syntax and function syntax for create.

Does that make sense?

@NataliaTepluhina
Copy link
Member

NataliaTepluhina commented Jul 24, 2019

Thank you, Chris, I think it's a brilliant proposal. I really like all renaming ideas, it seems very logical to me.

While I can understand @gustojs concerns about point 3), I think it's a typical 'lesser evil' choice. Obviously, there will be users complaining about the change but in fact, part of the user base will complain about any possible change we make just because they used to use an old way of doing things. I agree with Chris here, making APIs consistent and preventing a schism worth to make a part of the community temporarily unhappy about the upcoming change.

@chrisvfritz regarding learning path: great examples! However, I understand it's just a draft for now but the last example there should be explained in a very detailed manner. I have some concerns about using asynchronous logic there as it might create a cognitive overload for newer developers.

@nekosaur
Copy link
Contributor

nekosaur commented Jul 24, 2019

Touching on @gustojs comment on point 4, I actually think users won't swallow having to type e.g. this.$context.$vuex every time very easily.

I do like the other points, especially the renaming. My only concern with regard to point 3 is that while it resembles both the function api and the object api, it's still a 3rd way of doing things, which will potentially increase the amount of context switching done.

@posva
Copy link
Member

posva commented Jul 24, 2019

I think the reasoning behind create naming is smart and I like it.

Prepending on is fine as long as we provide code transform and provide tools to use them (online, cli) and encourage its use so they appear high in search results.

create as an object for new Vue seems reasonable too

Avoids a significant context switch (no pun intended 😄) when moving between options and the create function (e.g. this.$context.refs -> context.refs instead of this.$refs -> context.refs).

I don't think it changes much between the two options so I would rather keep what we have not to break usage. I would rather see properties prepended with a $ in the context parameter so it becomes this.$refs -> context.$refs. But if this can be solved with a codemod too, then I rather not change it

Having the object with named get and set does make things more understandable for beginners imo. The overhead of having both options in the codebase should also be minimal

@Akryum
Copy link
Member

Akryum commented Jul 24, 2019

I do like the other points, especially the renaming. My only concern with regard to point 3 is that while it resembles both the function api and the object api, it's still a 3rd way of doing things, which will potentially increase the amount of context switching done.

@nekosaur There is actually only one way. It's just that you don't declare them with variables but put them directly in the bindings object.


Regarding this, we could document it as the context too, so no $context necessary and no context switching?

@posva
Copy link
Member

posva commented Jul 24, 2019

Regarding this, we could document it as the context too, so no $context necessary and no context switching?

But that could be very confusing because context do not have access to everything in create, eg: $refs aren't there yet

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 24, 2019

the last example there should be explained in a very detailed manner. I have some concerns about using asynchronous logic there as it might create a cognitive overload for newer developers.

@NataliaTepluhina I agree 100%! I think we can also think up some even simpler examples at that point in the docs, since we won't have to provide as much of an overview like in the blog post.

I actually think users won't swallow having to type e.g. this.$context.$vuex every time very easily.

I would rather see properties prepended with a $ in the context parameter so it becomes this.$refs -> context.$refs. But if this can be solved with a codemod too, then I rather not change it.

Regarding this, we could document it as the context too, so no $context necessary and no context switching?

I think if we consistently refer to this as the context throughout the documentation, then we'll have a good compromise. And I agree that staying consistent prepending with $ could help a lot too. I've seen a lot of people tripped up by that in the current API, because they keep forgetting.

But that could be very confusing because context do not have access to everything in create, eg: $refs aren't there yet.

If we offered a visual timeline of when everything is added to the context (e.g. props and state are added onCreated, $refs are added onMounted, etc) then we can resolve that confusion.

@nekosaur @posva @Akryum I'm happy to revise the proposal if you all agree that would be a good direction. What do you think?

@Akryum
Copy link
Member

Akryum commented Jul 24, 2019

👍

@posva
Copy link
Member

posva commented Jul 24, 2019

Can we properly type that by providing a this parameter to the different hooks like create, onCreated and others? eg: right now $el is always an element but it's undefined in beforeCreate.

I really don't like having a $context, I do believe people will find that to be very verbose. Having this seems to be a nice middle ground if people know what they have access to. It does bring a problem if plugins inject things in created that are accessible through Vue.prototype.$something and someone try to access it in a create hook. I hope this is marginal enough and not a good practice in general to not be a problem

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 24, 2019

Can we properly type that by providing a this parameter to the different hooks like create, onCreated and others? eg: right now $el is always an element but it's undefined in beforeCreate.

I think we probably can, at least as intellisense provided by Vetur - but many users either don't code in environments where they can access intellisense or they don't even look at the intellisense when it's there, so I don't think we can rely on tooling alone to solve any problems.

I really don't like having a $context, I do believe people will find that to be very verbose. Having this seems to be a nice middle ground if people know what they have access to.

@posva I've come to agree with you about $context, but I think documentation will be a better solution than adding this to create. The problem is that create is very likely to contain nested function contexts, and we've all seen less advanced users have trouble with function bindings.

Either way, it's a documentation problem: we help users understand that this === context, or we can help them understand function bindings. I think teaching this === context will be much easier. 😄

@posva
Copy link
Member

posva commented Jul 24, 2019

I don't think we can rely on tooling alone to solve any problems.

Absolutely. The idea just crossed my mind and I thought I would share

Either way, it's a documentation problem: we help users understand that this === context, or we can help them understand function bindings. I think teaching this === context will be much easier.

I'm confused 😆 , I thought by the previous paragraph, that you would rather keep an extra parameter context containing the information, so that people don't have issues with function binding (I understood binding the function to this) but that last one makes me think you rather bind the function to its context set to the context object:

create (props, context) {
  this === context
}

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 24, 2019

@posva I can understand why that would be confusing - sorry about that! 😅 I'm not suggesting this be available in create. Instead, I'd prefer they learn that the context argument in create is the same object (but earlier in the lifecycle, so not as many properties) as this in other options like onCreated and watch. Does that make more sense?

(I've also just updated proposal 4 based on this feedback - let me know if it looks good to you.)

@posva
Copy link
Member

posva commented Jul 24, 2019

It's clear now!

There is something that doesn't feel completely right to me, not completely sure of what it is though.
I feel like advanced users will prefer having the variables without the $ because it's unnecessary prefixing.
Is it that much of a big deal to have the $ in front when accessing through this when it comes to context switching? The more I think about it the more it makes sense to keep the same variable name when it comes to learning but if we don't write many accesses to this anymore because we write everything in create, then it may be better to have $context to prevent collisions

I'm sorry I'm going back and forth 😓

@Akryum
Copy link
Member

Akryum commented Jul 24, 2019

Shouldn't just put this as the context in create too and remove the arg? 😅

@posva
Copy link
Member

posva commented Jul 24, 2019

I think it's because we want to avoid the common pitfall of

create() {
  function onClick() {
    this // not the `this` you'd expect!
  }
}

(taken from https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#the-setup-function)

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 24, 2019

I feel like advanced users will prefer having the variables without the $ because it's unnecessary prefixing.

@posva The more I think about it, the more I think the $ prefix is good even for advanced users, because it not only provides consistency, but also important information that this API is from the framework (or a plugin), rather than defined by the user themselves. Plus, I've never seen advanced users complain about having to type the $ in $refs, $router, etc, so even if it's a pain point for some, it would be an extremely small one.

@posva
Copy link
Member

posva commented Jul 24, 2019

but also important information that this API is from the framework (or a plugin), rather than defined by the user themselves

But if the user cannot inject anything into the parameter, they already know it comes from the API


I do think it's the best option we got yet as well

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 24, 2019

But if the user cannot inject anything into the parameter, they already know it comes from the API.

@posva They technically could in onBeforeCreate or by adding properties to the prototype, if we make context the exact same object as this in other options, which I think we should do to guarantee consistency. Otherwise, our pitch of this === context won't really be the truth.

That would also solve the other problem you brought up earlier:

If plugins inject things in created that are accessible through Vue.prototype.$something and someone try to access it in a create hook. I hope this is marginal enough and not a good practice in general to not be a problem

Allowing other properties to be added to context would create slightly more work with TypeScript, but I'd rather extend the interface for context once per plugin, than have to use hacks like context.$parent.$route every time I want to access these properties.

@shentao
Copy link
Member

shentao commented Jul 24, 2019

  1. I like the change. create just makes a bit more sense, especially in the context of lifecycle hooks that help position it in the timeline.
  2. Same here. Should help keep things cleaner.
  3. I have very mixed feelings about this. Especially in examples like this:
const state = {
  count: props.initialCount,
  countIncrease: Vue.computed(
    () => state.count - props.initialCount
  ),
  incrementCount() {
    state.count++
}

where I feel like putting computed (derived state) and methods (not state at all) into the same object makes it a bit confusing to be honest. And I do feel like this would be recieved poorly since we promised to keep the object API and this sadly doesn’t feel like it at all, since it removes the keywords data, computed, methods that I believe are what makes the Vue components very approachable for beginners.

Then having create as an object where the name feels a bit more actionable (like a function), just makes it seem a bit more out of place. I think it should stay a function only API. I mean, it could feel like we just want to trick them by renaming setup to create and saying Hey, it can be an object now, we aren’t technically breaking the promise. But it’s not the Object syntax they have been fighting for.

It's easier for users to remember which options add properties to the instance, since there would only be one: create.

There are also plugins that add properties to the instance. And plugins that require you to configure them using special properties like vuelidate does.

Also, something you haven’t mentioned is that with that change, the usage of this becomes less popular, which might not be a bad thing, but might feel like we’re taking something from them that they spent a lot of time learning and understanding.

Will have a bit more on this later once I get back home.

  1. I like it!
  2. I like it!

@yyx990803
Copy link
Member

yyx990803 commented Jul 24, 2019

First, some context - Chris and I have discussed part of this proposal in a call previously and I am not really a fan of this for the following reasons:

  1. Renaming is not cost-free.

    It's not just about a codemod that upgrades existing code.

    • It makes almost all existing educational content "out of date", because any non trivial component will likely need lifecycle hooks. This can be very annoying for content creators since they will either remake their content or patch their existing content. This will discourage content creators in the Vue ecosystem.

    • It creates edge cases for tooling. All code analysis tools now need to support both the prefixed and non-prefixed versions of lifecycle hooks. And they may need to have special handling depending on the version of Vue the user is using (e.g. an older version of Vue does not support onXXX).

    • Can cause confusion when users search Google/StackOverflow and get answers using non-on-prefixed code. (As we know few users actually pay attention to the changelog)

    This is all magnified by the fact that lifecycle hooks are extremely common in user code, unlike some of the other breaking changes we currently have proposed.

    Given that renaming lifecycle hooks is not cost-free, I don't think the benefits actually justify the proposed change. In fact, I believe the statement that "we are not touching 2.x options" is going to help much more in getting the function API shipped.

  2. I am against the idea of replacing data/computed/methods.

    People will get mad about it (again) because we promised we are not touching their 2.x API. There's no way around this at this point.

  3. I am against the idea of allowing create (or setup) to be an object.

    • The object-format cannot be used for reusable components, similar to how data works. In addition, the moment the user wants to use a object-incompatible API (e.g. watch, inject, lifecycle hooks) or start using composition functions, they'd have to refactor into the function format. As a result, the object format will rarely be usable in real projects.

    • It can be somewhat seen as the object-based API but without the separation of data/computed/methods - where the separation is what most people liked about the object-syntax (keeping things organized). With the single create object you can organize code neither by options nor by function composition. So it is technically worse than both the 2.x API AND worse than the full function API. It may seem like a "simplification" at first, but users will soon realize they can't really use it for anything non-trivial.

    • The alternative object format creates more hoops to jump through for tooling (type inference, IDE support).

As I see it - the general idea behind this proposal is about providing a "bridging format" with the sole purpose of transitioning users from the 2.x API to the function-based API. However by introducing this "in between" API (object format create), we end up with:

  • Yet another way of doing things (now we have three: 2.x API, object format create, function format of create);

  • Where this way of doing things is something that they will eventually have to refactor out of as soon as their app gets non-trivial;

  • Its value diminishes and eventually becomes useless in the long run, but cannot be easily removed because there will be tutorials and content written using it, and removing it is a breaking change (which will piss people off).

Overall, I disagree with this proposal's direction. The fact is that the new API indeed is a different way of writing components. Trying to make it "feel" like the old way doesn't affect the fundamental difference underneath - instead, it only waters down and obscures the benefits of the new mental model.


My take on the adoption strategy:

  1. We should not be shipping mid-way format of the new API just so to get users on board. It may get a few more to try it now but it creates its own problems that cannot be easily undone in the long run. Either use 2.x API or use the full function based API. Quoting what Chris said:

    But either way, I think some people will be upset in the short term - I don't think we can get around that. The difference is with a replacement, some people being upset is a temporary problem until we convince them it's for the best (which I'm confident we can do) and they realize it's actually a simplification of the current API, rather than more complex. Or worst case, they just get used to it.

  2. Position the new API as "advanced" (for now) and leave the main documentation untouched (using 2.x API). The new API is only introduced in its own dedicated sections. React is taking a similar strategy when it comes to hooks - its documentation is still fully class-based and hooks are only discussed in dedicated sections.

  3. Give it time. I think the initial controversy made things look worse than it is, it may feel like a "schism" when people feel threatened about their options being taken away. But when the two APIs can just co-exist I don't think it's going to be "schism", but more like picking different options when faced with different scenarios. TypeScript users today happily uses class-based components while the rest are not bothered by it at all. Similarly, users who are aware of the problems the new API better deals with will be using it, while those who don't need it will not. It's fine. I think I was rushing it a bit when I thought we can fully replace 2.x API with the function-based one, but now I think it's OK for them to co-exist for a relatively long period of time. And we need that time to observe real world usage and adoption to decide whether this should be eventually pushed as the idiomatic way to do things.

@LinusBorg
Copy link
Member

LinusBorg commented Jul 25, 2019

I waited a bit before commenting because I wanted to let it sink in a bit.

Right now I'm pretty much in Evan's boat.

The killer for me is that we simply can not, in any way touch the 2.x API for now. People will lose their shit otherwise. And introducing a 3rd way of doing things would be equally bad.

So while I think there's a lot of great ideas in here, our hands are tied to a certain extent.

So I'm fine with renaming it to create (as others mentioned I think we already discussed this elsewhere), but it should keep working like setup is designed to work in it's RFC (and the proposed Ammendment @yyx990803 what's out status on that?)

@gustojs
Copy link
Member

gustojs commented Jul 25, 2019

Imho we can touch it, cause we're touching it anyways with other RFCs. Just not to this point.

@shentao
Copy link
Member

shentao commented Jul 25, 2019

IMO we should try to just add the API without really touching the Object syntax at all and have it inside the "advanced components" section or create an entirely new docs section (that talks about component composition, code reuse, what to use instead of mixins etc. Not "alternative-component-format").

And I think this would make sense because beginners probably won’t see the benefit in learning it when they are just starting, only when they get more experienced and start facing architecture-related problems it will become relevant. Then we can probably measure if the community is actually embracing the function API.

The fact it can fully replace the Object Syntax is just a bonus feature we don’t really have to mention. Unless it’s an advanced cookbook thing or a really in-depth composition chapter. If someone figures that out – good for them!
We can just leave it to the community to figure out the best patterns, with our slight push.
Think Inception. 😅

@Akryum
Copy link
Member

Akryum commented Jul 25, 2019

@chrisvfritz Maybe we can postpone this plan for Vue 4? 😅

@sdras
Copy link
Member

sdras commented Jul 28, 2019

I'm late to this party, I know, but I'm pretty relieved to see @yyx990803 's response. I quite like 1 and 2, though, if it's overly difficult to change, probably not necessary.

I'd be pretty concerned about 3, re:data, computed and watchers. I've seen a few people talk about "beginners" in this thread, but it's not just about people just learning- one of the things that appeals most about Vue is the immediate legibility about this distinction. Beginner to advanced, the ability to jump into a codebase and understand what's happening is not to be overlooked. Ironically, that's also why I'm in strong support of 5. The legibility of the distinction there is meaningful and can help maintainers be more efficient.

I love what Evan and @shentao proposes about having it be an advanced API- honestly I think this might be the path where we get most adoption. Consider the psychology of it: "your whole job changed and you have to write in a syntax that we haven't made available to you yet" vs "we still support the old API, but this is for advanced users". The latter is more exciting, no? The former introduces anxiety.

In terms of teaching, I like the idea of talking about what benefits this new API brings. I'm still extremely interested in composition, and I think others will be too. IMO, we should be focusing on capabilities over syntax. Capabilities excite people, that's likely how they got into Vue to begin with.

Thank you so much for putting this together, @chrisvfritz! With every discussion I think we all understand one another better. The concepts here are well-described and intriguing.

@chrisvfritz
Copy link
Member Author

chrisvfritz commented Jul 30, 2019

@shentao

I feel like putting computed (derived state) and methods (not state at all) into the same object makes it a bit confusing to be honest.

I got that feedback in some very brief user testing too. I'm very open to a different name for this variable, like scope, self, etc.

I do feel like this would be received poorly since we promised to keep the object API and this sadly doesn’t feel like it at all, since it removes the keywords data, computed, methods that I believe are what makes the Vue components very approachable for beginners.

This way actually has fewer concepts to learn than the current API and tested very well with beginners (haven't tested it with anyone who's never used Vue yet). One of the main reasons I like it is how easy it is to teach. 😄

@yyx990803

Renaming is not cost-free.

I think API changes like this are actually much cheaper than API additions. With API additions, people won't be able to use them everywhere, which means sometimes they'll follow a tutorial for a newer version of Vue and the code will fail - no warnings, nothing. With API changes, we can emit warnings when people try to do things the old way, directing them to docs about why the change was made and what they should do instead.

It makes almost all existing educational content "out of date", because any non trivial component will likely need lifecycle hooks.

Even more fundamental is state, which we decided to rename data to, but I'm guessing you've now changed your mind on that?

This can be very annoying for content creators since they will either remake their content or patch their existing content. This will discourage content creators in the Vue ecosystem.

I think this is a good concern to bring up, but the primary reason I opened this issue is to make my life easier as a content creator in the Vue ecosystem. 😄 Over the past many weeks, the current proposed API has been such a pain to help people learn that it's actually much easier to make little tweaks to my existing content (and we could build a codemod to help with this specifically) than try to teach the functions API without it feeling like I'm introducing a completely different framework. It changes the learning curve from a sheer cliff to a big hill.

It creates edge cases for tooling. All code analysis tools now need to support both the prefixed and non-prefixed versions of lifecycle hooks.

Seems like a trivial amount of work in exchange for making it possible to document Vue 3 in a way that makes it feel simple and consistent.

And they may need to have special handling depending on the version of Vue the user is using (e.g. an older version of Vue does not support onXXX).

Won't some tooling already require special handling depending on the version of Vue? We already have different options available in different versions, even within 2.x.

Can cause confusion when users search Google/StackOverflow and get answers using non-on-prefixed code. (As we know few users actually pay attention to the changelog)

Wouldn't that be a reason to avoid any change/addition in the API, even non-breaking?

I believe the statement that "we are not touching 2.x options" is going to help much more in getting the function API shipped.

From everything I've seen, the core concerns these users have are:

  • A) They'll have to completely rewrite their existing apps when migrating. (Or even if a codemod does it for them, they'll have to spend a lot of time and energy relearning concepts.)
  • B) Vue is moving to a more complex API that would destroy its approachability and gentle learning curve.
  • C) A schism will be created between people using the 2.x-style API and the functions API, making it necessary for users to be able to understand and translate between both syntaxes, even if they're only using one.
  • D) The functions API feels less organized, because they've grown to appreciate the separation of data, computed, and methods as an organizational benefit, rather than limitation.

I think concerns A, B, and C are completely legitimate and account for most of why so many people were confused and worried about the RFC. My proposal actually addresses all of these:

  • A) From user testing, I've seen that even an existing user who's a relative beginner and didn't enough sleep last night can wrap their heads around the change with create in a few minutes, to the extent that they're able to write new code using this syntax without a reference. So conceptually, it's not a huge change.
  • B) This makes Vue's entire API more unified and easier to teach, maintaining its approachability and gentle learning curve.
  • C) Code using the functions API will no longer be completely unrecognizable to beginners. Plus, people can get most of the organizational benefit of setup just by consolidating property creation in create, so there won't be as much reason to go 100% functions.

This comes at the expense of people in group D - which is the main group I think you have in mind. From what I've seen, the people in group D are a quickly shrinking group - they're becoming convinced. More and more people are writing blog posts to help explain why it doesn't make sense to separate these and they'll come around, just as the community has come around on SFCs that combine HTML, JS, and CSS.

By focusing on D (the temporary misunderstanding), rather than A, B, and C (the legitimate concerns), I think we're trading some short-term pain before Vue 3 comes out with long-term pain for as long as Vue 3 is maintained.

Plus, it might seem like you're doing group A a favor by not touching the 2.x options API, but I think you're just postponing the problem for advanced users. If many people choose to use the functions API exclusively - as many people on the team and prominent members of the community have already said they'd probably do - they'll eventually want to migrate to the functions API. But instead of that feeling like a natural transition, since they're already using create to create reactive properties on the instance, it'll be a radical shift.

People will get mad about it (again) because we promised we are not touching their 2.x API. There's no way around this at this point.

From what I saw, we promised that we're not deprecating/removing the options-based API, which this proposal does not do. It just consolidates 3 options into 1, since they're all logically connected by creating properties on the instances. It's a change it'd make sense to make even without the rest of the functions API. It also reduces the number of concepts new users have to learn.

The object-format cannot be used for reusable components, similar to how data works.

This is a problem that already exists if we keep data, so it doesn't actually create any new problems, right? Plus, the Vue community is already familiar with this distinction from data and it's been proven that with the right guidance, new users can wrap their heads around it. I personally feel like the syntactic simplicity it affords for those learning Vue for the first time or using it for very simple use cases is worth it, but I could probably be convinced otherwise.

In addition, the moment the user wants to use a object-incompatible API (e.g. watch, inject, lifecycle hooks) or start using composition functions, they'd have to refactor into the function format. As a result, the object format will rarely be usable in real projects.

No, it looks like this is a misunderstanding. In the rough outline of a learning path, you'll see that I do not recommend deprecating/removing any options besides data, computed, and methods - watch, inject, lifecycle functions, etc would all still be available as options, so there would be no object-incompatible APIs. There would be both options and functions versions of both.

It can be somewhat seen as the object-based API but without the separation of data/computed/methods - where the separation is what most people liked about the object-syntax (keeping things organized).

Again, I think this is prioritizing the temporary concerns of group D over the legitimate and permanent concerns of B and C.

With the single create object you can organize code neither by options nor by function composition. So it is technically worse than both the 2.x API AND worse than the full function API. It may seem like a "simplification" at first, but users will soon realize they can't really use it for anything non-trivial.

I think this goes back to the misunderstanding about watch, inject, lifecycle functions, etc remaining options in the object-based API. Since they would remain options, you'd still have code organized by options. The difference is this code would have a lot of the benefit of the full functions API, because:

  • properties created for the instance would be together under a single option, so you can skim them at a glance
  • these properties could be organized by feature within the object and even namespaced in a nested object

The alternative object format creates more hoops to jump through for tooling (type inference, IDE support).

Couldn't we adapt what we already have for data? If your alternative is to keep data, I don't see how this creates a new problem.

As I see it - the general idea behind this proposal is about providing a "bridging format" with the sole purpose of transitioning users from the 2.x API to the function-based API.

No, not at all - I think I didn't explain this very well. I'm not trying to get every user on the function-based API (apart from computed). At the core, I'm trying to solve a fundamental documentation problem: I can't find any way to document Vue 3's current proposed API that feels simple and consistent, and offers a relatively gentle learning curve throughout.

Yet another way of doing things (now we have three: 2.x API, object format create, function format of create)

Again, I think you may have misunderstood. This is creating fewer ways of doing things in Vue 3. Rather than a 2.x-style API and a radically different functions API, I want a single, more unified API where it's possible to refactor options to functions gradually, in a way that doesn't feel like a radical shift or create awkward transitional phases where the component is actually less organized than before.

Where this way of doing things is something that they will eventually have to refactor out of as soon as their app gets non-trivial

The difference in what I'm proposing is that when users get to non-trivial apps, they'll refactor to something nearly identical (create is a function rather than an object), rather than having to forget 90% of what they learned about the JavaScript part of Vue in the Essentials.

Its value diminishes and eventually becomes useless in the long run, but cannot be easily removed because there will be tutorials and content written using it, and removing it is a breaking change (which will piss people off).

I think this is probably coming from the misunderstanding about deprecating other options. It will serve the same purpose in the ecosystem as the object syntax of data currently.

The fact is that the new API indeed is a different way of writing components. Trying to make it "feel" like the old way doesn't affect the fundamental difference underneath - instead, it only waters down and obscures the benefits of the new mental model.

I actually feel like the current strategy waters down and obscures the benefits. Instead of the function versions of APIs being mostly what users learned in previous guides, except being a function rather than an option, users will have to learn all at once:

  • what the equivalent of each option is, which may be:
    • a function with the same name (e.g. computed, watch)
    • new concepts (e.g. data -> reactive/binding)
    • just JS (e.g. methods are no longer a concept)
  • what arguments the functions take (they're often different from the options in the object-based syntax)
  • what each function returns and what (if anything) has to be done with it
  • more details about Vue's reactivity system, including reactive objects, bindings, and dependency tracking

If we go with what you suggest, where users will "either use the 2.x API or use the full function based API" - then we're confronted with that inevitable, huge cliff in the learning curve.

Position the new API as "advanced" (for now) and leave the main documentation untouched (using 2.x API). The new API is only introduced in its own dedicated sections.

That's mostly what I'm proposing, except with a few API changes to allow people to reuse 90% of what they learned in the Essentials when they get to more advanced apps (just as they can now). As it is, the functions API is so radically different that I might even advise users who want to use it right from the start to skip the Essentials and learn from a 3rd-party introduction instead, since outside the template syntax, everything else will be radically different. That's not a great situation. 😔

React is taking a similar strategy when it comes to hooks - its documentation is still fully class-based and hooks are only discussed in dedicated sections.

Their strategy is actually to start with functions, mention classes briefly, then continue with functions, suddenly switch to classes, continue to use classes throughout their advanced guides, then finally after the API reference go back to functions and introduce hooks, casually mentioning that you might want to start migrating the API you just learned to this completely different API because its superior.

That is not a strategy I want to emulate. 😅

Give it time. I think the initial controversy made things look worse than it is

Nothing in this proposal is actually meant to address the controversy in the community - it's addressing the real consequences this API will have in our ecosystem and my own struggle to document it. Even some people on the core team have admitted to me that they don't really understand many aspects of the new API. It can use all the help it can get to help us simplify it.

it may feel like a "schism" when people feel threatened about their options being taken away.

This is something that will get worse with time, not better. There won't be a "feeling" of a schism - it will exist. There'll be a large group of (generally less advanced) users exclusively using the 2.x-style API and a large group of more advanced users exclusively using the functions API. The less advanced users already complain about seeing a lot of examples that assume an SFC context. Now imagine how bad it will be when the <script> block is entirely unrecognizable to them.

But when the two APIs can just co-exist I don't think it's going to be "schism", but more like picking different options when faced with different scenarios.

I don't think many teams will choose to use the API this way (e.g. use the 2.x-style syntax for most components, then the functions API for components where you really need to manage complexity), because:

  • If you have the two syntaxes living side-by-side in the same codebase, the API surface area that devs have to memorize suddenly doubles.
  • If you start components with the 2.x-style API, that initial migration to a create function will be very painful since there are so many differences. We could publish a code transform to help with that, but there's no guarantee it would transform to the kind of code they want to write. So I think most teams in this group would eventually just choose to start every component with the functions API.
  • When users choose to partially migrate a component, it'll be organized even worse than before, with some data, computed, and methods being split between the options and the create function, possibly increasing complexity rather than reducing it.

TypeScript users today happily uses class-based components while the rest are not bothered by it at all.

TypeScript users are bothered by it because they're in the clear minority and most of the ecosystem doesn't seem to cater to them. As long as you're in the overwhelming majority, you're happy. With the functions API, we're talking about a split that may be closer to the middle, because it could include all users that build potentially large apps. This group of advanced users will also be over-represented in community resources, because they're mostly created by advanced users.

With a split like that, we're at risk of not just a minority of more advanced users being unhappy (though they're advanced, so they can cope with it), but everyone being unhappy because a lot of public examples will take considerable effort to translate to the syntax they're using.

I think I was rushing it a bit when I thought we can fully replace 2.x API with the function-based one, but now I think it's OK for them to co-exist for a relatively long period of time. And we need that time to observe real world usage and adoption to decide whether this should be eventually pushed as the idiomatic way to do things.

I agree with this 100%. I'm just trying to minimize the problems we have during that period of coexistence.

@LinusBorg

The killer for me is that we simply can not, in any way touch the 2.x API for now. People will lose their shit otherwise. And introducing a 3rd way of doing things would be equally bad.

I think I address this in my response to Evan above, where I break down the underlying community concerns into four categories: A, B, C, and D.

@shentao

IMO we should try to just add the API without really touching the Object syntax at all and have it inside the "advanced components" section or create an entirely new docs section (that talks about component composition, code reuse, what to use instead of mixins etc. Not "alternative-component-format").

If we expect people to use 100% one or the other, as a lot of people on the team have already said they would and as Evan is proposing we suggest to users, then I think it would be dishonest to claim this is meant to be used like mixins or scoped slots - it would change the way every component in your app is written.

@Akryum

Maybe we can postpone this plan for Vue 4? 😅

That would defeat the point, unfortunately. 😄 I'm proposing this to solve problems that we have while working on Vue 3 and as soon as it's released, so waiting for Vue 4 would be too late.

@sdras

one of the things that appeals most about Vue is the immediate legibility about this distinction. Beginner to advanced, the ability to jump into a codebase and understand what's happening is not to be overlooked.

I completely agree! The problem I find with the current proposed API, is beginners would simply not be able to jump into advanced codebases, because they would be completely unrecognizable (or at the very least, some components would be). Point 3 is essential (and 4 very helpful) in bridging that gap, because otherwise people are looking at components that may have zero options in common with what they're used to. With 3, they'd already have the basics covered, being able to add any kind of property to the instance.

I love what Evan and @shentao proposes about having it be an advanced API- honestly I think this might be the path where we get most adoption.

I agree - that's what I'm proposing. 🙂 The only piece of the functions API people would see in the Essentials is Vue.computed.

Consider the psychology of it: "your whole job changed and you have to write in a syntax that we haven't made available to you yet" vs "we still support the old API, but this is for advanced users". The latter is more exciting, no? The former introduces anxiety.

Again, I completely agree! That's why the only major change I'm proposing is the 3 different options that create properties on the instance being consolidated to 1 option (create) and 1 function (Vue.computed), where they'd reference a variable rather than this to access other properties. In some very brief user testing with existing users, everyone was able to write hypothetical code in this style directly after being introduced to it with no confusion except for the name of state (which Damian also brought up) - and I agree it might be better renamed to scope, self, or something else. So I've confirmed it definitely won't feel like their whole job is changing.

@yyx990803
Copy link
Member

yyx990803 commented Jul 30, 2019

@chrisvfritz

A) They'll have to completely rewrite their existing apps when migrating. (Or even if a codemod does it for them, they'll have to spend a lot of time and energy relearning concepts.)

No one will have to rewrite their code. This proposal does not change anything about that.

B) Vue is moving to a more complex API that would destroy its approachability and gentle learning curve.

  1. The strategy we are adopting is adding a new set of API, not moving to it.
  2. The gentle learning curve is still there given that absolute beginners will still start with the object-based syntax.
  3. The function-based API is not that more complex than the object-based API. It's not going to "destroy" anything. I trust in the users' ability to learn.

C) A schism will be created between people using the 2.x-style API and the functions API, making it necessary for users to be able to understand and translate between both syntaxes, even if they're only using one.

I don't see how this proposal helps addressing this problem. As I said, it's a half-baked addition that is neither 2.x nor 3.0. And now you have three ways of doing things. How is that going to help?

D) The functions API feels less organized, because they've grown to appreciate the separation of data, computed, and methods as an organizational benefit, rather than limitation.

The "feel" is based on the first impression of a simple example. Users who actually are trying to use the API will read the docs (or other educational materials) where "organizing code by feature" will be a very prominent central theme. At this point we are no longer concerned about "selling it", as most users aware of the API know that it is going to land anyway.


Final words: I'm OK with (4) and (5) in the original proposal. (1)~(3) are not going happen. I really appreciate your time spent on this proposal, but at this point, I honestly do not have the time to fully convince you as I need to focus on getting it shipped.

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

No branches or pull requests