🔥 (#144) I've got a Christmas gift for you
Hey!
Welcome to edition 12^2 of the newsletter.
Christmas is in a few days, so what better way to celebrate than with a gift?
I've been working on the second edition of Vue Tips Collection, and I want to give you the first two chapters for free, 30 tips in total:
Vue Tips Collection 2 (Preview).pdf
The full book has 118 tips, including 47 brand new ones.
All tips in the book have been re-written for Vue 2, Vue 3, and the Composition API and Options API.
Oh, and there's a limited number of hardcovers as well.
But I'll tell you more about the book and everything else on January 9th.
For now, enjoy the preview, and enjoy the holidays!
And enjoy your tips, as always.
— Michael
🔥 Don't Override Component CSS
It can be really tempting to quickly modify a component's CSS from outside the component. If all you need is a slight modification, it seems harmless — but it's not.
Let's say you have a normally blue button, but you need it to be green in this specific case. You can override the background colour from the parent component like this:
<template> <Button class="green">Make this button green</Button> </template> <style> .green.button { background: green; } </style>
This does work, but it's very fragile and prone to breaking.
What if the class name changes?
What if the HTML of the component is modified?
Anyone making changes to the button component will have no idea that this component's background colour is overridden. They won't know to update this component too.
Instead, we can just extend the functionality of the button component. That way, we keep all of the code that modifies the button inside the button component.
Here, we can add a is-green
prop to the button component:
<template> <Button is-green>Make this button green</Button> </template> <style> /* No extra styles needed! */ </style>
Adding to the component itself makes it easier for anyone else who might need this button to be green in the future!
I've created a demo showing the original component and the new one with the added prop: https://codesandbox.io/s/wizardly-aryabhata-kn37d?file=/src/components/Button.vue
🔥 My favourite git commands
Here are a few of my favourite git commands (is it weird to have favourite git commands?):
I'm often jumping back and forth between different branches, and typing is annoying:
# Checkout the previous branch git checkout -
Sometimes I add new files, then realize I don't actually need them:
# Remove any files not tracked by git git clean
Or I completely broke everything and need to start over:
# Undo all changes to git and the working directory, # going back to the most recent commit git reset --hard
Github takes all of the commits on your PR branch and combines them into a single one when you merge. But sometimes you want to merge a branch, and you aren't ready for a PR just yet:
# Squash all commits from a branch into one commit git merge --squash <branch> git commit
🔥 Refresh a Page in Vue
If you need to force a reload your entire page using Vue, all you need is some Javascript:
window.location.reload();
But this is a code smell — you should almost never need to use this method.
Instead, a better solution might be one of these:
- Create a method to reset and initialize state instead of relying on
onMounted
hooks or the top-level ofsetup
. You can also create aninitialize
action for Pinia. - Make sure your important state is reactive. This tends to fix a lot of common issues.
- Key-changing — by changing just the
key
attribute on a specific component, you can force just one component to reload instead of your entire app. Still a bit of a hack, but it gets the job done.
📜 Server Routes in Nuxt 3
Server routes are a powerful feature of Nuxt 3 that allow you to create custom endpoints for your app.
In this article I look at the different types of server routes and how to use them.
Check it out here: Server Routes in Nuxt 3
💬 Write Programs
"The only way to learn a new programming language is by writing programs in it." — Dennis Ritchie
🧠 Spaced-repetition: Clean Components Toolkit Preview
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.
Here are some tips from the tools in the Clean Components Toolkit.
Thin Composables
Thin composables introduce an additional layer of abstraction, separating the reactivity management from the core business logic. Here we use plain JavaScript or TypeScript for business logic, represented as pure functions, with a thin layer of reactivity on top.
import { ref, watch } from 'vue'; import { convertToFahrenheit } from './temperatureConversion'; export function useTemperatureConverter(celsiusRef: Ref<number>) { const fahrenheit = ref(0); watch(celsiusRef, (newCelsius) => { // Actual logic is contained within a pure function fahrenheit.value = convertToFahrenheit(newCelsius); }); return { fahrenheit }; }
Humble Components Pattern
Humble Components are designed for simplicity, focusing on presentation and user input, keeping business logic elsewhere. Following the "Props down, events up" principle, these components ensure clear, predictable data flow, making them easy to reuse, test, and maintain.
<template> <div class="max-w-sm rounded overflow-hidden shadow-lg"> <img class="w-full" :src="userData.image" alt="User Image" /> <div class="px-6 py-4"> <div class="font-bold text-xl mb-2"> {{ userData.name }} </div> <p class="text-gray-700 text-base"> {{ userData.bio }} </p> </div> <div class="px-6 pt-4 pb-2"> <button @click="emitEditProfile" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" > Edit Profile </button> </div> </div> </template> <script setup> defineProps({ userData: Object, }); const emitEditProfile = () => { emit('edit-profile'); }; </script>
Extract Conditional
To simplify templates with multiple conditional branches, we extract each branch's content into separate components. This improves readability and maintainability of the code.
<!-- Before --> <template> <div v-if="condition"> <!-- Lots of code here for the true condition --> </div> <div v-else> <!-- Lots of other code for the false condition --> </div> </template> <!-- After --> <template> <TrueConditionComponent v-if="condition" /> <FalseConditionComponent v-else /> </template>
Extract Composable
Extracting logic into composables, even for single-use cases. Composables simplify components, making them easier to understand and maintain. They also facilitate adding related methods and state, such as undo and redo features. This helps us keep logic separate from UI.
import { ref, watch } from 'vue'; export function useExampleLogic(initialValue: number) { const count = ref(initialValue); const increment = () => { count.value++; }; const decrement = () => { count.value--; }; watch(count, (newValue, oldValue) => { console.log(`Count changed from ${oldValue} to ${newValue}`); }); return { count, increment, decrement }; }
<template> <div class="flex flex-col items-center justify-center"> <button @click="decrement" class="bg-blue-500 text-white p-2 rounded" > Decrement </button> <p class="text-lg my-4">Count: {{ count }}</p> <button @click="increment" class="bg-green-500 text-white p-2 rounded" > Increment </button> </div> </template> <script setup lang="ts"> import { useExampleLogic } from './useExampleLogic'; const { count, increment, decrement } = useExampleLogic(0); </script>
List Component Pattern
Large lists in components can lead to cluttered and unwieldy templates. The solution is to abstract the v-for loop logic into a child component. This simplifies the parent and encapsulates the iteration logic in a dedicated list component, keeping things nice and tidy.
<!-- Before: Direct v-for in the parent component --> <template> <div v-for="item in list" :key="item.id"> <!-- Lots of code specific to each item --> </div> </template> <!-- After: Abstracting v-for into a child component --> <template> <NewComponentList :list="list" /> </template>
Data Store Pattern
In growing applications, prop drilling and event frothing increase complexity, as state and events are passed through many component layers. The Data Store Pattern solves this by creating a global state singleton, exposing parts of this state, and including methods to modify it.
<script setup lang="ts"> import useUserSettings from '~/composables/useUserState'; const { theme, changeTheme } = useUserSettings(); </script>
// useUserState.ts import { reactive, toRefs, readonly } from 'vue'; import { themes } from './utils'; const state = reactive({ darkMode: false, sidebarCollapsed: false, theme: 'nord', }); export default () => { const { darkMode, sidebarCollapsed, theme } = toRefs(state); const changeTheme = (newTheme) => { if (themes.includes(newTheme)) { state.theme = newTheme; } }; return { darkMode, sidebarCollapsed, theme: readonly(theme), changeTheme, }; };
p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection, Mastering Nuxt 3, and Reusable Components
评论
发表评论