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

Function-based component API (extended discussion) #55

Closed
abalashov opened this issue Jun 24, 2019 · 154 comments
Closed

Function-based component API (extended discussion) #55

abalashov opened this issue Jun 24, 2019 · 154 comments

Comments

@abalashov
Copy link

Opening an issue here with a copy of my original comment to #42, per @yyx990803's request:


I am the author of "Love letter to Vue": http://www.evaristesys.com/blog/love-letter-to-vue/

As a big booster of Vue who can appreciate its distinctive characteristics and the way it has stood apart from competing frameworks, I must say that I am perturbed and disappointed by the function API proposal. I have to agree with the above posters who say that no serious problem is being solved here which would warrant such a radical shift, and that in essence, this proposal amounts to chasing shiny new things. Yes, there are indeed some code decomposition and modularity problems that this change addresses. But not all theoretical problems are worth addressing.

One of the biggest virtues of Vue, which I mention in the article, is that here, halfway through 2019, it's still quite literally the same Vue I first picked up in late 2016. This is a refreshing contrast to the general pace of change and deprecation in the JS/web world. Frank Chimero's high-level, perspicacious take on this problem bears mention here: https://frankchimero.com/writing/everything-easy-is-hard-again/

From a political and a philosophical point of view, I think it's vital to understand that introducing radical API changes of any kind to any project is breaking, and a generous offer of backward compatibility with old APIs does not solve this problem. With due acknowledgment to the fact that the 2.x API is not slated for deprecation and the labourious emphasis this has received, you're still sunsetting it in an overall ecosystem sense. The 2.x API will no longer be a first-class citizen. A completely new way of doing things insinuates a preferred new way of doing things, and sample code, tutorials, books, etc. will inevitably adopt it over time, leading to a manageability and direction crisis for those with investments in huge Vue code bases. New APIs are unavoidably ideological statements, and the devaluation of old APIs -- indeed, the very ontology of "old" vs. "new" -- have an ideological valence.

While change is inevitable, people want to follow "best practices" in an effort to stave off bit rot, and feel a psychological pressure to buy into the current wave of thinking. So, offering to support the old options API is not of much help; this RFC raises the prospect that the commitment to maintaining backward compatibility with the 2.x option API is not steadfast, and will slowly de-orbit over time, like the Mir space station. In effect, you are decreeing that there's now a new way of doing things, and this is the way they should be done.

I also agree with the criticism mentioned in the Downsides section above: the concern that this way of doing things is arcane and more likely to lead to byzantine or labyrinthine "spaghetti code". One of the biggest selling points of Vue is its simplicity and approachability. It's static enough that a major organisational commitment to Vue has shelf life and durability, rather than evaporating in one's hands, as so many other JavaScript-related commitments do owing to capricious, whimsical API changes and architectural gewgaws.

Thus, technical bickering about code organisation and modularity notwithstanding, from a business and ecosystem health point of view, I think this is the light in which this RFC should be considered, and the cost against which the (from my perspective, marginal) improvements offered should be carefully and judiciously weighed. I do not see a sufficiently captivating problem here worthy of such a radical solution, even if commitment to the 2.x approach is scrupulously observed for some time. It's not worth throwing away the things that make Vue good.

I'm natively Russian, and there is an old political anecdote about this:

Lazar Kaganovich brings Stalin a scaled-down model of a new, reconstituted Moscow, the grandiose global Capital of Socialism that Stalin envisioned (not unlike the visions of Hitler & Speer for the Thousand Year Reich architecture). Most iconic historical features of pre-revolutionary Moscow are demolished, and replaced with grand plazas and enormous, titanic buildings that capture the idealism of the class-conscious revolutionary proletariat, the enormity of the Generalissimus's futuristic vision, and the zeal to shed bourgeois architecture and aesthetics and break with the past.

Stalin asks: "Lazar, where is Saint Basil's Cathedral?"

"It was removed; it will be demolished."

Stalin heaves a sigh and rolls his eyes.
"Lazar, put it back."

@yyx990803
Copy link
Member

yyx990803 commented Jun 24, 2019

I respect your opinion so please don't take any of the following as being personal.

no serious problem is being solved here which would warrant such a radical shift, and that in essence, this proposal amounts to chasing shiny new things

I couldn't disagree with this more. Logic composition is probably one of the most serious problems in terms of scaling projects. Quoting myself from another thread:

  • the RFC thread also has MANY users expressing how they find the RFC to be solving the exact problem they are facing right now. And they have also mentioned that the current object-based syntax is the exact bottleneck that hinders the scalability of their projects. I acknowledge that many of you may never personally experienced this (which is totally valid, and even great!), but if an aesthetics choice comes at a real maintenance burden (admittedly only in certain types of projects), then it's no longer just an aesthetics problem. Denying some users' opportunity to solve their problems with a better abstraction due to the aesthetics preference of some other users seems like the wrong trade-off to make.

Vue started small, but today it's being used in a very wide range of projects, with varying level of complexity and business domains. Users dealing with different types of projects will run into different needs, some can be easily dealt with using the object-based API, while some cannot. The primary example is

  1. Large components (hundreds of lines long) with encapsulates multiple logical tasks
  2. The need for sharing the logic of such tasks between multiple components.

For (1), each logical task is forced to be split between option types. For example, a single data-fetching task may need a prop, a data property, a computed property, a mounted hook and a watcher to work together. This means when you pick up this component and try to understand its data-fetching logic, you are constantly jumping up and down in the options list trying to locate the pieces that are related to it. At the same time, when you skim over a property, although you know what type it is, it's quite a bit harder to tell which logical task it is supposed to be dealing with. This gets worse as more logical topics are added to the component. In comparison, with the new API, all related logic for data fetching can be grouped together, and more importantly, cleanly extracted into a separate function, or even a separate file.

An analogy for this problem is file organization in projects. Many of us have come to agree that organizing files by file type (e.g. splitting everything into html, js and css folders) don't really scale. The code related to a feature will be forced to be split between three folders, just for a false impression of "separation of concerns". The key here is that "concerns" is not defined by file type. Instead, most of us opt to organize files by feature or responsibility. This is exactly why people love Vue single-file components. SFC is a way to organize code by feature. Ironically, when SFCs were first introduced, many resisted it because they feel that it violated separation of concerns, only later to acknowledge that SFCs are in fact the more reasonable way to separate concerns.

Point (2) has largely been explained in the Motivations section of the RFC, showing that it achieves what mixins/HOCs/scoped slots can achieve without any of their drawbacks.

With React Hooks, we discovered some of its characteristics could help those users solve these problems described above. This is the fundamental reason for us to come up with this proposal. It is indeed a "new thing", but we are adopting the new thing because it presents a solution to objectively existing problems, not just because they are "new". In the long run, the availability of this new API will pay huge dividends in the hours saved for the developers dealing with the mentioned problems.

Type safety is also an important consideration - again, this is something many users wanted badly but may not appear valuable to those who do not use TS. That's understandable - but I think it's a bit selfish to claim that it's not solving any problems because the problems being solved does not affect you.

introducing radical API changes of any kind to any project is breaking, and a generous offer of backward compatibility with old APIs does not solve this problem

"Breaking" is defined by users forced to change their code. Since users will not have to change their existing code, it is not breaking. I don't think there's anything else to argue about in this aspect.

If even backwards compatibility is not enough, then you are essentially saying a project should never introduce any radical new ideas, ever. I think that's project policy level argument, which if I get to vote, I will firmly vote against. We will try our best to keep the best interest of our users in mind, but the project must and will evolve.

this way of doing things is arcane and more likely to lead to byzantine or labyrinthine "spaghetti code". One of the biggest selling points of Vue is its simplicity and approachability. It's static enough that a major organisational commitment to Vue has shelf life and durability, rather than evaporating in one's hands, as so many other JavaScript-related commitments do owing to capricious, whimsical API changes and architectural gewgaws.

On the contrary, the very motivation of this proposal was to improve the maintainability of long-term Vue projects.

If we look at any JavaScript project, all code starts from an entry file, which is essentially an implicit "main" function being called when your app starts. If having a single function entry will lead to spaghetti code, then all JavaScript projects should be spaghetti code - which is obviously not the case. Why? Because as developers we've learned to organize our code by splitting it up, either into modules or into smaller functions.

A core characteristics of the function-based API design is that understanding code in setup() is not any different from understanding idiomatic JavaScript code and any technique you can use to organize ordinary JavaScript code can be used to organize your setup() function. Any knowledge / style guide / code review process that applies to normal JavaScript code written by your team can be applied to code in Vue setup() functions.

I agree that with the new API you have a theoretically lower bottom threshold for code quality, but as mentioned that can be mitigated by whatever you are already doing to prevent spaghetti code in non-Vue parts of your codebase. On the other hand, code written with the new API also has a substantially higher upper limit in terms of code quality. Any code written with the new API can be refactored into much higher quality code than their options-based equivalent, whereas with the options-based API you will have to resort to mixins and deal with its drawbacks.

I also want to point out that this RFC is not about trading simplicity for maintainability. We should be aware that we are comparing the impressions between an API you've probably been using for years vs. an API you've just seen for the first time. Fundamentally, this is a shift of how you think of a component:

  • Options-based API thinks of a component as defined by the types of properties/methods/options it contains.
  • Function-based API thinks of a component as defined by the logical topics it is encapsulating.

What many users are lamenting when they talk about "losing simplicity" is in fact losing the ability to inspect a component by option types. But with the new API, it should be quite straightforward to implement a component analyzer that provides a view that allows you to inspect the component by property types. That is to say we would be able to look at our component from both perspectives, whereas with options-based API you are limited to one (since the intention about logical topics is lost when split between options).

It's not worth throwing away the things that make Vue good

I'm getting tired of repeating "nothing is being thrown away." But let's try to define what "things that make Vue good" really is. Many users against this RFC seem to define that as the object-syntax, and as if taking the object-syntax away takes away everything that makes Vue Vue. But let's take a look at what is left intact:

  • The template syntax does not change (and the performance is getting better!)
  • The way the reactivity system works does not change
  • The concepts of computed properties, watchers & component lifecycle do not change
  • The SFC format does not change
  • The CLI does not change
  • The progressive nature of the framework does not change
  • The team's commitment to providing better development tools does not change
  • Technically, even the object format does not change: everything that worked will still work.

Vue's object syntax has existed since day one. Many of the above were added later along the way, and each contributed to Vue's growth. If you believe the object-syntax is all that matters to you, I would kindly ask you to take a step back and rethink what really makes Vue what it is. After all, this RFC is not that radical a change as it may seem.

@smolinari
Copy link
Contributor

smolinari commented Jun 24, 2019

I have to agree with the above posters who say that no serious problem is being solved here which would warrant such a radical shift, and that in essence, this proposal amounts to chasing shiny new things.

The new API solves these problems:

  1. It fixes the issues Vue currently has with logic composition.
  2. It solves the issues of type inference, leading the way for Vue to be built on TypeScript and offering first class TypeScript support.
  3. And not directly mentioned in the RFC, but core to the whole RFC, is the way features of one or more components can now be encapsulated properly and still have strong cohesion. It's allows for better following of the SOLID design principles.

I wrote an article many moons ago about the "separation of responsibilities" Vue affords the community and I knew at that time, it was why Vue was so cherished by those who "understood" it. It's why I love Vue too. Yes, Vue was also my favorite and still is, because every time I leave Vue and work even a little on some React project, even a simple to-do list, it feels......icky.

So, I get it. I do understand the feeling of having been let down, by leaving Vue's "norm" of object option API.

But, I also understand the benefits of the new API. I wouldn't consider myself an advanced programmer, and I'm learning more and more. I'm on the Quasar Framework team and I'm only mentioning that, because here and there, I get to work on bugs and dig into sometimes complex components. For instance, just the other day, I dug into QEditor to fix a link button/ link editor issue. It was hard for me to get into that component's code, because so much of the feature's code I needed to look at was split up into the different options. Some of the code was in a mixin, the rest spread out across a 400 line component, using a render function. It was hard for me to get the gist of the whole feature of the link button and the link editor.

In the end, I fixed the bug, but my solution wasn't elegant and of course, Razvan, the founder and lead dev of Quasar, came up with a much more elegant (and cross-browser complete) solution.

Thing is, I know for sure, had QEditor been built with a more "feature based" code design, where the link button and editor were more encapsulated, I'd have been able to understand the inner workings of the component much faster and might even have come up with a more elegant solution.

I also wrote an article asking users to actually dig into the source code of Quasar. But, I also understood the "nah, it's wonderful magic, I don't want to learn the tricks" kind of mentality. And it's Vue's option object magic that more advanced developers want to break. And, unfortunately, it's also that kind of magic that makes Vue so wonderful.

I realize that story and my impressions/ opinions aren't going to persuade many users, who haven't gotten into that kind of depth of component code yet. And that is more than likely 80% of Vue's user base. However, the things most of you are counting on, the component frameworks you are using, could very much take advantage of better code encapsulation. So, maybe we can all see this as a good addition (and most importantly, always an addition) to the current API for more advanced users????

One thing that also just hit me while writing this reply was this. We are all happy with Vue's template DSL, which is staying despite the new API btw 😄. When I reread this part of the RFC......

Options removed in the Lean Build
These options will not be available in the lean build of 3.0.

data (replaced by setup() + value + state)
computed (replaced by computed returned from setup())
methods (replaced by plain functions returned from setup())
watch (replaced by watch)
provide/inject (replaced by provide and inject)
mixins (replaced by function composition)
extends (replaced by function composition)
All lifecycle hooks (replaced by onXXX functions)

......all v3.0 is doing is the same for HTML. It's a (rather simple) DSL for getting Vue's reactivity into standard JavaScript. I'm not sure how many of you can agree with that, but if you look at the 7 function + the lifecycle hook onXXX additions, it's really not that much to learn or use. It's most definitely easier than React's hook system (and also doesn't have it disadvantages). And in the end, Vue's SFCs, even with this new API, is absolutely nothing like React. Why people say that, I have no idea.

What is similar to React is offering more flexiblity and yes that means more responsibility on us, the users of Vue. But, it's still Vue and it's still better than the rest and now it's even more awesome.

Scott

@beeplin
Copy link

beeplin commented Jun 24, 2019

@smolinari totally agree... digging into Quasar's complex components and finding something among all kinds of mixins is a nightmare. :))

EDIT: in case of misunderstanding: I am talking about the flaws of mixin mechanism. Nothing to do with quasar ;)

@smolinari
Copy link
Contributor

@beeplin - And just to be clear, you would also agree when I say, Quasar isn't poorly written either. It's Vue's built-in constraints, which make it that nightmare.

Scott

@martinsotirov
Copy link

martinsotirov commented Jun 24, 2019

I think the elephant in the room is that the problems this RFC solves (logic composition and better type support) are legitimate but not faced by the community at large.

I have used Vue exclusively for frontend work since 2016 (from small one-off things to large enterprise projects), and I have yet to face any of the problems this RFC solves. I admit that mixin usage can be a problem for large codebases (see Vuetify) so I just avoid using mixins. There are just better ways to structure large modular codebases.

@thatandyrose
Copy link

thatandyrose commented Jun 24, 2019

Hi guys and gals, not sure if this is the right place to comment on the RFC (if not let me know!) but here goes:

What's great about the current options based API is clarity and opinion. It's clear what is framework hook/code and what is your code.

What's bad about the new proposal is, it lacks opinion and everything is kinda just mixed in there. It's not as clear nor as friendly.

Having said that, I 100% agree this is solving a real issue, which is separating different concerns of your component. And I also agree mixins don't kinda work.

However, could we find another middle ground? vuex has a nice example of "modules" within a store to separate concerns of different sections of your app.

In this way, could we not think of the options API as having many concerns? So, you could perhaps have many data options, many computed options, but all nested within their own concern.

I realise this is just an off the cuff idea, but could be path towards solving the concerns issue while still maintaining the API friendliness and simplicity of vue!

Thoughts? or... shall I just "get my coat" (reference to an old British sitcom 😬 )

@smolinari
Copy link
Contributor

smolinari commented Jun 24, 2019

In this way, could we not think of the options API as having many concerns? So, you could perhaps have many data options, many computed options, but all nested within their own concern.

That IS the whole idea of the function-based API. It takes the options you mentioned and makes them functions, which need to be called within a setup method, but can be encapsulated in different "feature" functions or rather - for better TS support - feature objects.

@yyx990803 - If you could just say, Vue will offer both APIs forever. Then all this will stop. The fear comes from the greatness of the options API possibly being lost at some point (doesn't matter when). It is what makes Vue so attractive to beginner, intermediate and maybe even early advanced programmers (if that is such a thing, but you get the point). You did make a great API and again, the fear is, you want to make it into something different, something where those who love Vue aren't seeing or even contemplating finding that same love with the new API. Those others aren't seeing the advantages of the new API and in fact, are seeing it as something "ugly". I too still feel that sentiment. It doesn't really look elegant to me either. But, I can overlook those "warts", as I do see the practical advantages.

If you could find a better more "elegant" way to have a function-based API, next to the options API, you'll probably be hitting the home run it should be. 😄 Your "programming art" just needs to be more "pretty" and less "abstract". 😉

Scott

@yyx990803
Copy link
Member

@martinsotirov I'd be curious to hear about "better ways to structure large modular codebases" when using the options-based API, especially how they allow you to avoid all these mentioned problems in a large enterprise codebase.

@martinsotirov
Copy link

martinsotirov commented Jun 24, 2019

@martinsotirov I'd be curious to hear about "better ways to structure large modular codebases" when using the options-based API, especially how they allow you to avoid all these mentioned problems in a large enterprise codebase.

Export shared functionality in a separate vanilla JS service class that you just import in every component that needs it. Much easier to track things later than trying to figure out where X or Y was inherited from.

Structuring code in a way that avoids mixin usage also forces you to rethink your architecture and discover potential problems, similar to how writing unit-testable code forces you to think about your code. And in many other cases it is better even to reimplement a nuanced version of some functionality instead of blindly following the DRY principle, if it helps keep clarity of the codebase better.

@martpie
Copy link

martpie commented Jun 24, 2019

I don't have as much experience with Vue.js as I have with React.js so take what I am going to say with a pinch of salt. Though I absolutely like this proposal, I think one point has been really wrongly formulated.

One advantage of Vue.js is also one of the advantage of the Go language: you have "one way" of doing things, instead of 20 different ones. Whereas React falls short is this aspect: classes/function components, hooks/higher-order-components/decorators for example, are just solving the same problems in 2 or 3 different ways.

Yes, they are all supported and nothing is deprecated, but when opening a file, you will never know with what concepts the developer wrote this component (redux or providers? HoC or decorators? etc..), and the cognitive effort to switch from one to the other is not trivial.

Of course this could be enforced by team decisions, but still, the point remains.

Here is I think one of the key of the ranting going on: giving more tools/flexibility to the community also gives more tools to fracture how everyone will write Vue components.

I don't think there's a solution to this problem, but I just wanted to share that.

@yyx990803
Copy link
Member

yyx990803 commented Jun 24, 2019

@martinsotirov I think what you are doing is essentially side-stepping the options-based API and introducing a custom pattern, whereas the function-based API provides first-class affordance to such logic extraction. Reactivity inside service classes are also somewhat implicit (I assume you are returning class instances in data()?). In addition, note that your service class can only encapsulate pure state logic and won't be able to encapsulate logic that has to do with side effects (i.e. watchers or component lifecycle hooks), so there's a limitation on what type of logic you can extract.

@martinsotirov
Copy link

In addition, note that your service class won't be able to encapsulate logic that has to do with side effects (i.e. watchers or component lifecycle hooks), so there's a limitation on what type of logic you can extract.

If you get to the point of having to reuse things tightly bound to the component (side effects etc.), then you are doing something wrong and have to rethink your architecture or break up the component. That's the benefit of avoiding mixins, at least for me – it forces me to write simpler and better structured components.

@Akryum
Copy link
Member

Akryum commented Jun 24, 2019

@martpie Vue is already providing a lot of different ways for doing the sames things, and it's been fine!
For example:

  • Inline templates, SFC templates, render functions, JSX, x-template, ...
  • Extends, Mixins, HOC, Renderless components
  • Script tag, vue-cli, custom build setup
  • Directive shorthands or full directives
  • Root state, event bus, Vuex, other community solutions

@Akryum
Copy link
Member

Akryum commented Jun 24, 2019

Export shared functionality in a separate vanilla JS service class that you just import in every component that needs it.

@martinsotirov Sounds a lot like what the Function API provides (but it does in a more Vue kind of way).

have to rethink your architecture or break up the component

The Function API allows you to easily do exactly that without having to create a lot of new components.

@yyx990803
Copy link
Member

yyx990803 commented Jun 24, 2019

@martinsotirov

If you get to the point of having to reuse things tightly bound to the component (side effects etc.), then you are doing something wrong and have to rethink your architecture or break up the component. That's the benefit of avoiding mixins, at least for me – it forces me to write simpler and better structured components.

The fact that you can cleanly extract and reuse side effects is the exact power of the function-based API. It's not just about reuse - but also code organization. There are certain things that just won't make sense as a separate component, nor would they fit in a service class, but can still be perfectly extracted into a function (e.g. data fetching on mount + refetch when props change).

@jacekkarczmarczyk
Copy link

@yyx990803 I agree about life cycle but why watchers? Can't I put watchers for example in the constructor or in some method (startWatching() { watch(() => this.foo, () => this.doSomething() })?

Also as this RFC became quite a PR (public relations, not pull request) failure maybe you can try using a backdoors to introduce it. What I mean is to provide an API for reactive data which is not related to the component API, so something that is already partially done (see observable in 2.6), I guess watchers and computeds are almost ready as well. And if this is something that people need they will start using it but nobody will oppose because it doesn't even try to deprecate anything, it's not related to component API etc. And when it will start to be widely used it will be much more natural to merge it into the component API. That could mean that the function API could be introduced later than in 3.0, maybe 3.X, maybe 4.0, but you'd have a time to see how the small features (observable, watchers) etc are used in the real world and have a chance to analyse it, maybe rethink API etc

Sounds a lot like what the Function API provides (but it does in a more Vue kind of way).

@Akryum that's the point - this interferes with the existing API. If it wasn't related to the component API there wouldn't be a threat that something will be backwards incompatible or deprecated.

@yyx990803
Copy link
Member

yyx990803 commented Jun 24, 2019

@jacekkarczmarczyk the RFC has already been updated to reflect exactly that: 6fe6f7b

@jacekkarczmarczyk
Copy link

@yyx990803 Not exactly, the current RFC still uses components as a main example of how to use it:

Expose logic-related component options via function-based APIs instead.

A new component option, setup() is introduced

One of the key aspects of the component API is how to encapsulate and reuse logic across multiple components.

What I meant is to decouple it completely from components and call it reactive data api

@martinsotirov
Copy link

@martinsotirov Sounds a lot like what the Function API provides (but it does in a more Vue kind of way).

You're thinking from the point of view of a framework developer, not product owner or CTO. From the business perspective, you want to have less things tightly coupled to the framework, not more. You want to be able to reuse at least parts of it when you port your product to framework Y next year.

@yyx990803
Copy link
Member

@jacekkarczmarczyk it was originally two separate RFCs (advanced reactivity + dynamic lifecycle injection). Unfortunately the ability of hooking into component lifecycle is a crucial part of the composition capabilities.

@Akryum
Copy link
Member

Akryum commented Jun 24, 2019

@martinsotirov Maybe you shouldn't use a framework then? 😕 I'm confused because I would rather use the framework to its full potential to go faster, be more efficient and have better, more maintenable code than going halfway which can lead to unnecessary more complicated code, unnecessary custom solutions, spaghetti code, bikeshedding...

@backbone87
Copy link

backbone87 commented Jun 24, 2019

i never really got this "framework independence" stuff really. the benefits of (good) frameworks only comes to fruition when you embrace its paradigms and best practices fully. sure, not every framework is good for every use-case (edit: or every dev team), but that doesnt mean a framework should stop evolving. especially in the Web app world where you are so heavily constrained by the platform (browser) it makes no sense to argue for framework agnostic coding. i personally never saw a project swap frameworks without rewriting like 80% of its codebase anyway.

@martinsotirov
Copy link

martinsotirov commented Jun 24, 2019

@martinsotirov Maybe you shouldn't use a framework then? 😕 I'm confused because I would rather use the framework to its full potential to go faster, be more efficient and have better, more maintenable code than going halfway which can lead to unnecessary more complicated code, unnecessary custom solutions, spaghetti code, bikeshedding...

The solution to tight coupling is not to not use a framework. That's just naive.

i never really got this "framework independence" stuff really. the benefits of (good) frameworks only comes to fruition when you embrace its paradigms and best practices fully. sure, not every framework is good for every use-case (edit: or every dev team), but that doesnt mean a framework should stop evolving. especially in the Web app world where you are so heavily constrained by the platform (browser) it makes no sense to argue for framework agnostic coding. i personally never saw a project swap frameworks without rewriting like 80% of its codebase anyway.

Starting to get off topic here, but anyway - this is not about framework independence but about SOLID code. Even if you're not switching frameworks but just upgrading versions, it would be much easier if your core business logic is not tightly coupled to specific framework features.

I agree with you about the 80% code rewrite, what we're arguing here about is the 20%.

Am I old or are best practices and design patterns just generally not known among web developers nowadays?

@mika76
Copy link

mika76 commented Jun 24, 2019

@backbone87 This is really one of those things which different people see differently. And I don't think the two sides would ever come together. One one hand is the camp that says "use the framework to its fullest potential" but the other one is "protect your business logic in separate modules so as not to rewrite later". Both views are totally valid and they both have pretty strong pros and cons too.

See the further up from from developing you go, the more you see how much all this rewriting costs companies. While they have to pay for someone to rewrite code that worked perfectly well in a previous framework those developers are NOT working on new features, or fixing important bugs for customers. They are basically making something work which was already working. Real big waste of money.

Now I'm not saying that there are not reasons to change things and to evolve, but the people that have to fork out money, or even worse, BEG their bosses/investors for money to do this are the ones that feel the pain.

@martinsotirov I think we're just getting old man 😆

Now please, this is just an explanation to @backbone87 's comment - I really do not want to open up another huge debate/talk about what's better. If there's one thing I think everyone should be aware of by now in these threads is how diverse a population is using Vue and how passionate people are about it. This is not a bad thing, but it makes evolution and change hard - always has and always will.

@yyx990803
Copy link
Member

Please stay on track and focus on the original RFC (on why it is or is not necessary).

@tochoromero
Copy link

I'm personally looking forward to trying this function API, I do see it's advantages and the problems it is solving. But the Object API would still be my preferred way of writing a good amount of simpler components.

I can also see components with the normal object API properties: life cycle hooks, data, computed properties and methods, coexisting with a setup function to bring in functionality that would normally be on a mixin.
Is this mix and match approach going to be supported?

@Akryum
Copy link
Member

Akryum commented Jun 24, 2019

Is this mix and match approach going to be supported

Yes.

@jacekkarczmarczyk
Copy link

it was originally two separate RFCs (advanced reactivity + dynamic lifecycle injection)

@yyx990803 and that's exactly what i'm talking about. Maybe the new RFC trying to add too much unknown to the well established API is what made people scared. Maybe if you implemented those 2 RFCs first people would have time to get familiar with new techniques and then, in the next version of Vue, maybe major, maybe minor (and after getting a lot feedback from real world use cases) you could add the function-based component API which would use already known patterns.

Despite appearances programmers (at least some) are humans and human manipulation techniques (such as introducing changes step by step instead of 1 big at a time, even if that leads to the same outcome) also could be successful :)

The only drawback is that it could delay the whole process. However since none of those 2 RFCs as I understand is not a breaking change then they could be introduced anytime, maybe even in 2.X, the point is that it would give people time and opportunity to understand new API and make the switch from advanced reactivity + dynamic lifecycle injection to function-based component API more natural

Side note - it's not about how to introduce any change/new feature, but problem with this RFC escalated enormously and i'm trying to find a way how to make the change eventually implemented with people's approval rather than with votes against

@jmellicker
Copy link

This quote from Rich Harris seems relevant: "Frameworks are not tools for organizing your code, they are tools for organising your mind."

https://youtu.be/AdNJ3fydeao?t=398

@smolinari
Copy link
Contributor

@FredericLatour

That being said, I'm certain it won't be available before the end of the year (unfortunately).

The new API is already working with 2.X as a plugin. https://codesandbox.io/s/todo-example-6d7ep

What's your take on this?

Learn the new API as it is the best solution for TS support. I'm not sure if you realize this, but the class-based API proposal was discontinued in favor of this, the function-based API.

Scott

@FredericLatour
Copy link

@jmellicker Is your comment supposed to have any relation with mine? If so, I'm sorry to say that I can't get it :)

@FredericLatour
Copy link

@smolinari
Following the discussion on the matter (RFC) I thought there would be at some point some kind of plug-in but I did not realize it was already available.

Yes, I could read, that the class based API proposal was discontinued, however it was written (by some core vue contributor) that the current class/decorator based API would still work and therefore made the (apparently wrong) assumption that this was still the way to go for having the best typescript experience.

Thanks for clarifying.

@igasparetto
Copy link

@yyx990803,

The main reason is alignment with standard JavaScript.

I agree with this. I also agree on the Typescript note.
During the years, people tried to bend JavaScript to work OO, but JS is a functional, weakly typed language. Fact. We decided to remove TS from our projects to allow faster development. No regrets as well written tests assure quality.
I am in favour of the Function-based component API - please go for it! Regarding the syntax, people just need to get over their fears and learn the new way.

As for <template> - it can technically be dropped, maybe that could be an RFC.

You could, but please don't drop it. I have experienced first hand, Senior React developers saying templates is one of the advantages of Vue over React. IMHO, you risk killing Vue by removing the <template> tag.
What is the problem with <template>?

@leopiccionia
Copy link

@igasparetto It's not about removing Vue templates, but making the use of <template> tag optional.

So, instead of writing:

<template>
  <div>...</div>
<template>

<script></script>

<style></style>

One simply write:

<div>...</div>

<script></script>

<style></style>

Prior art of frameworks that use template-based SFCs but doesn't require <template> tag include Ractive, Svelte and MarkoJS.


But, as said, it's subject for another RFC.

@FredericLatour
Copy link

@igasparetto Sounds like a weird comment to me.
You praise the new function based component api and at the same explain how you ditched Typescript for the better!! Vue 3 is fully embracing typescript so I am not sure what you mean here. People are adopting typescript for the exact same reasons why you decided to remove it.

Saying that Javascript is a functional language is way exaggerated. Functions as a first class citizen is not enough to define a language as functional.

Typescript is not only about OOP (modern Javascript provides classes construct as well). Functional languages tend to be statically typed. As far as I can see, the recent explorations into functional Javascript rely on Typescript for obvious reasons.
see: fp-ts

I suppose that the React dev you are mentioning were talking about the html based templating language as opposed to mixing javascript within Html and not the 'template' tag by itself.
In that respect, I haven't seen any intention to remove the templating language.
I suppose that some discussions go around the possibility to use render function in a way favor UI composition ...

@michaelscheurer
Copy link

According to @yyx990803

Vue started small, but today it's being used in a very wide range of projects, with varying level of complexity and business domains. Users dealing with different types of projects will run into different needs, some can be easily dealt with using the object-based API, while some cannot. The primary example is

1. Large components (hundreds of lines long) with encapsulates multiple logical tasks
2. The need for sharing the logic of such tasks between multiple components.

I solve the mentioned problems in my applications with separate JS classes and thus additionally use the advantages of OOP.

  1. & 2. are exactly the problems I encountered when my Vue applications grew bigger and more complex over time.

How have I solved 1. and 2. so far? It was a long way.

I have tried mixins and discarded them. I failed with the attempt to pass logic in functions via props to different components. I also tested the class api, but there were too many unresolved problems (Of course it would be best if the class api could have been successfully finished). But I don't know the solutions either.)

After some time I found the solution for 1. and 2. It was relatively simple.

I outsourced all logic, which is used in several components, to own JS classes. Wherever I use a certain pice of logic, I instantiate the class and call the desired methods. I always store a class instance (if instanciated) in the returned data(() => {}) object (by default, the stored class instance in data() is set to null). In the template part I can easily access a class variable {{classInstance.classVariable}}

The advantages of this approach are obvious:

  • I can take advantage of the benefits of OOP: inheritance, maintainability, well structured code, clear responsabilities, performance improvements with v8
  • I can access functions and complex logic at any point in any component: in methods, computed properties, watchers, etc. All with one line of code.

I'm not sure if with the described approach a completely additional function-based API is really necessary for Vuejs. In my opinion, it would be sufficient if certain ceavets were solved in object-based api, for example:

  • full reactivity for non-primitive data types
  • improved (native) typescript support (even better oop support with ts classes)
  • possibility to store a class instance in vuex and manipulate the class variables directly and keep the reactivity anyway.
  • support async computed properties

In my opinion, 1. and 2. can already be solved easily and nicely today. I would rather invest the energy in perfecting and spreading this approach than in implementing, documenting and maintaining another parallel API.

@aztalbot
Copy link

aztalbot commented Jul 9, 2019

@michaelscheurer Do you mind linking to an example of what that looks like? I’ve gone in a similar direction on certain projects, but I never felt it was something I’d want everyone else to follow (the function based API feels more natural to me). It would still be missing lifecycle hook logic, right (other than just calling a method from a lifecycle hook)?

@thenikso
Copy link

This function-based API proposal is AMAZING and it must become a reality!

Time and time again things started to go south as soon as I typed class somewhere. You'll just add a tiny utility method there and before you know it the whole app logic is in some nested class method somewhere. I don't like OOP and I am glad to see Vue moving away from it with this proposal.

I believe that Vue should focus on component composition, avoiding other kind of abstraction/composition like class inheritance or mixins. This proposal goes a long way in that direction: if you want to do something, create a component; everything else is just functions!

With the framework suggesting these best practices I could see how a user would be keen in doing the same for the rest of the app, promoting better code.

What's keeping my current projects together is Vue component isolation and typescript. While OOP, mixins, $something injection are difficult to follow and maintain. The function composition proposed with this RFC, along with the improved ts support would be a game changer.

Among the many use cases I can foresee, here is one where I could useKeyboardShortcuts({ 'cmd+c': copy }) in the setup and have keyboard shortcuts defined locally to my component and nicely handling override and cleanup (pseudocode):

const shortcutsStack = [];
const handleShortcuts = (event: KeyboardEvent) => {
  const shortcutAction = getShortcutFromShortcutsStack(decodeKeyFromEvent(event));
  if (shortcutAction) {
    event.preventDefault();
    event.stopPropagation();
    shortcutAction();
  }
};

/**
 * Add global keyboard shortcut to the component in `setup`
 *     useKeyboardShortcuts({ 'cmd+c': copy });
 */
export function useKeyboardShortcuts(shortcuts: { [keyComb: string]: (() => void) }) {
  onMounted(() => {
    if (shortcutsStack.length === 0) {
      window.addEventListener('keydown', handleShortcuts);
    }
    shortcutsStack.shift(shortcuts);
  });
  onUnmounted(() => {
    removeShortcuts(shortcuts, shortcutsStack);
    if (shortcutsStack.length === 0) {
      window.removeEventListener('keydown', handleShortcuts);
    }
  });
}

I hope a 2.x plugin to play around with this is around the corner because my hands are tingling!

@smolinari
Copy link
Contributor

I hope a 2.x plugin to play around with this is around the corner because my hands are tingling!

It's already made: https://github.com/vuejs/vue-function-api

My example trying to show a little bit of encapsulation (not very good): https://codesandbox.io/s/function-based-api-example-vue-rfc-78nc1

Scott

@shepelevstas
Copy link

This new setup() method looks like just another lifecycle hook (between maybe befroeCreate() and created()) into what is happening with a component behind the scene anyway. Just allows to tap into the process of component creation. More complex than other hooks, with its own logic, but feels like a good thing to have. Powerful and rarely needed. For advanced use. If the rest of the API stays the same as before (and it is so!), then this development should be much appreciated!

@shepelevstas
Copy link

shepelevstas commented Jul 10, 2019

What if, by the time of this RFC, there were no hooks like mounted() or created(), and the RFC was about adding them? Whould anyone argue that it will make API more complex, that there will be more things to learn and more ways to do things? Sounds absurd, right?
If I understood right, the framework is being rewriten from ground up to be lighter and faster, and all the API is preserved and fully compatible with the previous major version. This is realy cool! Itn't it?

@hl037
Copy link

hl037 commented Aug 2, 2019

I really like this feature. It opens a totally new world of possibilities to writes components, I like it. What I appreciate the most is to focus on functionalities more than "objects". The functional way feel the easiest way to achieve this to me : you don't need to learn anything new like TS classes or specific JS stuff : everything is functions. Any beginner can use it without wondering about "magic stuff".

I think the readibility problem is mostly a matter of habbits.

What should be highlighted is that this "syntax" is not a core change : there are still methods, computed props etc. and they still work the same. The addition is only an additional way to declare them, that feels really clever to me

@thenikso
Copy link

thenikso commented Aug 5, 2019

something I've noticed porting a somewhat large project at work to the function api is that it is easy to wrongly use wrapped values like so:

const myValue = value<string | null>(null);

const myValueIsEmtpy = computed(() => {
  if (!myValue) { // OOPS! you should check myValue.value but it is easy to oversight
    return true;
  }
  return false;
});

Even if you use state instead of value, the problem is still there for computed properties.

Am I making sense?

@rbiggs
Copy link

rbiggs commented Aug 6, 2019

Is there still a plan to make available a version of Vue with just the functional parts, meaning, without the class-related code? I'd rather just use the functional API and see no need for loading the class-based code. I see no need for burdening users who chose the functional option with unnecessary bloat.

@yyx990803
Copy link
Member

Closing in favor of #78

@smolinari
Copy link
Contributor

Ok. So now it's reactive and ref. Cool. I like it! 👍

Scott

@emahuni
Copy link

emahuni commented Sep 20, 2019

I don't like the idea of dropping <template> either. Like @gustojs said, some people need it, and also others like myself find freely floating fragments strange.

Code works differently with/without compilation. As a progressive framework, many users may wish/need/have to use it without a build setup, so the compiled version cannot be the default. In comparison, Svelte positions itself as a compiler and can only be used with a build step.

@yyx990803 I fully support Vue's design constraint as a progressive framework. I think it's awesome. Also, this new API is fantastic – and powerful. I plan on using it heavily. I personally don't mind the ergonomics of .value and toBindings.

However, SFC's are compile-time constructs that wouldn't work in the browser the same way. Yes, they mirror components exactly, but they need a build-step. The upside is ergonomics and so we all love SFCs. So, maybe there is a sort or middle-ground. The <script> section of an SFC should always have standard JavaScript – that makes sense and I don't want that to change. But, what if we add a <setup> section with an experimental automatic-bindings mode. Here's what I'm thinking:

<template>
  <div>
    Hello {{ name }}
  </div>
  <div>
    Count is {{ count }}, count * 2 is {{ double }}
    <button @click="increment">+</button>
  </div>
  <div>
    Mouse is at {{ x }}, {{ y }}
  </div>
</template>

<setup experimental-automatic-bindings>
import { computed as c } from 'vue' // no need for `reactive` or `binding`, `binding` is automatically added
import useMouse from 'hooks.js'

// keep it as a function so it is idiomatic JS still and args can be typed
export default function (props, context) {
    const state = { // automatically converted to const state = binding({ name: '' })
       name: ''
    }
    let count = 0 // automatically converted to const count = binding(0)
    const double = c(() => count * 2) // `.value` automatically added to `count`
    const increment = () => { count++ } // `.value` automatically added to `count`
   
   onMounted(async () => {
     state.name = await fetchName() // `state.name` is actually `state.value.name`
   })
    
   // keep return so we can control what is exposed to template and how
   return {
      ...state // actually `toBindings(state.value)`
      count,
      double,
      increment,
      ...useMouse // if this returns reactive state, wrap in toBindings, lint rules govern this function in the other file
    }
}
</setup>

This kind of idea might not work (or implementation could be quite complex), I'm just trying to brainstorm ergonomic solutions. In any case this sort of thing is outside the scope of this RFC. This API is great and we should get our hands on it and then maybe try experimenting with things like this.

But, what I think this sort of approach would accomplish is hiding what some think are the ugly parts of this API, while also making it an explicit choice by the developer to opt for some "auto-magic." It's also somewhat in line with <style scoped> in that it adds something to the compiled output and will work differently if directly copied to a file where that functionality is not enabled. I could foresee IDE support to toggle between hiding/showing all the bindings logic (so you can see the output and maybe then copy to non automatic binding file or copy in logic with bindings and then hide the bindings).

In a file with hooks, maybe this functionality is enabled by some comment like // @vue experimental-automatic-bindings so any useXXX function in that file is transformed. Or, maybe any hook or setup function inside a <setup experimental-automatic-bindings> is transformed so that these must always be in .vue files at all times (in the same way scoped css must be in a vue file).

We don't have to add a <setup> section, we could just have <script experimental-automatic-bindings> and transform any setup and hooks in there in case having setup inside createComponent would be beneficial. Hooks would be non-default exports from those modules. The point is to not make it any sort of default behavior and not drift too far from JS – we're just auto-inserting our binding logic.

The downside is it makes assumptions about how you will write your code here, and specifically it makes assumptions about what you won't try to do. For example, you can't ever have a non-binding primitive in this case (although some static analysis might be able to avoid wrapping values that don't need to be bindings), and you can't rebind any variable, you can only ever mutate .value. It can also be tricky for the compiler to know when to NOT apply toBindings – maybe it can rely on some assumption based on code path or maybe an escape hatch is needed.

Another downside is that when using TypeScript in this mode, if TypeScript knows something is a binding (like a computed or one returned from a hook), then it may error if you try to do certain things that violate that type (double + 1 // type error, double is a Binding, you can't add 1 to it). However, the error will make it clear at that point to just add .value manually or maybe you have some lint auto-fix for it already (and these cases are probably less frequent).

Why did this go unnoticed? I think this is more elegant and is more acceptable to a lot of developers. I love the new function api, just the verbosity I'm not happy with and that sort of solve it the Vue way.

@smolinari
Copy link
Contributor

It's because it would require two trains of thought to use Vue, which I personally believe is something worse than the convenience offered (and I can't speak for Evan, but I do believe from the many comments I've read, it's his thinking too).

I mean really, we are talking about only 3 methods more than what the options API has. reactive() (which could actually be seen as the data() method, ref() and toRefs(). Not verbose at all. Oh. Ok. There is setup() too. But, that's just a gluing/ initializing method. 😃 Easy Peasy! 😁

Scott

@leopiccionia
Copy link

Why did this go unnoticed? I think this is more elegant and is more acceptable to a lot of developers. I love the new function api, just the verbosity I'm not happy with and that sort of solve it the Vue way.

For real, because nobody implemented it, so far. It can probably be done in userland (say, <script lang="experimental-setup">), with knowledge of Babel, vue-loader/vue-component-compiler, etc. internals and free time.

There are some pain points, too, like TypeScript integration, that frameworks that followed a similar path, like Svelte, didn't figure out how to solve yet.

@smolinari
Copy link
Contributor

TypeScript integration

Oh yeah. Good point. 👍

Scott

@aztalbot
Copy link

aztalbot commented Sep 20, 2019

Why did this go unnoticed? I think this is more elegant and is more acceptable to a lot of developers. I love the new function api, just the verbosity I'm not happy with and that sort of solve it the Vue way.

For real, because nobody implemented it, so far. It can probably be done in userland (say, <script lang="experimental-setup">), with knowledge of Babel, vue-loader/vue-component-compiler, etc. internals and free time.

There are some pain points, too, like TypeScript integration, that frameworks that followed a similar path, like Svelte, didn't figure out how to solve yet.

Exactly. I anticipate this will be a community experiment (and probably not the only one) once Vue 3.0 is properly released. This should not be a core team concern. If anything, the fact that such tooling is plausible is a big plus for this API because it gives the community flexibility to choose between different sets of trade-offs (if they really want to).

@emahuni
Copy link

emahuni commented Sep 30, 2019

It's because it would require two trains of thought to use Vue, which I personally believe is something worse than the convenience offered (and I can't speak for Evan, but I do believe from the many comments I've read, it's his thinking too).

I mean really, we are talking about only 3 methods more than what the options API has. reactive() (which could actually be seen as the data() method, ref() and toRefs(). Not verbose at all. Oh. Ok. There is setup() too. But, that's just a gluing/ initializing method. 😃 Easy Peasy! 😁

Scott

😁 I am obviously not talking about the methods only.

From his above example, try to expose the .value and other naked bindings that approach hides and imagine that being all over your code. We are obviously talking about code spanning multiple files etc as the this API promotes. It can get really verbose and hard to read with a few lines. I know these are sort of necessary evils, but hear me out. That unnoticed approach is very simple and approachable; clean; to quote:

"what I think this sort of approach would accomplish is hiding what some think are the ugly parts of this API, while also making it an explicit choice by the developer to opt for some "auto-magic." It's also somewhat in line with <style scoped>"

It's not perfect, yet, but I think this can actually work better both on the code and later IDE support. 🙃

@anoop-ananthan
Copy link

Still, they can't answer a simple question. If Vue forces us to code like React, why should we use Vue? Why not React? React is where the jobs are. Failure of Angular is a reason for the success of React. Vue was loved due to the similarity with Angularjs. Vue is just junk and a waste of time if there is no simplicity and ease of use.

@leopiccionia
Copy link

@anoop-ananthan If you're dissatisfied with Vue 3 and think you're better using React, use React. You might enjoy it. Or you might notice that Vue 3 is still simpler and easier than React and more similar to Vue 2 than React.

But your confrontational tone and disrespectful words aren't likely to change anybody's opinions. Especially commenting on a closed issue...

@vuejs vuejs locked as resolved and limited conversation to collaborators Nov 12, 2019
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