🔥 (#140) 🎉 Announcing: Clean Components Toolkit! 🎉
Hey there!
Clean Components Toolkit is now LIVE!
You can grab it here.
I'm giving you a massive 33% discount during the launch, but only until December 1st.
In addition, the first 20 people to use the code NEWSLETTER20 will get an extra $20 off.
Clean Components Toolkit is filled with tools — techniques, principles, and patterns for building incredible Vue components.
The tools included in this toolkit will help you solve some of the most painful problems you face every day as a Vue developer:
- Master the Art of Component Splitting & Combining — knowing when (and when not) to break up components is crucial
- Seamlessly Manage State Across Components — especially as your component tree grows in complexity
- Organize & Reuse Logic in Components with Ease — you'll learn the three types of components you need to keep your logic organized
...and more!
Here's a screenshot of the complete list of tools (more details on the landing page):
The toolkit has the following structure:
- An overview of the pattern, covering what it is and going over edge cases and common pitfalls
- In-depth refactoring examples to show you how the tool is applied
- A quiz so that you can make sure you've understood the material
- A video where I go through the quiz, explaining the answers and providing some more commentary on the tool
I am so excited to release this to you today — I know you'll love it.
Buy the Clean Components Toolkit now.
— Michael
🔥 More Tool Previews
Last week I shared a preview of some of the tools, but I wanted to give you more of a preview.
Here's an overview of a few more tools that you'll get access to 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>
📜 The Hidden Components Pattern
This article goes through an entire refactoring example, showing how you'd apply the Hidden Components Pattern in a real-world situation.
It's a really useful pattern, helpful for finding components that are "lurking" in other components.
Check it out here: The Hidden Components Pattern
🧠 Spaced-repetition:
I shared these previews last week, but I wanted to make sure you had a chance to see them.
If these are interesting, you can check out the toolkit here.
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, }; };
Here's the link again so you can check it out.
评论
发表评论