DejaVue

Michael is back from his paternity leave! And he brought three Design Patterns in Vue.js which he and Alex discuss in the 10th DejaVue Episode! Learn more about what Design Patterns are and what the three shown patterns do, when they should be used and which downsides they could bring.

Enjoy the episode!

Chapters
  • (00:00) - Welcome Back Michael!
  • (01:42) - What are Design Patterns?
  • (04:20) - Design Pattern One - Thin Composables
  • (11:07) - Design Pattern Two - Data Store
  • (19:22) - Design Pattern Three - Preserve Whole Object
  • (27:33) - Wrapping up

Links and Resources

Creators & Guests

Host
Alexander Lichter
Web Engineering Consultant • Founder • Nuxt team • Speaker
Host
Michael Thiessen
Full-time Vue educator
Editor
Niki Brandner
Sound Engineer

What is DejaVue?

Welcome to DejaVue, the Vue podcast you didn't know you needed until now! Join Michael Thiessen and Alexander Lichter on a thrilling journey through the world of Vue and Nuxt.

Get ready for weekly episodes packed with insights, updates, and deep dives into everything Vue-related. From component libraries to best practices, and beyond, they've got you covered.

Speaker 1:

Everybody. Welcome back to Deja View.

Speaker 2:

It's your favorite View podcast. You just don't know it yet.

Speaker 1:

Indeed. And you hear maybe you see the person, the voice. He is back. Welcome back, Michael Thiesen. How are you doing, Michael?

Speaker 2:

I'm doing great. Thanks. It's, it's great to be

Speaker 1:

back. Lovely. Lovely to have you here after your paternity leave and break. It's it's so nice to have another episode and the 2 of us straight away again. Lovely.

Speaker 2:

Yeah. Yeah. For those of you who don't know who I am, I am Michael Thiessen. I do full time view education and courses and newsletters and all, all up on Twitter or X or whatever you like to call it these days. Yeah.

Speaker 2:

I'm here with Alex as usual.

Speaker 1:

Hey. Yes. Exactly. And I I am also doing content in the Vue and Nuxt ecosystem. I have a YouTube channel.

Speaker 1:

I'm part of the Nuxt team as well. And, by day, I'm a web engineering consultant helping companies with Vue and Nuxt JS. And, of course, all links are in the show notes. So if you have questions, if they wanna see what you're doing, if you wanna subscribe and follow also the podcast, it's all in there.

Speaker 2:

And, you may have seen me a couple episodes ago in the VJ as Amsterdam recap episode. So we kind of got our chronological episode, like ordering out of, out of whack, but we recorded that one right after the conference. So I was, you know, still around then. And so if you're wondering what was going on, that's what happened there.

Speaker 1:

Exactly. Exactly. I mean, now that, the replays are out, the videos are there, we thought, yeah, let's release it together with the talks and, go a little bit out of order. But I think that was pretty fitting. And, yeah, here we are.

Speaker 2:

Today, we are going to be talking about design patterns specifically in Vue. So design patterns, probably everyone has a slightly different definition of what they would consider a pattern, but, design patterns are basically different ways of solving problems, common problems that come up again and again in different applications. So for example, how do we get state from one part of your application to another part of your app? Everyone has to deal with this in different ways and it's different for every application. So we can't have like, you know, we can't create a library to solve this problem.

Speaker 2:

We can't solve it specifically with code, but we can have these ideas, these common approaches or ways of thinking about this problem that can help us find that good solution over and over again without having to, you know, constantly reinvent the wheel time and time again.

Speaker 1:

So it's basically some kind of approach or, like, mental model on how to solve specific problems when you don't, like, use an algorithm for, like, how, as I said, how do I do state with a to b, or how do I set up my composable in a nice way? Like, how to make sure the business logic somehow is filled into the components and reactivity is there and, and all that.

Speaker 2:

Yeah. A mental model is a perfect way of describing it. Like an algorithm for your brain, basically something like that. Yeah.

Speaker 1:

Sweet. Okay. And I mean, you know, quite some of these design patterns in Vue. Js. I mean, you made a whole course about it.

Speaker 2:

Yeah. I spent a lot of time putting together and thinking about these different patterns and, and not just patterns as they apply to software development, but how we can use them specifically in Vue and how they end up working in our view apps and how they apply to components and composables and all that, that sort of stuff. And so a couple months ago I released the clean components toolkit, which is a set of 18 different patterns and ideas like this. And probably around now, maybe soonish I will be, putting out an update for, for that with a few more tools in there as well.

Speaker 1:

Sweet. I mean, as someone who bought the course back in the back in the day, it's, like, November. I'm looking forward to the update.

Speaker 2:

Yeah. Some extra stuff for you.

Speaker 1:

Yes. And for everyone else as well. So if you're interested, of course, check the the show notes. There is a link straight away there, and the design patterns we'll discuss in a second. They are also mentioned in the course with, like, a near real world example and, like, how to refactor things, like, the whole the whole bundle, let's say.

Speaker 2:

Yeah. So let's just get right into it. So the first one that I wanna talk to you about is thin composables.

Speaker 1:

Interesting name. Okay.

Speaker 2:

Yeah. So hopefully the name gives you a bit of, an idea of what might, what it might be. The, the basic idea here is that instead of putting everything into 1 big composable and having all of your logic and all of the code in one spot like that, we actually create your composable as a thin layer of reactivity. And we take all of the other business logic that's in that composable. And we, we push it out into a plain JavaScript or TypeScript file.

Speaker 2:

And so in that way we separate it into these 2 pea pieces. We have your plain JavaScript that has your business logic and can be ported around between different frameworks or different applications. You can use it on your back end. You can do whatever you want with it. And then we have this composable that uses those functions, those business logic pieces, and then just applies your watchers, your computed refs, whatever else you need to do to make it reactive and and fit within your Vue app.

Speaker 1:

Interesting. So why like, I think it it seems like a reasonable pattern so far. Is MKB is in logic one function, composable using that in a way? But where when would you say would should people apply this pattern? Like, all the time they use composables, or is there, like, a a good way to distinguish between when should the composable be thin versus when it's okay to have, like, a thick composable, I guess.

Speaker 2:

Yeah. So the, when when to use the pattern is always the hardest part because it's usually fairly easy to describe what it does And then when to use it, it always requires some sort of judgment and, and experience. And so I'm not gonna say, oh, it depends because that's a, a useless answer. And I hate giving that answer. But it does depend.

Speaker 2:

And, but generally, generally I would say, as your composable. Grow. You might want to, to do this just to like organize it better and split things up. But also I think it makes it easier to reason about your, your code. If you've got, got some complex logic going on, you can think about the business logic separately without having to worry about, okay, how does this reactive piece update?

Speaker 2:

Why isn't it updating? What's going on here? I've like, why isn't this ref value changing when, when I think it should be, you can think about those things separately and you don't have to be working through your business logic problems at the same time.

Speaker 1:

I see. So you would say if the composable also grows, then it's a good indicator to say, like, yeah, the the bigger it gets, the more likely it's good to extract something, especially if it's not about reactivity into, like, an own business logic focused function or file and so on. Yeah. Sweet. And I can also like, now that you that you say that, I can also imagine another benefit because that straightaway comes to my mind, the testing part.

Speaker 1:

Right? Like, if I imagine testing, like, Vue component specific things works, but I think the people who ever written a unit test and every now and then do so. Right? People not maybe not in touch that much, they might be more comfortable testing a typical, like, JS or TS function and not necessarily composable, even though it's more difficult, I mean, depending on what it's doing. But to have your business logic in the right tracks than only having that encapsulated and being able to test and say, okay, logic is fine.

Speaker 1:

Maybe it's just an integration problem that can also help. Right?

Speaker 2:

Exactly. The, the testing piece makes it easier as well. Because again, you're only thinking about how the, the JavaScript or TypeScript part works. You're not having to think about the the reactivity and all of that at the same time. And you can just focus on just regular plain old JavaScript in in your tests, and it it can make it simpler.

Speaker 1:

Very nice. Now another thing I was wondering, which also came to my mind is, would you then collocate the composable and the the business function, or would you, like, split them up in different files?

Speaker 2:

Yeah. I think I'd probably co locate it, unless you're planning to use that business logic elsewhere as well. So I've had at least one use case where I had a business logic, like a method, a utility method that I wanted to use both on the server side and the client side. And it wasn't really, like, something that made sense to to have as a API endpoint that both pieces could use. And so I really needed that method in both places.

Speaker 2:

And so then if you have a a use case like that, then it makes sense to put that maybe somewhere else in, like, a shared folder or however you wanna structure that.

Speaker 1:

Okay. Yeah. It makes sense. And as you said, like, if you need it in different projects, then you can, like, build an MPM package out of that or Yeah. A library or, like, a Nuxt layer if using Nuxt and so on and so on.

Speaker 2:

And if you have to migrate to to React or whatever, you can Absolutely not. You can, do that fairly easily because you're not tied into that reactivity. But yeah, hopefully, you don't, you don't need to do that, but it's always good to to plan for that worst case for, you know, reducing that extra overhead in the future.

Speaker 1:

Yeah. Also, like, as you said, being independent of the framework, at least, or to separate nicely also helps with with the mental model once again to say that we don't have to focus on Vue's reactivity system. I mean, same applies for other frameworks, too. If there are people saying, oh, yeah, I use Angular, Swell, or React, I don't need to focus on the parts that the framework does. It's really it's plain and simple.

Speaker 1:

It's a function I understand. It can be not only tested, but also read without any knowledge of the framework. Yeah. Yeah. Really good point.

Speaker 1:

I I like that. I also have done that quite often in the past without, like, having a name for it. It's more like, okay. This feels big. I want to test it.

Speaker 1:

I want to reuse it. And especially even because you can't use composables from every context. Right? So, like, say, in the form submit, you don't want to, like, call a composable. It feels, at some point, at least for me, like, natural to have an option to call that in a view or Nuxt context or without that.

Speaker 1:

Yeah. Very nice. And I think that that was one of the the easier patterns, but one definitely worth learning and applying. So, yeah, what's what's on to the next one?

Speaker 2:

So the next one we're gonna talk about is the data store pattern. So we'll circle back to this in in just a moment, but this is very familiar to those of you who have used Pina before, because this is exactly how pina works. But we can actually use this pattern without using pina. So the idea here is that if you need to share state across your app, you don't want to just rely on passing props around because then you end up basically having your props or all your, all your state really high up this component tree. And probably a lot of it ends up in your root app component, and you're passing it through props down down the component tree, and it gets to be a huge mess.

Speaker 2:

So instead we have VX, which we had in pure 2, and now we are using, Pina for lots of state management stuff. And the idea behind those is basically we are gonna have some global state. And so this is what the data store pattern solves for us is that we stick some state inside of a composable, and then we can use that composable in any component that we need to have access to that state. And so we can also co locate, different business logic within that so we can have state and then the, the different logic that uses that state all in the same composable wrapped up neatly. And, yeah, it makes a big difference.

Speaker 1:

I see. But so as you said, the data store, first thing, of course, like terms like pinio, you acts like ring the bell. You say it's pretty similar to how pina itself, like, works from from a mental model here once again. But what would be the difference between, let's say, pina and, like, a self built data store composable?

Speaker 2:

So basically, Pina has a bunch of extra stuff added on to make it easier to use and, some like developer experience improvements. So the biggest one for me would be the protection against, cross. What's, what's the word? I always forget the exact name of it, but basically you can get your state polluted on the server. If you're using server side rendering with Knox, for example, then if you have global state on your server while that state is global across the whole server.

Speaker 2:

And so if you have multiple requests come in at the same time, you can end up leaking state between those two requests. And that's not a good thing. And so pina has specific protection against that, which is really nice. If you're using Nuxt, you get that with use state. That's the point of that is to help solve that problem among other things.

Speaker 2:

And, Pina also has some integration with dev tools and, some other things like that. So if you took this data store pattern and then you said, oh, well, I'm using this on SSR, so I need to add this protection. And, oh, I wanna add dev tools integration. So I'm gonna do this. And, oh, maybe I wanna make it easier to test this, so I'm gonna add this.

Speaker 2:

Eventually, if you kept going, you would just end up reinventing pina. And and so that's the, you have both ends of the spectrum. You have pina is like the, the full powered version and the data store is just, you can write a simple composable in 5 lines of code. And, if you need extra flexibility, you don't like the way pinion works for some reason you can re implement your own, like light version of it, in this way. Interesting.

Speaker 1:

Yeah. As you mentioned, like this was also one of things that comes to my mind, like, this cross requests, state pollution, something something like that along the lines with, it's a classic problem. As you said, useState and Nuxt would serve solve that, and pinia also solves that. But then if you say, okay, you can use it as a lightweight version, you can opt in for pinia to avoid reinventing the wheel, what would you say is the benefit of having the datastore pattern yourself and not just straightaway, say run and, and implement Pinia instead?

Speaker 2:

My first answer would be, I find it useful to know how it works and to have this better mental model of what Pena is actually doing. Even if you're just gonna stick with Pena and you're not gonna do your own thing. I think it is really helpful, to, to know, okay, this is actually what it's doing. It's encapsulating state in this way, and it's doing all this other stuff. Oh, I understand how it works.

Speaker 2:

It's the same reason why, we sometimes like to, deconstruct things or rebuild our own version of view, or let me, let me try and see how this reactivity system works with proxies and like, you know, trying to build themselves, build up that stuff so we can just understand it more deeply. The second reason is that there are some instances where you don't want to use pina. And so this is a more difficult question to answer. I was actually, I talked to Eduardo about this at view conf Toronto a few months ago. And he was saying, there are some places where you don't want to use Pena.

Speaker 2:

And I kept pushing him for, an example, but he could not come up with 1, like, on the spot. It was it's a it's a tricky problem. And so, I don't have, like, a hard, use case for that, but, yeah. And I've seen him say on Twitter before, like, when should you use pina while try and avoid using pina? And I think that's one thing I like about him is he's not, vain and trying to be like, use my thing, use the thing I made.

Speaker 2:

He's just like, you know what? If it helps you use it. But like, if there's a simpler way, use the simpler way, you know?

Speaker 1:

Yeah, exactly. It's not about like ideology saying, okay, I built this. Everybody has to use it. I, I generally, I think this is a thing that's common to Vue ecosystem as well. Like, hey, there's a cool tool.

Speaker 1:

If you have a problem that this tool solves, use it. In other ways, don't because it's complexity, it's additional JavaScript, and so on and so on. And I like that. I also think, like, Pinia, I've built quite some software where pinia comes in handy, but I also like the data store pattern when when building composables in Nuxt, especially if you have if you have a simple, like, I know use user with, like, okay, some authentication, some things because of the fine grain control for use state, you know, what is hydrating, what is happening when, how to set things, and also then with using use fetch or use, async data, which you shouldn't use in a pinna store. It's something that, is sometimes really confusing people, but it's more about documentation.

Speaker 1:

So we'll get onto that at some point. It's also why I I commonly say, like, okay. Yeah. If you need pinna, go for it. But it's it's also a good way to just pull yourself and then see how global state works, We shouldn't do it enough that way of SSR and so on.

Speaker 1:

So, totally agree.

Speaker 2:

One specific thing that I remember Eduardo mentioning to me is that like the difference is that Pina will return a reactive object. Whereas with this data store pattern, you can have it returned just a plain object if you want, or, you know, anything, because it's your own function, you can return, whatever you want. And so that's one specific way that you could change things up and have it be more flexible.

Speaker 1:

Yeah. Fair point. And then you you you are the master of your own, like, fine grained reactivity. You don't have like, everything is reactive. And you can say, okay, here we go.

Speaker 1:

Some refs all nice. I don't have to call store to refs. I know the user is a read only ref, and it's fine. And read only may be another good, point where where in pinion, you shouldn't declare a state as read only because it's, like, dev tools will break and so on so on well with fewer things, you can do that. Sweet.

Speaker 1:

So 2 two patterns down the line. Also, 2, I already knew, which is great, but, I like I also like naming. Like, thin composables, like, hey. Everybody thinks of, like, a tiny, tiny thing. Also, data store, it's something everybody, like, has at least some some image or, like, some image in in your mind.

Speaker 1:

And the next one we'll talk about is called preserve whole object. I'm curious.

Speaker 2:

Yeah. So I I get this question a bunch. I've wondered this myself many times too. And so this is why I had to investigate it. And the question is, is it better to pass the entire object as a prop or to pass the properties of an object into a component?

Speaker 2:

So if you had a user object and the user has a name, an email, and, profile image, Should you pass that whole user object as one thing into your component or should you split it up and pass those things individually pass in a name, prop, pass in an email prop and have that profile image prop. And the short answer is that we want to tend towards preserving that whole object. It's right there in the name. So that's kind of helpful to remember. And the reason, for this is that typically with code as it evolves and as you work on it and you add more features and you refactor it and, and all of that components and code in general tends to use more of the, of the things that you've passed into it and not less.

Speaker 2:

So if you're passing in some parts of the user object into a component, Probably in the future. You'll want more from that user object, not less. And so if you pass in these props individually, you end up needing to add in more and more props as time goes on. But if you have passed in that user object as a whole thing straight away, while that that component, you can add features that use more and more of that user object without actually modifying anything because they're already passing that whole user object straight in.

Speaker 1:

I see. So it's really about more like, okay, you have to touch less lines to to widen, functionality of a component if you say, like, okay, I have these 10 100 pops I can use. Interesting. I see. That that makes sense.

Speaker 1:

But one one question that came to my mind, and that's more of an edge case, but I think it's still important to mention is Mhmm. What about components that's, like, say, you pass a computed prop or something in there, and it's like a, let's say, a big nested object, then the component will always, like, rerender when anything in that object will change, like, also even deep low because it's nested reactivity. And let's say you have I don't know. One example is always it's always ecommerce. They have, like, a big, like, a big shopping cart with, like, 300 items and super complex UI.

Speaker 1:

Do you think in this case, it would make sense to only pass the things needed to avoid that the components have to rerender and it might come to, like, performance issues. I think the whole, like, topic is a bit about, like, prop stability, I think it's called in the few docs or so.

Speaker 2:

Prop stability. I haven't heard that term. But yeah, I think, well, in terms of performance, I think it makes sense to, if you need to have a optimization there, then certainly go for it. And I think it sort of speaks to like this broader idea of, we don't wanna be so focused on these patterns and, oh, I have to use this this way. I've always gotta use this.

Speaker 2:

It's it's usually just like, this is a helpful thing that can point you in the right direction, but you know, as with all advice, sometimes it's useful. Sometimes the exact opposite is useful. It just depends on your situation. And so I would say definitely in in in this case, if you have a bunch of performance issues with passing in the whole object, then that's what, you should change it so that you are only passing in the props that you need. One other, thing that comes to mind is I remember working on some code where we received this giant object from the back end and we passed the whole object in.

Speaker 2:

Well, actually we didn't even pass the whole object in for, for some reason we ended up, using a V bind of this object onto a component. But the problem is this object had like 30 different props, was super deeply nested, but only like 4 or 5 of those were actual props on the component. So then when you went into the dev tools and you looked at, at what was being rendered to the Dom, all of these extra properties were being added as attributes on this element. I was just like cluttering up the page and adding extra page weight. And it was just like, wait, what's going on here?

Speaker 2:

So not exactly the same issue, but like a similar one where it's like, okay, maybe we need to be like careful that we're not just like, if we're just passing around the whole thing, we need to be careful that we're not including things we don't want to include.

Speaker 1:

Yeah. That's that's a good point. Also like a bit of a, the con or the danger of using v bind because if you add more to an object, you don't necessarily notice that these are just, like, attributes because TypeScript won't complain because maybe you want to pass these things as attributes to to the HTML, element attack. Why not? So, yeah, that's that's a good point.

Speaker 1:

And with regards to probability, I'll just check the the view documentation. They have, like, a little part of performance, of course, where they say, okay. Let's say you have a list item, component, and you run like, you render this for each, item in a list. So do you have v 4? And then you have 2 things to pass in, and once the item should get an ID, and the second thing it should get is the information if it's active or not.

Speaker 1:

And the bad example would be to say, okay, we pass in the, the information if it's active or not. We pass in the ID of the active item, while the more performant one is to move that calculation to a parent and say, okay, we pass in true or false depending on the ID. So, like, if the ID equals active. So instead of Right. Like, changing all the properties in the list as all the all the components to rerender because the prop would change if you click on another item, It will only change for the one was selected before and the new one.

Speaker 2:

Right. So it's being be thoughtful about what props actually get passed. You're not constantly changing them and getting Exactly. Performance hit. Yeah.

Speaker 1:

Of of course, it's I think a lot of things as usual, if you have, like, 10 items, it probably doesn't matter much. But as soon as you say, okay, there is something reacting very slowly or there is like an actual problem on low end devices. It's, it's worth to benchmark and to figure out what to do and, that then find a solution as with all the performance problems, let's say.

Speaker 2:

Yeah. And even with design patterns like this, if you have a really small app, you can get away with not thinking about the structure of your app, really, because you don't need organization, but as it gets bigger and bigger, you have to be more and more thoughtful and intentional. And like that, that organization and how you structure things and what patterns you use and what you don't use really makes a big difference as the complexity and size of things grows.

Speaker 1:

Also like consistency. If you say at some point you want to onboard something else, same idea.

Speaker 2:

Yeah. Saying, oh, we always pass the whole object. That's like, you know, maybe there's no late rule for it. Exactly. But having that as like a, this is

Speaker 1:

our As a guideline.

Speaker 2:

Yeah. Yeah. And like you catch it in your your code reviews and whatever else. Like, that's yeah. I think that makes a big difference too.

Speaker 1:

Wonderful. Yeah. And I I also think it's it's always a trade off as we have it everywhere versus, like, code readability versus performance. Like, that's why we don't write, like, for loops because we we don't care about these 1 millisecond or a 0 point something of, like, 1,000,000 operations. It doesn't matter.

Speaker 1:

And if it does, then you will write it for loop, but commonly it doesn't.

Speaker 2:

So we've covered 3 different patterns this episode, and that's all we've got time for today. If you like this and want to hear more patterns, just let us know. Comment on wherever you're watching this or listening to this on on X. You can let us know. And if you want to dive in deeper and see actual examples of this with like refactoring steps and step by step examples worked out on how this applies to real world code.

Speaker 2:

I have a course on this called clean components, toolkit. As I mentioned earlier, it's, by the time this episode drops, there should be an update coming out, or shortly thereafter. So we'll be going from 18 to 21 different patterns. So three patterns that we 3 patterns that we showed today will be are included in that and, a bunch more.

Speaker 1:

So 80 more to learn and 3 more to dive deeper into with real examples. Very nice. Michael, like, I'm I'm asking you now, do you have do you have a discount for our listeners for the the loyal deja vu community here? Can you give us something?

Speaker 2:

You know, I I'll see what I can do. If I do have discount, I'll put it in the, the show notes in the description and we'll, we'll get to that.

Speaker 1:

Perfect. Then check out right there down below for the link with a discount. And, yeah, then that's that's it for today. I think a very concise episode about some design patterns of Vue. Js that you should know.

Speaker 1:

Also, as Michael said, let us know if you wanna hear more, if you want to apply them, if you've applied them already and just didn't know the names of that, and which other things you wanna see or hear about. Yeah. That's it for today. Thanks a lot for listening. Check out the other episodes if that's the first one you've listened or the second one or the third.

Speaker 1:

They're way more.

Speaker 2:

And if you enjoyed these patterns, make sure to share with a coworker or friend. Maybe they'll learn from it as well.

Speaker 1:

Exactly. And then we'll you will see or listen or hear us next week again, and stay tuned for further episodes.