Introducing SetupService concept for SFC #481
Replies: 3 comments 3 replies
-
|
it is worthy of our reference to angular or microsoft desktop winform, can develop tools to add 2 files for an SFC at sample time. For the setupservice, give the setupservice.ts file the same name of .vue file. ie.sfc1.vue.ts(or sfc1.vue.setup.ts), sfc1.vue |
Beta Was this translation helpful? Give feedback.
-
|
This whole solution feels like personal preference imo, and instead of accessing shared functions three levels deep, you could oftentimes just use functions for shared logic, and composables already make it so much nicer to structure and package reactive business logic. It's a matter of taste, unless it's for memory optimizations. Class objects can be used whenever you like inside setup scripts, like your code already proves. The issue I see here though, is that you are not using native TypeScript types for props. So a non-standard types, such as string literals, enums (if you like those), records, etc, cannot be conveniently defined, unless you use the const props = defineProps<{
title: string,
}>()Perhaps that can be passed into classes as well, but right now I don't think the defineProps macro works outside of Another thing that just feels so much more intuitive is when related code is actually immediately available in the same file: Like why should the "formattedTitle" value be written inside another file in a another directory, instead of in the same LandscapeCard.vue component?
I feel like if you are coming from a strict backend-background (C# or something), you tend to feel uncomfortable when things are not inside a class object, but unlike OOP languages, JavaScript have other options, and functions make more sense due to tree shaking, etc. Just wanted to provide my perspective, which is from someone who use class objects as tools in programming, but not as a core part. |
Beta Was this translation helpful? Give feedback.
-
|
This isn't the first time class-based components are discussed, it'd be interesting to highlight how yours differs from previous ones, or join existing discussions: #276, #623, #311 for example. Your description of getter vs computeds is slightly wrong. This is minor and class-based components are def. worth discussing, but if you go on with a real RFC you may want to correct this: What you describe is basically replacing a local, reusable function that derives a value with a getter.
The comment "This allows us to avoid a watcher" is misleading. |
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
The goal of this concept is to use TS
classfor everything that concerns thescriptpart of the component, so we can benefit from native features such as inheritance, protected or private properties and methods, getter and setter, etc...Basic example
Here is the actual way to make it work:
The best way could be something like:
Motivation
When a project becomes quite complex, notably on the script part, with a lot of composables, services, ajax requests, user permissions, router logic, etc... It can be harder and harder to maintain and to avoid code duplication or small mistakes.
Of course this is the case for every project, but maybe there's a way to reduce this effect.
The advantage of using TS
classfor the script part is that you can use every native features.If you have multiple components that share a small or massive code base, you can use an
abstract classand make those components inherit from this class.You can use
protectedorprivateproperties and methods for thescriptinner logic to prevent their access from thetemplate.template)templatefor a better DX (screenshot below)Yellows are public.
Reds are protected or private.
Blues are public from inherited class.
Detailed design
Explaining the detailed design here would be equivalent to explain a entire project.
So here is a repo with a simple documented project to easily explore this concept:
https://github.com/jbmaisner/setup-service-demo
However here are some key points:
Inheritance
Every component's SetupService inherits at least from the BaseSetupService.
This obviously means that if you need something to be accessible in all your components, the BaseSetupService is the right place.
It can be used for user's datas, global methods, whatever you need.
Of course you can add intermediate
abstract SetupServicefor more complex components that share the same code base (like theLandscapeCardand theAnimalCardin the demo project).protected and private
As explained above, these native features enhance the DX and can help to prevent side effects, especially when working in a team.
You can easily separate the inner
scriptlogic from thetemplateand avoid triggering a method that was not designed to be triggered from thetemplate.We also have a better control on the datas we want to be accessible in the
templateor that should be updated or not from thetemplate.getter and setter
Getters can naturally act as
computedto perform logic before returning the result.It is also reactive when returning part of a
reactive()constant.Getters are naturally readonly, to prevent unwanted changes from the
template.Concerning the setter, it can also act as the
setof a computed.Therefore we have a better control on the datas.
Do we want to make additional actions ?
stateinstead, so the setter will not be triggered.(Example in the demo repo)
Real usage feedback
Currently we use this concept at my work, on small and pretty big projects.
We use it in a new opensource UI framework that should be released later this year.
And I also use it on personal projects.
It fits very well in each of these cases.
It really enhances the DX with all the features of TS and allows us to write better and more robust and maintainable code.
DX
We really care about the DX, and it can be a pain to create two files each time we want to create a new component.
That's why we created a script, plugged into a VSCode extension that we develop internally, to simplify this process.
You can see a usage example in the GIF below.
Basically we enter the name of the component, the name of the folder where it should be created, and then the SetupService it should extend from.
The component name is then automatically formatted, the two files are created based on a template, and simultaneously opened in VSCode.
Finally we have a little blue button in the window top right which allows us to quickly open the
.vueor...SetupService.tsfile related to the one we are editing.Of course we would opensource this helper to be accessible to all the community.
Actual downsides
We think this concept is very easy to use, however there are currently some little downsides.
In order to make the
propsto work correctly, we have to import them in.vuefile in order to use them in the constructor :Which is pretty ugly.
We have similar problems with
defineEmitsorinject/providethat can only be used in a.vuefile.Also, it's currently not working very seamlessly with the Vue.js Devtool, but we already started to work on it, and I'm pretty sure we can find a way to make it nice and clean.
Finally
It's quite complex to explain everything here, I recommend to test this concept with the demo repo.
I can easily imagine there may be some caveats to implement this.
And I know it's a radically different philosophy, but we really think it enhances the DX.
After some reflexions I decided to give this concept a chance to be discussed here.
If you're still reading this, thanks for your interest.
Beta Was this translation helpful? Give feedback.
All reactions