🔥 (#129) Pre-release of Clean Components Toolkit is here! 🎉
Hey all!
I'm excited to announce that today you can buy the pre-release version of the Clean Components Toolkit!
You'll be able to buy it for a discounted price until next Monday at midnight EDT.
After that, it won't be for sale again until it's completed. This is so I can focus on making the toolkit incredible!
To clear up any confusion — this is a new and different product from the previous Clean Components course.
It's a "spiritual successor" — it solves the same problems (and more!) but with totally new content and in a totally new way. That's why it keeps the same "Clean Components" naming.
Check out the Clean Components Toolkit here.
Have a great week, and thanks for your support!
— Michael
🔥 The Right Number of Options for Composables
The Options Object Pattern can be really useful, but it can cause trouble if used too much.
If you have only a couple options for your composable, it may be simpler to leave it out:
import { onMounted, onBeforeUnmount } from 'vue'; export function useEvent(event, handler, target = window) { // No point in having an options object onMounted(() => { target.addEventListener(event, handler); }); onBeforeUnmount(() => { target.removeEventListener(event, handler); }); };
If we need to target a different element, like a button, we can use it like this:
import useEvent from '~/composables/useEvent.js'; // Triggers anytime you click the button useEvent( 'click', () => console.log('You clicked the button!'), buttonElement );
But, if we want to add more options in the future, we break this usage because we've changed the function signature:
before: useEvent(event, handler, target) after: useEvent(event, handler, options)
It's a design choice you'll have to make. Starting with a small options object prevents breaking, but adds a small amount of complexity to your composable.
But what if you have lots of options?
Since all of the options are optional, the sheer number of options is never really a problem when it comes to using a composable. Further, we can organize the options into sub objects if we really felt the need.
With the useEvent
composable we can group all the listenerOptions
into their own object to help organize things:
import { onMounted, onBeforeUnmount } from 'vue'; export function useEvent = (event, handler, options) => { // Default to targeting the window const { target = window, listener, } = options; onMounted(() => { target.addEventListener(event, handler, listener); }); onBeforeUnmount(() => { target.removeEventListener(event, handler, listener); }); };
The usage now becomes this:
import useEvent from '~/composables/useEvent.js'; // Triggers only the first time you click in the window useEvent( 'click', () => console.log('First time clicking the window!'), { listener: { once: true, } } );
🔥 Suspense: More Flexible Loading State
You've probably written lots of components that handle their own loading state.
Either while data is being fetched or while an async component is loaded.
But with the new Suspense
component, we can instead handle that loading state further up the component tree:
<template> <Suspense> <!-- Shown once ChatWindow loads --> <template #default> <ChatWindow /> </template> <!-- Loading state --> <template #fallback> <Spinner color="blue" /> </template> </Suspense> </template>
<script setup> import { defineAsyncComponent } from "vue"; import Spinner from "@/components/Spinner.vue"; // ChatWindow will load asynchronously, but it doesn't // have to have any loading state at all. Or even know // that it's being loaded asynchronously! const ChatWindow = defineAsyncComponent( () => import("@/components/ChatWindow") ); </script>
This also works if the child component returns a Promise from the setup
function:
// async functions always return a Promise async setup() { const { users } = await loadData(); return { users, }; }
Even better is that the async child component can be anywhere as a descendant.
This means you can have a "root" loading state in your app, and any async components anywhere will trigger this loading state:
<!-- App.vue --> <template> <Suspense> <template #default> <AppWithAsyncBehaviour /> </template> <template #fallback> <FullPageLoading /> </template> </Suspense> </template>
🔥 A better way to handle errors (and warnings)
You can provide a custom handler for errors and warnings in Vue:
// Vue 3 const app = createApp(App); app.config.errorHandler = (err) => { alert(err); }; // Vue 2 Vue.config.errorHandler = (err) => { alert(err); };
Bug tracking services like Bugsnag and Rollbar hook into these handlers to log errors, but you can also use them to handle errors more gracefully for a better UX.
For example, instead of the application crashing if an error is unhandled, you can show a full-page error screen and get the user to refresh or try something else.
The warning handler in both versions only works in development.
I created a demo showing how this works. It uses Vue 3, but Vue 2 works nearly the same:
📜 Configuration in Nuxt 3: runtimeConfig vs. appConfig
Nuxt 3 provides powerful configuration options, allowing you to adapt your application to different use cases.
The two key parts of Nuxt 3's configuration system are runtimeConfig and appConfig.
This article will explain the purpose and differences between these two options and show you how to use them.
Check it out here: Configuration in Nuxt 3: runtimeConfig vs. appConfig
💬 Working
"The greatest performance improvement of all is when a system goes from not-working to working." — John Ousterhout
🧠 Spaced-repetition: Reactive SVG components
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.
SVGs can be reactive components, too.
After all, they're HTML elements just like div
, span
, and button
.
Here's an SVG component that has a prop to change it's fill colour:
<template> <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <circle cx="50" cy="50" r="50" :fill="color" /> </svg> </template> <script setup lang="ts"> defineProps<{ color: string }>(); </script>
I'm sure you can build some pretty wild things if you dig into different SVG elements and attributes.
Scoped slots and SVGs? Why not...
Here's a demo if you want to see this example in action.
p.s. I also have four courses: Vue Tips Collection, Mastering Nuxt 3, Reusable Components and Clean Components
评论
发表评论