🔥 (#125) Smelly Composables
Hey there!
My journey with Clean Components continues, but it looks like I'll be launching it in early September!
I've got summer holidays and more work to do yet, so I'll get you a specific date as we get closer.
My focus throughout has been on helping you solve your most important and immediate problems with writing great Vue components, without wasting your time.
The lazy approach to making courses and content is to just throw everything in, and keep adding and adding and hoping it all combines to something great.
I hate this, and I try to avoid this (but I don't always succeed).
It's unfair to you.
This approach puts all of the work on you to figure out what's important and what's not.
Instead, I spent nearly two months just doing research for this course.
Figuring out what your biggest problems and pain points were, and then figuring out solutions that are simple.
Honestly, some of the patterns in here are so simple that I'm worried you'll be left wondering, "What did I even pay for?!?". I have ridiculous visions of everyone rushing to get refunds because it's "not long enough".
So I have to resist the urge to add complexity and length just so it "feels" more complete.
(Yes, I struggle with imposter syndrome. It doesn't really go away...)
But if I can teach you the same thing in less time, isn't that more valuable?
This is something that's been on my mind lately, so I hope you don't mind me getting a bit vulnerable here.
Of course, this newsletter isn't just about the philosophy of course creation!
I've got some more tips for you, as always.
— Michael
Vue Tips Collection
Maybe you just want to stay on top of the latest features, remind yourself of interesting things Vue can do, and get daily inspiration.
Vue Tips Collection is a beautiful book of 115 awesome tips, as well as a daily email to get your creative juices flowing.
🔥 Too Many Props/Options is a Smell
Although the total number of options (and required params) isn't itself a problem, it is an indication that the design isn't quite as good as it could be.
Chances are that your composable (or component) is trying to do more than one thing, and should instead be separated into several composables. The point of composables is that they each do one specific thing really well, and can be composed together to produce more complex functionality.
Imagine that we have a useEvent
composable that looks like this:
import { ref, onMounted, onBeforeUnmount } from 'vue'; export function useEvent(event, handler, interval, options) => { // Default to targeting the window const { target = window, ...listenerOptions } = options; const startInterval = () => { setInterval(handler, interval); }; onMounted(() => { target.addEventListener(event, startInterval, listenerOptions); }); onBeforeUnmount(() => { target.removeEventListener(event, startInterval, listenerOptions); }); };
We'd use it like this. As soon as the button is clicked, we'll log to the console every second:
import useEvent from '~/composables/useEvent.js'; useEvent( 'click', () => console.log('Logging every second'), 1000, { target: buttonElement, } );
We can see that it's doing two separate things:
- Listening for events
- Setting up an interval
Instead of including the interval functionality in our useEvent
composable, it makes more sense to break it out into a second composable:
export function useInterval(callback, options) { const { interval = 1000 } = options; const intervalId = setInterval(callback, interval); return () => clearInterval(intervalId); };
Our useEvent
composable becomes simpler:
import { onMounted, onBeforeUnmount } from 'vue'; export function useEvent(event, handler, options) { // Default to targeting the window const { target = window, ...listenerOptions } = options; onMounted(() => { target.addEventListener(event, handler, listenerOptions); }); onBeforeUnmount(() => { target.removeEventListener(event, handler, listenerOptions); }); };
And now we can compose the two together to get the desired effect:
import useEvent from '~/composables/useEvent.js'; import useInterval from '~/composables/useInterval.js'; useEvent( 'click', () => useInterval( () => console.log('Logging every second') ), { target: buttonElement, } );
When we click on the buttonElement
, we call useInterval
to set up the interval that will log to the console every second.
🔥 Blockquotes
This element is used for quotes outside of the main flow of an article.
Like this quote. Most browsers will indent this automatically, and most websites will add extra styling.
While you can use a div
with some CSS, the <blockquote>
element is the semantically correct way of doing this.
In Markdown, you can use >
to get a blockquote.
🔥 The picture element
The <picture>
element lets us provide many image options for the browser, which will then decide what the best choice is:
<picture> <!-- You can have as many source tags as you want --> <!-- (or none at all!) --> <source srcset="big-image.png" media="(min-width: 1024px)"> <source srcset="bigger-image.png" media="(min-width: 1440px)"> <source srcset="biggest-image.png" media="(min-width: 2048px)"> <!-- One img tag is required to actually display the image --> <!-- and is used as the default choice --> <img src="regular-image.png"> </picture>
You can provide different options based on screen size, resolution, and supported image formats.
The mdn docs have more info on this element.
📜 Prisma with Nuxt 3: Creating the Prisma Schema (2 of 5)
Trying to manage database schemas alongside your Nuxt app types can be a challenge.
But with Prisma, most of these problems go away.
It handles all of the boilerplate and coordination, so you just write one single schema that's used in your database and in your TypeScript app.
In this article I show you how to create your Prisma schema.
Check it out here: Prisma with Nuxt 3: Creating the Prisma Schema (2 of 5)
💬 Creating complexity
"The purpose of software engineering is to control complexity, not to create it." — Unkown
🧠 Spaced-repetition: h and Render Functions
The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨🔬
Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.
When using the render
function instead of templates, you'll be using the h
function a lot:
<script setup> import { h } from 'vue'; const render = () => h('div', {}, 'Hello Wurld'); </script>
It creates a VNode (virtual node), an object that Vue uses internally to track updates and what it should be rendering.
The first argument is either an HTML element name or a component (which can be async if you want):
<script setup> import { h } from 'vue'; import MyComponent from './MyComponent.vue'; const render = () => h(MyComponent, {}, []); </script>
The second argument is a list of props, attributes, and event handlers:
<script setup> import { h } from 'vue'; import MyComponent from './MyComponent.vue'; const render = () => h(MyComponent, { class: 'text-blue-400', title: 'This component is the greatest', onClick() { console.log('Clicked!'); }, }, []); </script>
The third argument is either a string for a text node, an array of children VNodes, or an object for defining slots:
<script setup> import { h } from 'vue'; import MyComponent from './MyComponent.vue'; const render = () => h(MyComponent, {}, [ 'Simple text node', h('span', {}, 'Text inside of a <span> element'), ]); </script>
These render functions are essentially what is happening "under the hood" when Vue compiles your single file components to be run in the browser.
But by writing out the render function yourself, you are no longer constrained by what can be done in a template.
You have the full power of Javascript at your fingertips 🖐
This is just scratching the surface on what render functions and h
can do. Read more about them on the official docs.
p.s. I also have four courses: Vue Tips Collection, Mastering Nuxt 3, Reusable Components and Clean Components
评论
发表评论