Custom hook development
What is a Custom Hook
Custom Hooks are one of the core features of Vue 3's Composition API, allowing developers to extract reusable logic into standalone functions. These functions can be used just like built-in Hooks but are entirely defined by the developer in terms of behavior and return values. Custom Hooks follow the use
prefix naming convention, making the code more readable.
// A simple custom Hook example
import { ref, onMounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function updatePosition(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
return { x, y }
}
Advantages of Custom Hooks
Custom Hooks solve the problem of difficult logic reuse in the traditional Options API. In Vue 2, we typically reused logic through mixins or higher-order components, but these approaches had issues like naming conflicts and unclear origins. Custom Hooks provide a more flexible and explicit logic reuse mechanism through function composition.
Compared to mixins, custom Hooks offer the following advantages:
- Explicit input and output: Parameters and return values are clearly defined
- Avoid naming conflicts: Each Hook call has its own scope
- Better type inference: Improved TypeScript support
- Composability: Easy to combine multiple Hooks
Best Practices for Creating Custom Hooks
Naming Conventions
Custom Hooks should always start with use
, which is a community-established convention. This naming style immediately indicates that it is a Hook function, not a regular utility function.
// Good naming
function useFetch() {}
function useLocalStorage() {}
// Bad naming
function fetchData() {}
function localStorageHelper() {}
Single Responsibility Principle
Each custom Hook should focus on a single specific functionality. If a Hook becomes too complex, consider splitting it into smaller Hooks.
// Good practice: Focuses on window size
function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
// ...Logic to listen for window changes
return { width, height }
}
// Bad practice: Mixes unrelated functionalities
function useWindowAndMouse() {
// Window size logic
// Mouse position logic
// Other unrelated logic
}
Reactive Data Management
Custom Hooks should return reactive data so that components can automatically react to data changes. Typically, ref
or reactive
is used to wrap return values.
function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
return {
count,
increment,
decrement
}
}
Common Custom Hook Examples
Data Fetching Hook
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
async function fetchData() {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
fetchData()
return { data, error, loading, fetchData }
}
Local Storage Hook
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const value = ref(storedValue ? JSON.parse(storedValue) : defaultValue)
watch(value, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return value
}
Debounce Hook
import { ref } from 'vue'
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value)
let timeout
watch(value, (newValue) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
debouncedValue.value = newValue
}, delay)
})
return debouncedValue
}
Combining Multiple Hooks
The true power of custom Hooks lies in their ability to be composed. One Hook can use other Hooks to build more complex logic.
import { useFetch, useDebounce } from './hooks'
export function useSearch() {
const searchQuery = ref('')
const debouncedQuery = useDebounce(searchQuery, 500)
const { data, loading, error } = useFetch(
computed(() => `/api/search?q=${debouncedQuery.value}`)
)
return {
searchQuery,
results: data,
loading,
error
}
}
Testing Custom Hooks
Testing custom Hooks is similar to testing regular Composition API functions. You can use the renderHook
function provided by @vue/test-utils
to test Hooks.
import { renderHook } from '@vue/test-utils'
import { useCounter } from './useCounter'
test('useCounter', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count.value).toBe(0)
result.current.increment()
expect(result.current.count.value).toBe(1)
result.current.decrement()
expect(result.current.count.value).toBe(0)
})
Performance Optimization Considerations
While custom Hooks offer many conveniences, performance issues should also be considered:
- Avoid unnecessary reactive conversions: Not all data needs to be reactive.
- Use computed properties wisely: For derived data, use
computed
instead of recalculating on every render. - Clean up side effects: Clean up event listeners, timers, etc., in
onUnmounted
.
function useEventListener(target, event, callback) {
onMounted(() => {
target.addEventListener(event, callback)
})
onUnmounted(() => {
target.removeEventListener(event, callback)
})
}
Integration with Other Composition API Features
Custom Hooks can seamlessly integrate with other Vue Composition API features, such as provide/inject
, watchEffect
, etc.
// Hook for providing global state
export function useSharedState() {
const state = reactive({
user: null,
theme: 'light'
})
provide('sharedState', state)
return state
}
// Using in components
export function useInjectSharedState() {
const state = inject('sharedState')
if (!state) {
throw new Error('sharedState not provided')
}
return state
}
Practical Application Scenarios in Projects
- Form handling: Create reusable form validation logic.
- Permission control: Encapsulate permission-checking logic.
- Animation effects: Reuse animation-related logic.
- Third-party library integration: Encapsulate the usage logic of third-party libraries.
- Business logic: Extract domain-specific business logic.
// Form validation Hook example
export function useFormValidation() {
const errors = reactive({})
const touched = reactive({})
function validateField(field, value, rules) {
// Validation logic...
}
function resetValidation() {
Object.keys(errors).forEach(key => {
errors[key] = ''
})
Object.keys(touched).forEach(key => {
touched[key] = false
})
}
return {
errors,
touched,
validateField,
resetValidation
}
}
Type-Safe Custom Hooks
Using TypeScript can add type support to custom Hooks, improving code reliability and development experience.
import { ref, Ref } from 'vue'
interface MousePosition {
x: Ref<number>
y: Ref<number>
}
export function useMousePosition(): MousePosition {
const x = ref(0)
const y = ref(0)
// ...Implementation logic
return { x, y }
}
Popular Custom Hook Libraries in the Community
- VueUse: Provides a large number of practical custom Hooks.
- vue-composition-toolkit: Focuses on collections of business logic Hooks.
- vue-hooks: A React-style Hook implementation.
The Hook implementations in these libraries serve as excellent references for learning custom Hook development.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn