Vue 3组合式API实战:构建可复用的自定义Hook
Vue 3的组合式API彻底改变了我们组织组件逻辑的方式。与Options API相比,组合式API提供了更好的逻辑复用、类型支持和代码组织能力。今天,我们将深入探讨如何创建和使用自定义Hook来构建可复用的逻辑。
什么是自定义Hook?
自定义Hook是一个使用Vue组合式API的函数,它可以封装和复用有状态的逻辑。与React Hook类似,Vue的自定义Hook允许我们在多个组件之间共享逻辑。
关键概念:自定义Hook通常以”use”开头命名,这是社区约定俗成的规范。
基础示例:useMousePosition Hook
让我们从一个简单的例子开始:跟踪鼠标位置。
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
const updatePosition = (event) => {
x.value = event.clientX;
y.value = event.clientY;
};
onMounted(() => {
window.addEventListener('mousemove', updatePosition);
});
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition);
});
return { x, y };
}
// 在组件中使用
// Component.vue
import { useMousePosition } from './useMousePosition';
export default {
setup() {
const { x, y } = useMousePosition();
return { x, y };
}
};
高级Hook:useFetch数据获取
让我们创建一个更复杂的Hook来处理数据获取:
// useFetch.js
import { ref, computed, watch } from 'vue';
export function useFetch(url, options = {}) {
const data = ref(null);
const error = ref(null);
const loading = ref(false);
const fetchData = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data.value = await response.json();
} catch (err) {
error.value = err.message;
console.error('Fetch error:', err);
} finally {
loading.value = false;
}
};
// 自动执行初次获取
if (options.immediate !== false) {
fetchData();
}
// 监听URL变化
watch(() => url, (newUrl) => {
if (newUrl) {
fetchData();
}
});
return {
data,
error,
loading,
fetchData,
// 计算属性
hasData: computed(() => data.value !== null),
hasError: computed(() => error.value !== null)
};
}
// 使用示例
// UserComponent.vue
import { useFetch } from './useFetch';
export default {
props: ['userId'],
setup(props) {
const {
data: user,
loading,
error,
fetchData
} = useFetch(
() => `https://api.example.com/users/${props.userId}`,
{ immediate: true }
);
return { user, loading, error, fetchData };
}
};
组合Hook:构建复杂逻辑
自定义Hook的真正威力在于它们可以相互组合:
// useLocalStorage.js
import { ref, watch } from 'vue';
export function useLocalStorage(key, defaultValue) {
const data = ref(
JSON.parse(localStorage.getItem(key)) || defaultValue
);
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
}, { deep: true });
return data;
}
// useAuth.js - 组合多个Hook
import { useFetch } from './useFetch';
import { useLocalStorage } from './useLocalStorage';
export function useAuth() {
const token = useLocalStorage('auth_token', null);
const user = useLocalStorage('auth_user', null);
const {
data: loginResult,
loading: loginLoading,
error: loginError,
fetchData: login
} = useFetch('https://api.example.com/login', {
method: 'POST',
immediate: false
});
// 监听登录结果
watch(loginResult, (result) => {
if (result && result.token) {
token.value = result.token;
user.value = result.user;
}
});
const logout = () => {
token.value = null;
user.value = null;
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
};
const isAuthenticated = computed(() => !!token.value);
return {
token,
user,
login: (credentials) => login({
body: JSON.stringify(credentials)
}),
loginLoading,
loginError,
logout,
isAuthenticated
};
}
TypeScript支持
使用TypeScript可以极大地提高自定义Hook的类型安全性:
// useCounter.ts
import { ref, computed } from 'vue';
interface UseCounterOptions {
initialValue?: number;
min?: number;
max?: number;
}
interface UseCounterReturn {
count: Ref;
increment: () => void;
decrement: () => void;
reset: () => void;
isMin: ComputedRef;
isMax: ComputedRef;
}
export function useCounter(options: UseCounterOptions = {}): UseCounterReturn {
const {
initialValue = 0,
min = -Infinity,
max = Infinity
} = options;
const count = ref(initialValue);
const increment = () => {
if (count.value < max) {
count.value++;
}
};
const decrement = () => {
if (count.value > min) {
count.value--;
}
};
const reset = () => {
count.value = initialValue;
};
const isMin = computed(() => count.value <= min);
const isMax = computed(() => count.value >= max);
return {
count,
increment,
decrement,
reset,
isMin,
isMax
};
}
最佳实践
最佳实践:
- 保持Hook的单一职责原则
- 使用TypeScript提供类型安全
- 为Hook提供清晰的文档和示例
- 考虑错误处理和边界情况
- 测试你的Hook
测试自定义Hook
// useCounter.test.js
import { renderHook, act } from '@testing-library/vue';
import { useCounter } from './useCounter';
describe('useCounter', () => {
test('should initialize with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count.value).toBe(0);
});
test('should increment count', () => {
const { result } = renderHook(() => useCounter({ initialValue: 5 }));
act(() => {
result.current.increment();
});
expect(result.current.count.value).toBe(6);
});
test('should respect max limit', () => {
const { result } = renderHook(() =>
useCounter({ initialValue: 9, max: 10 })
);
act(() => {
result.current.increment(); // 10
result.current.increment(); // 应该还是10
});
expect(result.current.count.value).toBe(10);
expect(result.current.isMax.value).toBe(true);
});
});
总结
自定义Hook是Vue 3组合式API的核心特性之一,它们提供了:
- 逻辑复用:在多个组件间共享业务逻辑
- 代码组织:将相关逻辑组织在一起
- 可测试性:独立的逻辑单元更容易测试
- 类型安全:与TypeScript完美集成
通过创建和使用自定义Hook,你可以构建更加模块化、可维护和可测试的Vue应用。开始尝试将你的组件逻辑提取到自定义Hook中,你会发现代码质量得到显著提升。
在下一篇文章中,我们将探讨如何使用Vue 3的新特性构建高性能的动画效果。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END




暂无评论内容