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.
-
Summary
The goal of this concept is to use TS
class
for everything that concerns thescript
part 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
class
for 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 class
and make those components inherit from this class.You can use
protected
orprivate
properties and methods for thescript
inner logic to prevent their access from thetemplate
.template
)template
for 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 SetupService
for more complex components that share the same code base (like theLandscapeCard
and theAnimalCard
in 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
script
logic from thetemplate
and 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
template
or that should be updated or not from thetemplate
.getter and setter
Getters can naturally act as
computed
to 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
set
of a computed.Therefore we have a better control on the datas.
Do we want to make additional actions ?
state
instead, 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
.vue
or...SetupService.ts
file 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
props
to work correctly, we have to import them in.vue
file in order to use them in the constructor :Which is pretty ugly.
We have similar problems with
defineEmits
orinject
/provide
that can only be used in a.vue
file.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