Vue 3 Hooks Replace Mixins for Cleaner Component Logic Reuse
Vue3 Hooks Practical Usage Analysis
Foreword
In Vue.js application development, as business logic becomes more complex, components often need to handle repetitive logic (such as data fetching, form validation, event listening, global state management). The traditional approach is to write this logic directly inside the component, leading to code redundancy, difficulty in maintenance, and violation of the "separation of concerns" principle. The Composition API introduced in Vue 3 provides a more elegant solution for logic reuse: Vue Hooks, which can separate logic such as state, methods, and lifecycle hooks, making component code clearer and easier to maintain.
1. Core Concepts and Functions
1.1 What are Vue Hooks
Hooks are not a concept unique to Vue; they originally came from a new functional programming mindset introduced after React 16. In React, Hooks provide a series of functions starting with use, allowing developers to complete almost all component development work in functional components, including lifecycle, state management, and logic reuse.
In Vue 3, Hooks combine with Vue's reactivity system to become a powerful mechanism for encapsulating and reusing state logic. They allow developers to split Vue component logic into multiple independent, reusable functions, which are called Hooks. Each Hook encapsulates a piece of code with state logic that can be shared and reused across multiple components.
1.2 Core Value
- Logic Reuse: Encapsulate common logic (like data requests, form validation, mouse tracking) into independent functions to avoid code duplication. Compared to Vue 2's Mixins, Hooks avoid naming conflicts and unclear sources of origin.
- Clearer Code Organization: Organize code by "logical concern" rather than "option type". For example, place state, methods, and lifecycle hooks related to one feature together, instead of scattering them across
data,methods,mounted. - Better TypeScript Support: Function-based composition naturally suits type inference, providing precise type hints without extra configuration.
- Simplify Complex Components: Large components can be split into multiple small Hooks, each responsible for a single duty, improving maintainability.
2. Basic Hooks Classification
Vue 3 has a built-in series of basic Hooks for implementing core functionalities like reactivity and lifecycle management.
2.1 Reactive State
Used to create and manage reactive data, as shown in the table below.
| Hook | Function |
|---|---|
| ref | Creates reactive data for primitive types (or objects) (accessed via .value) |
| reactive | Creates reactive data for object types (access properties directly) |
| toRef | Converts a property of a reactive object into a ref (maintains reactive connection) |
| toRefs | Converts all properties of a reactive object into ref objects (for destructuring) |
| watch | Watches reactive data changes (explicitly specifies the source) |
| watchEffect | Automatically tracks reactive dependencies and executes when dependencies change (no need to explicitly specify the source) |
<script setup lang="ts">
import { ref, reactive, toRef, toRefs } from 'vue'
const count = ref(0)
const user = reactive({ name: 'Zhang San', age: 20 })
const nameRef = toRef(user, 'name')
const { name, age } = toRefs(user) // Still reactive after destructuring
</script>
2.2 Computed and Watchers
| Hook | Function |
|---|---|
| computed | Creates a computed property (depends on reactive data, automatically caches results) |
| watch | Watches reactive data changes (explicitly specifies the source) |
| watchEffect | Automatically tracks reactive dependencies and executes when dependencies change (no need to explicitly specify the source) |
<script setup lang="ts">
import { ref, computed, watchEffect } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
watchEffect(() => {
console.log(`Current count value: ${count.value}`)
})
</script>
2.3 Lifecycle Hooks
In the Composition API, they exist as functions and must be used within setup or <script setup>.
| Hook | Corresponding Vue 2 Lifecycle | Function |
|---|---|---|
| onMounted | mounted | Executes after the component is mounted |
| onUpdated | updated | Executes after the component is updated |
| onUnmounted | unmounted | Executes after the component is unmounted |
| onBeforeMount | beforeMount | Executes before the component is mounted |
| onBeforeUpdate | beforeUpdate | Executes before the component is updated |
| onBeforeUnmount | beforeDestroy | Executes before the component is unmounted |
| onErrorCaptured | Executes when catching an error from a child component | |
| onActivated | Executes when a cached component (KeepAlive) is activated | |
| onDeactivated | Executes when a cached component is deactivated |
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('Component mounted')
// Example: Initialize timers, event listeners
})
onUnmounted(() => {
console.log('Component unmounted')
// Example: Clear timers, event listeners
})
</script>
2.4 Other Basic Hooks
| Hook | Function |
|---|---|
| shallowRef | Creates "shallow reactive" data (only top-level is reactive) |
| shallowReactive | Creates "shallow reactive" objects (only top-level properties are reactive) |
| readonly | Creates read-only reactive data (modifications are forbidden) |
<script setup lang="ts">
import { reactive, toRef, toRefs, shallowRef, readonly } from 'vue'
const user = reactive({ name: 'Zhang San', age: 20 })
const nameRef = toRef(user, 'name')
const { name, age } = toRefs(user) // Still reactive after destructuring
const obj = shallowRef({ a: 1 }) // Modifying obj.value.a will not trigger an update; reassigning obj.value will
const readOnlyUser = readonly(user) // Modifying readOnlyUser.name will throw an error
</script>
3. Basic Usage of Hooks
In Vue 3, custom Hook functions are an important means of improving code reusability and organization. State management, business logic, etc., can be encapsulated in custom Hooks. These functions are created using the Composition API, can contain any combination logic, and can be shared among multiple components. By encapsulating reusable logic code, developers can build and maintain large applications more efficiently.
3.1 Creating a Hook Function
Generally, we create a hooks folder under the src folder to uniformly store the hook code used in the program. Let's write a simple hook with an example, as shown below.
A custom Hook is a regular JavaScript function, usually named with a use prefix. It can use functions provided by the Composition API to manage state and side effects. After creating the hooks folder under the src folder, create a useCounter.js file inside it with the following code:
import { ref } from 'vue'
// Define a Hook named useCounter
export function useCounter(initVal = 0) {
// Define a reactive state
const count = ref(initVal);
// Define a method to increase the state
const increment = () => {
count.value++;
};
// Define a method to decrease the state
const decrement = () => {
count.value--;
};
// Define a method to reset the state
const reset = () => {
count.value = initVal;
};
// Return state and methods for external use
return { count, increment, decrement, reset };
}
3.2 Using a Custom Hook
In the example above, we defined a Hook named useCounter, which encapsulates a reactive state and three methods for operating on the state. Using custom Hooks is very simple: just import and call them in the component's <script setup>, destructure the required properties and methods, and they can be used directly in the template:
<script setup>
import { useCounter } from '@/hooks/useCounter'
// Destructure and use directly, no need to redefine variables
const { count, increment, reset } = useCounter(10)
</script>
<template>
<div>Current Count: {{ count }}</div>
<button @click="increment">+</button>
<button @click="reset">Reset</button>
</template>
In the example above, the useCounter Hook is called in the component's setup function to get the state and methods, which are then exposed to the template for use. This way, we have successfully achieved state encapsulation and reuse.
4. Best Practices for Hooks
4.1 Design Principles
Custom Hooks are a pattern for handling component logic, allowing us to reuse state logic without copying and pasting code between components. Custom hooks are simple JavaScript functions, but the following should be observed when using them:
- Naming Convention: Use the
useprefix to name Hook functions to distinguish them from ordinary JavaScript functions, e.g.,useCounter,useFetch. - Single Responsibility Principle: Each Hook function should be responsible for only one feature, e.g.,
useLocalStoragehandles local storage operations. This helps maintain code readability and maintainability, avoiding mixing multiple unrelated logics into the same Hook. - Avoid Side Effects: Try to handle side effects (like DOM operations, network requests) within lifecycle hooks rather than directly inside the Hook function.
- Parameterization: To make Hook functions more general, pass different configurations or initial values through parameters.
- Composition and Nesting: Hook functions can call and compose with each other to build more complex functionality, but care must be taken to avoid circular dependencies and infinite recursion.
- Documentation and Comments: Add appropriate documentation and comments to Hook functions explaining their purpose, parameters, and return values. This will help other developers understand and use the Hook functions.
5. Summary
As a new mechanism for encapsulating and reusing state logic introduced in Vue 3, Hooks provide us with a more flexible and efficient way to organize and reuse code. By learning and mastering the use of Hooks, we can better cope with the demands of complex Vue project development, improving code maintainability and readability. At the same time, Hooks also provide us with a new functional programming mindset, helping us better understand and grasp the essence of Vue development.