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

Use standard JS classes instead of custom syntax? #2371

Closed
nullptr128 opened this issue Feb 19, 2016 · 33 comments
Closed

Use standard JS classes instead of custom syntax? #2371

nullptr128 opened this issue Feb 19, 2016 · 33 comments

Comments

@nullptr128
Copy link

Hello!
First of all, I'd want to thank you for providing this great library.

After creating few projects with Vue I've came across an idea - could it be possible to allow using ES6 classes in place of standard Vue component syntax?

I think we already have a nice ES6 standard for classes so using these would be ideal because code would become less framework-dependent, more readable (esp. for people who never were using Vue), I believe that also less error-prone and more versalite.

It could look like this way:
http://pastebin.com/mqy2r2XW

This is how it worked in for example KnockoutJS. They werent making their own way of declaring component, properties, methods as JS nested objects, they've used standaard javascript class syntax instead.

I've managed to make it this way using some hacky module (that translates a class into structure readable by Vue) but it'd be great to have this as build-in functionality.

My proposal is to tune up component builder in Vue - for now it only accepts JS objects with Vue declarations like props, methods etc. For backwards compatibility it could work this way:

a) if object is passed to Vue.component then it works using original behaviour

b) if function is passed, Vue.component treats it as class constructor and uses new syntax.

It could be nice, modular and simplier, I'd love to use:

import SomeComponentClass from 'components/some-component.js';
Vue.component( 'some-component' , SomeComponentClass );

Or just return it from .vue file via vue-lodaer/vueify.

@posva
Copy link
Member

posva commented Feb 19, 2016

Object literals are easier and more clear than classes that are applying some black magic.
However a webpack transform could be a great way to handle this

@smolinari
Copy link

I know for a fact, if Vue would be programmed as @nullptr128 suggests, it would win a lot more backend developer hearts, including mine. Though, I love Vue anyway. LOL!

Scott

@yyx990803
Copy link
Member

My opinion is that ES6 classes offer no practical advantages over plain object definitions except for syntax preferences.

But if you really like classes, you can use https://github.com/vuejs/vue-class-component

@smolinari
Copy link

I will bet you a beer ES6 classes will become more the norm as time goes on. 😉

Scott

@yyx990803
Copy link
Member

@smolinari that's why there are tools that allow you to write components as a class, but I don't think I'll ever change Vue's default API to use classes.

@phobos2077
Copy link

I agree that some kind of solution for ES6 should be added to the base (or at least as an official plugin). It is silly to say that the feature that is already in EcmaScript standard (ES6 class syntax) is "bad" and ignore it. I am a more of a backend developer but we have to use JS for our projects so I'm looking for a good MVVM library that will be easy to learn for any backend developer. OOP is much easier to understand for backend dev than more exotic (for us) functional/prototype patterns.
Previously I used KnockoutJS for a short time and stumbled upon VueJS recently. It seems almost perfect, but the way Vue objects are created bugs me. It's not a show stopper, but a disadvantage in my opinion (in Knockout you could create objects any way you want, successfully using ES6 classes as view models).

@znck
Copy link
Member

znck commented Mar 3, 2017

Vue has official plugin for ES6 class components: https://github.com/vuejs/vue-class-component

@smolinari
Copy link

I've learned a bit more about JavaScript and I must say, concerning my comments above, I see now I was a bit mistaken. For those of you who don't know the power of JS's prototype based object model, you should really take the time to learn it. Trying to fit classical OOP over JS' prototype system is like putting a stocking over your head for a date thinking you might look better. Ok. That analogy might not be perfect, but hopefully you get the idea. Learn the true power of JS and you won't need the stocking. 😄

Scott

@nullptr128
Copy link
Author

This however does not address original issue.

Having custom object literal syntax is less flexible than using inbuild class system. If Vue would take function parameter and treat it like class contructor, you could either use prototype based definitions or ES6 class syntax (which is more like syntatic sugar).

Class based definitions are easier to understand, easier to follow and you can even use modules that were originally designed to make another task, extend them and add UI features.

It also could be compataible with dependency injection libraries like Inversify.

Also it makes programming components with TypeScript easier because you can get autocomplete for fields, functions, checking parameters, return types etc.

And also my personal opinion is that class based design is more readable than defining data in literal structures... I just dont feel it is neccessary to recreate the same class structure that original JS has using literals... We have "data" which are instance fields, "computed" which are in reality getters, "methods" which are methods. I feel it is unnecessary to recreate ES6 and OOP basics that everyone understands in form of literals dedicated only to one library.

I am considering moving to Angular just because Vue does not have good TypeScript support, and class based components would help it a lot.

@Akryum
Copy link
Member

Akryum commented Mar 3, 2017

Computed properties are not just getters, they are cached and smartly updated if a data dependency change.

@LinusBorg
Copy link
Member

LinusBorg commented Mar 3, 2017

We have "data" which are instance fields, "computed" which are in reality getters, "methods" which are methods.

  • We also have watch:, which has no equivalent in ES6 classes
  • We also have props:, which has no equivalent in ES6 classes
  • we have provide:/inject:, which have no equivalent in ES6 classes
  • we have model: , which has no equivalent in ES6 classes
  • And with object literals, we have an easy and readable way to add options for 3rd party extensions to a component, like firebase of the vuefire extension.

All of those features are not easy to integrate into an ES6 class - hence the use of @decorators in vue-class-component to provide the stuff that is missing - which makes it less readable to me, personally, and also is not supported in browsers (it's not even standard yet).

I fail to see how ES6 classes are "more flexible" in any way.

The only point I can concede is that the objet literal approach makes support of type systems like typescript and flow harder (but I'm sure that will change) - personally, I don't see the use of those in components specifically - complicated business logic worth typing resides in other modules for the store, api etc.

@yyx990803
Copy link
Member

yyx990803 commented Mar 3, 2017

Basically what @LinusBorg said. I know people coming from a class-heavy language will want to use classes, but JavaScript is not Java or C# or whatever backend language you happen to be used to, and ES2015 class as it is right now is simply not expressive enough unless you resort to not-even-standard features like decorators and class property initializers.

You may want to say it's ok, everyone is using build tools now, stage-2 is fine too... well no it's not. While using a class-based API by default may make it more "friendly" to devs used to classes, it also makes it more hostile to a large group of users who use Vue without build tools or transpilers. When you are advocating your preference, you might be missing some nuance we have to take into account as a framework.

This is why we offer the object-based API as the baseline and the class-based API as an opt-in. This allows us to cater to both groups of users.

@Schenn
Copy link

Schenn commented Mar 30, 2017

We also have watch:, which has no equivalent in ES6 classes

Watch is a setter.

props: is a getter.

Inject / Provide is Object.assign

Model is a selector to a node which is changed on Set / Get + event listeners

ES6 - Object.defineProperties solves all of these and can be used in the constructor of a class, not to mention other means, to provide this identical functionality to any class....

@LinusBorg
Copy link
Member

LinusBorg commented Mar 31, 2017

Watch is a setter.

Doesn't make semantic sense to me because, while they do receive arguments like setters do, watchers often don't change anything about the instances local state, so what do they "set", exactly?

props: is a getter

So would be computed properties. How to distinguish? Also, we have computed props that can also set state - wouldn't we rather use setters for this instead of watchers?

Inject / Provide is Object.assign

So here we are officially out of class API alternatives to add this feature to the class directly. We will have to do that in the constructor ourselves somehow - like many other options.

ES6 - Object.defineProperties solves all of these and can be used in the constructor of a class

Sure - We just think that this leads to a worse dev experience than the current API - some features you can set on the class directly (methods, data, computed props), while about 60% (watchers, props, inject/provide, plus any plugin APIs that use th options object as a common interface for the developer) you have to manually assign in a constructor function. This essentially leads to APIs like we see vue-class-component, which has to use decorators to work around this.

See, we don't say that Vue's options/features are not somehow configurable with a class interface - we just don't see any advantages and more so a few disadvantages in dev experience when doing so.

ES6 classes are nothing but syntactic sugar for concepts we can use internally with or without them, they offer zero new actual functionality. So when that syntactic sugar isn't really sweetneing the goods in our use case, why should we use it?

@smolinari
Copy link

smolinari commented Mar 31, 2017

I found this article very enlightening. That coupled with the knowledge of the power of the prototypal inheritence in JS and functional programming (the two pillars Eric mentions) and it all made much better sense for me as to why "class" is currently sort of a mistake in JavaScript. That is not to say, class can't be made better either. But for now I feel, as an old OOP programmer, I really shouldn't be fighting for my beloved class instances in JavaScript. 😄 It's actually more like added and unnecessary baggage.

Scott

@LinusBorg
Copy link
Member

LinusBorg commented Mar 31, 2017

Personally I think classes can be useful in many situationsin Javascript, it's just that Vue's instance options API isn't one of them.

We use them internally in many places where a real encapsulated class makes sense like Router in vue-router and Store vuex, for example.

@smolinari
Copy link

smolinari commented Mar 31, 2017

Sure, and that is fine to do. But, it should also be said, you don't need classes to get encapsulation in JS. 😄

Scott

@Schenn
Copy link

Schenn commented Mar 31, 2017

"what exactly is it setting"

Its not, but when the value is BEING set, the SETTER can either kick off an event, trigger callbacks, etc. e.g.

foo.bar = 1; triggers callback that updates the span with the content.

The point wasn't that "You should ONLY limit the app to class based architecture", just that using classes doesn't prevent you from doing the same work. There's no legitimate reason to explicitly prevent devs from using class instances if they so choose.

E.g. When rebuilding content on change, you may need to do ajax calls to get info, or push notifications, or etc etc etc. Putting all of these methods inside a giant literal object with hard coded properties is an anti-pattern. So, instead we break it up into components... which need to be compiled to work. So, might as well use es6 features. Oh? You mean that I have to export both the class AND an interface literal object to interact with it? Why? Because the object that is holding the data is hidden in favor of an interface object... Why? Because of arbitrary decisions and a general dislike towards class based development. Never mind the fact that many development houses and internal teams are switching languages on a regular basis.

Why provide an easy way for those devs to switch context when you can add extra work!

As an aside, you CAN use the class that is providing the data by referencing $data.property or $data.method in your templates and code and it works just as well.

@sjcalamia
Copy link

Yeah, I think if Vue can't be transformed to use classes then it will get left by the wayside within a year or two.

After reading this thread and learning that not only aren't there plans, but that the people contributing to Vue's development think classes are a bad idea for their code base, and considering the obvious direction of JavaScript and the internet as a whole, I am glad I haven't invested much time in Vue.

The fact is, many of these frameworks were created to solve issues in serious JavaScript development in the post-JITC era which JS had not yet caught up to. JavaScript is evolving with a lot of debate and consideration, and to dismiss that is strange. It looks like some of these other frameworks are at least trying to keep up.

React has a lot of the same things mentioned above, props etc, and yet manages to use ES6 classes. I'll be happier with React when they allow custom elements rather than JSX only, but eh, I guess we can't have everything. Maybe Polymer is the real future, hard to say, but it seems forward compatible.

@LinusBorg
Copy link
Member

LinusBorg commented Jun 7, 2017

@sjcalamia: I have nothing against classes per se, and the others don't as well, as far as I can speak for them.

But the point is, we currently lack two things:

  1. A good argument for making the public API (internally we already use classes in many places) available in form of a class to extend, besides "classes are the future".

Javascript is not an OOP language and won't become one, so (pseudo-)classes are only one of the available options when it comes to excapsulating objects and behaviour. So I simply doubt that everyone has to turn everything into a class to be "future-proof". Just as an example, d3 is arguably one of the biggest and most important libs in this ecosystem, and it's not class-based with no sign of becoming obsolete for that or any other reason.

So concerning Vue, I haven't seen any arguments (in this thread or elsewhere)arguing why Vue's API should be a class, what inherent advantages that brings, except (and this is indeed an important point) that typing systems like Typscript work better with classes - hence the existance of vue-class-component.

  1. A good proposal how to convert the API of Vue into something that:
  • works with classes
  • has a nice syntax
  • doesn't require ES"next" features for said nice syntax.

Since such an API doesn't seem to exist/be feasible with the current JS standard, and one of Vue's goals is to work in the browser without a build-system, we currently don't see any realistic way to make a class API available.

However, if we have these things one day, along with good arguments for it, some later major version of Vue might introduce a class API, that's totally possible.

And since you mentioned React: its class API is not without its problems, as can be seen here and has also been discussed at some length above. Also, React has a smaller API surface since it's lacking the Reactivity system, which brings with it watchers, computed props, the need to declare props etc. - so React doesn't run into the problems I detailed in previous replies.

Anyway, you already seem to have made up your mind and seen our future for us, so good luck with the framework you choose - React would be a great choice, of course!

@Schenn
Copy link

Schenn commented Jun 8, 2017

The real issue is that the class that we've built is being discarded in exchange for a proxy object. You could provide a class which sets up all those watchers, props, etc in its constructor and getters and setters and we as devs could simply extend it for our own class.

Instead, we have to use protected data to access the actual thing we built instead of the pseudo-thing you provide instead.

@Schenn
Copy link

Schenn commented Jun 8, 2017

Also: JS IS oop. Even if it's prototyped instead of derived from classes. Most of js code deals with literal objects, with methods and properties AND it supports Polymorphism, encapsulation and NOW inheritance (albeit sugar syntax).

https://stackoverflow.com/questions/107464/is-javascript-object-oriented

https://www.sitepoint.com/oriented-programming-1/

@yyx990803
Copy link
Member

yyx990803 commented Jun 8, 2017

@sjcalamia in case you didn't realize, this thread is already 1.5 years old. Since Vue should have been "left by the wayside within a year or two" because it doesn't use classes, we should already be irrelevant by now. However, Vue has seen exponential growth in this past 1.5 years despite the lack of classes, and is continuing to get more mindshare - something must be wrong with this world.

If you think class inheritance of UI components, or OOP for UI components is actually a good idea, you'll probably also be disappointed when you use React. Just because it has a class-based API doesn't mean it encourages or even plays nice with OOP. React is all about functional composition and its core devs explicitly suggest against class inheritance of more than 1 level deep.

From my experience building UIs, functional composition or mixins/traits based systems are much more flexible and suitable for UI components than OOP. Trying to shoehorn the whole OOP dogma onto UI components is like trying to hit screws with a hammer, just because you are used to a hammer.

"Easier to read" is just a matter of familiarity. The only practical benefit of class-based APIs is that it's more friendly to type-checking. But even that argument will soon be moot with Vue 2.4's enhanced type definitions + TS 2.3's contextual typing.

You are welcome to disagree, but please do so in a more constructive fashion by actually giving concrete examples of why classes are better, other than "because it's the future".

@sirlancelot
Copy link

I'm all for keeping the Vue component definition as-is for all the reasons Evan has outlined. It's just not the right fit for all the features a component definition needs.

That being said, I had this wild idea that I just need to get out there just in case it's still on someone's radar...

What if the component definition could look like this?

class MyVueComponent extends Vue {
  static functional = false

  static el = "#app"
  
  static template = "<div>{{ message }}</div>"
  
  static components = {}
  
  static mixins = []
  
  static model = { prop: "value", event: "input" }
  
  $render(h) {
    return h("div", this.message)
  }
  
  // Initialize Component Data
  $data() {
    return {
      message: "Hello Vue.js"
    }
  }

  get computedProperty() {
    return this.message + "!"
  }
  
  // Lifecycle hooks
  $beforeCreate() {}
  $created() {}
  $beforeMount() {}
  $mounted() {}
  $beforeUpdate() {}
  $updated() {}
  $activated() {}
  $deactivated() {}
  $beforeDestroy() {}
  $destroyed() {}
  
  // Watchers
  set "$data.message"({ value, previousValue }) {
    console.log("message was changed from", previousValue, "to", value)
  }
  
  // All other methods
  someMethod(param) {
    return new Promise(resolve => setTimeout(resolve, 200))
  }
}

It's quite an exotic idea, and I'm not a huge fan of the $'s littered throughout, but maybe someday someone somewhere could make a Webpack loader or Babel plugin to convert the above notation in to a proper Vue component definition.

@smolinari
Copy link

@yyx990803

But even that argument will soon be moot with Vue 2.4's enhanced type definitions.

Ooohh. Ooh. Ooh. Will this be using Flow? Will you be explaining this at VueConf? Or maybe anytime sooner? 😄

Scott

@salilpitkar
Copy link

salilpitkar commented Sep 17, 2017

I agree that class doesn't offer any advantage over current JS syntax. But Vue competes with React (at least in the US) and Facebook embraced ES6 classes as the official syntax for React. Devs working on React are becoming used to writing code in ES6/7/8 and hence would be an easier transition to Vue although they can choose use the plugin.

I don't think that introduction of classes in JS was a mistake because the syntax is cleaner, makes dev more productive, makes tooling easier and lures devs, who are used to OO syntax. It is still important to understand how ES6 works under the covers.

Also I think, using flow or Typescript highlights a subtle point in that even if languages are untyped or weakly typed, they could be strongly typed at compile type to avoid unnecessary runtime checks and problems.

@smolinari
Copy link

lures devs, who are used to OO syntax

And makes them think they are working in typical OOP, which JS doesn't actually offer, which ultimately and unnecessarily leads then to what I call "JS frustration" later. The "class" in JS is putting proverbial lipstick on a pig. Those who embrace the class keyword embrace something like stucco on a building, instead of the proper building blocks of the language.

Scott

@Schenn
Copy link

Schenn commented Sep 17, 2017 via email

@smolinari
Copy link

Learn JS, then you'll understand. I don't hate JS. I love it. 😄

Scott

@LinusBorg
Copy link
Member

Just as a small sidenote how confusing it can be to deal with "classes" in JS: trying to find out if an instance is a direct instance of a class:

https://jsfiddle.net/1ebm3173/1/

Sure, there's a way, but there are also many ways that intuitively should work if JS really had class inheritance, but it hasn't - it has prototype delegation.

Now I don't claim to be an expert in these things, I'm more in the camp of people that were confused when trying to do OO in JS and found out it's not really OO...

@smolinari
Copy link

Now I don't claim to be an expert in these things, I'm more in the camp of people that were confused when trying to do OO in JS and found out it's not really OO...

Exactly what I am getting at. I was fortunate to learn JS from ground up from someone who told me to dump what I knew about OOP before we started to enter the world of JS' object orientation (which it absolutely does have). The prototypal delegation is a different world, but once you understand it, it is actually liberating.

Scott

@Schenn
Copy link

Schenn commented Sep 17, 2017 via email

@yyx990803
Copy link
Member

yyx990803 commented Sep 17, 2017

People who "just want classes" probably never actually tried to envision what the equivalent of Vue's current API would look like using classes - more importantly, would it be using "the ES6 class as is specced today", or "the ES6 class that may or may not be specced (using stage-x proposals)"?

Well, I have tried, and my conclusion is using only specced features from ES2017, there's simply no way to provide something that is elegant enough. You end up with tedious constructor calls (and don't forget super, binding instance methods to this), awkward workarounds like static get option () { return { ... }} or attaching static properties after the class declaration block, etc. etc.

Polymer 2 uses classes now, and this is what you have to do to simply declare the name of the element:

export default class MyNewView extends Polymer.Element {
  static get is() {
    return 'my-new-view'
  }
}

// compare to
export default {
  name: 'my-new-view'
}

And this is what you frequently see in React:

export default class Foo extends React.Component {
  constructor (props) {
    super(props)
    this.state = { foo:123 }
    this.increment = this.increment.bind(this)
  }

  increment () { ... }
}

// compare to
export default {
  data: () => ({ foo: 234 }),
  methods: {
    increment () { ... }
  }
}

Using class for class' sake actually adds more noise to achieve the same amount of work. And the benefits? Maybe not much beyond the fuzzy warmness of being able to use a class to represent everything. The fact is, in terms of UI components, you rarely perform direct OO inheritance; most of the time you compose via mixins, slots and higher-order components. You can't even new the class in React.

Things can get a bit nicer with class fields (stage 3) and decorators (stage 2) - but they are proposals, not standards yet. They may change, break, or even be reverted. Defaulting to requiring them to use Vue means I'm delegating the risk to every user of Vue, which I don't think is a responsible choice, so it has to be opt-in (which you can already do with vue-class-component).

Arguing over whether it's OO or JS or prototypal is pretty off-topic. From a pragmatic point of view, the standard today is not good enough to provide a decent equivalent of Vue's current API, simple as that. What if class fields and decorators reach stage 4? Yes, by then I'd be happy to revisit this issue. But before then, I think I've explained everything I have to say about this topic, locking until that happens.

@vuejs vuejs locked and limited conversation to collaborators Sep 17, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests