🔥 (#145) Reassigning and Reactivity
Hey!
I hope you're enjoying your holidays 🎄
This is a pre-scheduled newsletter, since I'm spending this week with family.
Last week I shared a preview of the upcoming second edition of Vue Tips Collection. You can download it here if you missed it:
Vue Tips Collection 2 (Preview).pdf
It's going to be released January 9th.
Anyway, enjoy these tips and I'll see you in the new year!
— Michael
🔥 Reassigning and Reactivity
Reactive values cannot be reassigned how you might expect:
const myReactiveArray = reactive([1, 2, 3]); watchEffect(() => console.log(myReactiveArray)); // "[1, 2, 3]" myReactiveArray = [4, 5, 6]; // The watcher never fires // We've replaced it with an entirely new, non-reactive object
This is because the reference to the previous object is overwritten by the reference to the new object. We don't keep that reference around anywhere.
Vue developers for years have been tripped up by how reactivity works when reassigning values, especially with objects and arrays:
// You got a new array, awesome! // ...but does it properly update your app? myReactiveArray = [1, 2, 3];
This was a big issue with Vue 2 because of how the reactivity system worked. Vue 3 has mostly solved this, but we're still dealing with this issue when it comes to reactive
versus ref
.
The proxy-based reactivity system only works when we access properties on an object.
I'm going to repeat that because it's such an important piece of the reactivity puzzle.
Reassigning values will not trigger the reactivity system. You must modify a property on an existing object.
This also applies to refs, but this is made a little easier because of the standard .value
property that each ref
has:
const myReactiveArray = ref([1, 2, 3]); watchEffect(() => console.log(myReactiveArray.value)); // "[1, 2, 3]" myReactiveArray.value = [4, 5, 6]; // "[4, 5, 6]"
🔥 How to watch a slot for changes
Sometimes we need to know when the content inside of a slot has changed:
<!-- Too bad this event doesn't exist --> <slot @change="update" />
Unfortunately, Vue has no built-in way for us to detect this.
However, there is a very clean way of doing this using a mutation observer:
export default { mounted() { // Call `update` when something changes const observer = new MutationObserver(this.update); // Watch this component for changes observer.observe(this.$el, { childList: true subtree: true }); } };
But don't forget you'll also need to clean up the observer!
🔥 Restrict a prop to a list of types
Using the validator
option in a prop definition you can restrict a prop to a specific set of values:
export default { name: 'Image', props: { src: { type: String, }, style: { type: String, validator: s => ['square', 'rounded'].includes(s) } } };
This validator function takes in a prop and returns either true
or false
— if the prop is valid or not.
I often use this when I need more options than a boolean
will allow but still want to restrict what can be set.
Button types or alert types (info, success, danger, warning) are some of the most common uses — at least in what I work on. Colours, too, are a really great use for this.
But there are many more!
📜 Data Fetching Basics in Nuxt 3
Nuxt offers a set of powerful built-in tools for handling data fetching.
It provides composable functions that make it easy to fetch data and automatically handle server-side rendering, client-side hydration, and error handling. This enables you to write clean and efficient code, ensuring an optimal user experience.
In this article we'll examine the different methods Nuxt gives us for data fetching.
Check it out here: Data Fetching Basics in Nuxt 3
💬 Gall's Law
"A complex system that works is invariably found to have evolved from a simple system that worked. The inverse proposition also appears to be true: A complex system designed from scratch never works and cannot be made to work. You have to start over, beginning with a working simple system." — John Gall
🧠 Spaced-repetition: More Tool Previews
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 (more) previews of some of the tools included in the Clean Components Toolkit.
Preserve Object Pattern
Passing an entire object to a component instead of individual props simplifies components and future-proofs them.
However, this approach can create dependencies on the object's structure, so it's less suitable for generic components.
<!-- Using the whole object --> <template> <CustomerDisplay :customer="activeCustomer" /> </template> <!-- CustomerDisplay.vue --> <template> <div> <p>Name: {{ customer.name }}</p> <p>Age: {{ customer.age }}</p> <p>Address: {{ customer.address }}</p> </div> </template>
Controller Components
Controller Components in Vue bridge the gap between UI (Humble Components) and business logic (composables).
They manage the state and interactions, orchestrating the overall behavior of the application.
<!-- TaskController.vue --> <script setup> import useTasks from './composables/useTasks'; // Composables contain the business logic const { tasks, addTask, removeTask } = useTasks(); </script> <template> <!-- Humble Components provide the UI --> <TaskInput @add-task="addTask" /> <TaskList :tasks="tasks" @remove-task="removeTask" /> </template>
Strategy Pattern
The Strategy Pattern is ideal for handling complex conditional logic in Vue applications.
It allows dynamic switching between different components based on runtime conditions, which improves readability and flexibility.
<template> <component :is="currentComponent" /> </template> <script setup> import { computed } from 'vue'; import ComponentOne from './ComponentOne.vue'; import ComponentTwo from './ComponentTwo.vue'; import ComponentThree from './ComponentThree.vue'; const props = defineProps({ conditionType: String, }); const currentComponent = computed(() => { switch (props.conditionType) { case 'one': return ComponentOne; case 'two': return ComponentTwo; case 'three': return ComponentThree; default: return DefaultComponent; } }); </script>
Hidden Components
The Hidden Components Pattern involves splitting a complex component into smaller, more focused ones based on how it's used.
If different sets of properties are used together exclusively, it indicates potential for component division.
<!-- Before Refactoring --> <template> <!-- Really a "Chart" component --> <DataDisplay :chart-data="data" :chart-options="chartOptions" /> <!-- Actually a "Table" component --> <DataDisplay :table-data="data" :table-settings="tableSettings" /> </template> <!-- After Refactoring --> <template> <Chart :data="data" :options="chartOptions" /> <table :data="data" :settings="tableSettings" /> </template>
Insider Trading
The Insider Trading pattern solves the issue of overly coupled parent-child components in Vue.
We simplify by inlining child components into their parent when necessary.
This process can lead to a more coherent and less fragmented component structure.
<!-- ParentComponent.vue --> <template> <div> <!-- This component uses everything from the parent. So what purpose is it serving? --> <ChildComponent :user-name="userName" :email-address="emailAddress" :phone-number="phoneNumber" @user-update="(val) => $emit('user-update', val)" @email-update="(val) => $emit('email-update', val)" @phone-update="(val) => $emit('phone-update', val)" /> </div> </template> <script setup> defineProps({ userName: String, emailAddress: String, phoneNumber: String, }); defineEmits(['user-update', 'email-update', 'phone-update']); </script>
Long Components
What's "too long" when it comes to components?
It's when it becomes too hard to understand.
The Long Components Principle encourages the creation of self-documenting, clearly named components, improving the overall code quality and understanding.
<!-- Before: A lengthy and complex component --> <template> <div> <!-- Lots of HTML and logic --> </div> </template> <!-- After: Breaking down into smaller components where the name tells you what the code does. --> <template> <ComponentPartOne /> <ComponentPartTwo /> </template>
p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection, Mastering Nuxt 3, and Reusable Components
评论
发表评论