自定义 Hooks 就是将可重用的逻辑抽象到一个函数中,这样你可以在不同的组件中重复使用这些逻辑,而不必重复编写相同的代码。
使用场景
1. 处理异步数据
当你需要在多个组件中处理异步数据时,可以创建一个自定义 Hook 来封装相关的逻辑。
<!-- useAsyncData.ts -->
import { ref, onMounted, onUnmounted } from 'vue'; export function useAsyncData(fetchData: () => Promise<any>) { const data = ref(null); const error = ref(null); const isLoading = ref(false); const fetch = async () => { try { isLoading.value = true; data.value = await fetchData(); } catch (e) { error.value = e; } finally { isLoading.value = false; } }; onMounted(fetch); onUnmounted(() => { // 清理逻辑(如果需要) }); return { data, error, isLoading, fetch };
}
在组件中使用这个自定义 Hook 来处理异步数据:
<!-- MyComponent.vue -->
<template> <div> <div v-if="isLoading">Loading...</div> <div v-else-if="error">Error: {{ error }}</div> <div v-else>{{ data }}</div> <button @click="fetch">Fetch Data</button> </div>
</template> <script setup lang="ts">
import { useAsyncData } from './useAsyncData'; const fetchData = async () => { // 模拟异步数据获取 return new Promise((resolve) => { setTimeout(() => resolve('Async data!'), 2000); });
}; const { data, error, isLoading, fetch } = useAsyncData(fetchData);
</script>
2. 管理表单状态
当处理表单输入时,你可能需要跟踪多个字段的状态,并处理验证逻辑。自定义 Hook 可以帮助你封装这些逻辑。
<!-- useForm.ts -->
import { ref, watch, computed } from 'vue'; export function useForm(initialValues: any) { const form = ref(initialValues); const errors = ref({}); const validate = () => { // 验证逻辑 errors.value = {}; // 清除之前的错误 // ... 验证字段并设置 errors.value }; const reset = () => { form.value = initialValues; errors.value = {}; }; watch(form, () => { validate(); }, { deep: true }); return { form, errors, validate, reset };
}
在表单组件中使用这个自定义 Hook:
<!-- FormComponent.vue -->
<template> <form @submit.prevent="handleSubmit"> <input v-model="form.name" type="text" placeholder="Name" /> <span v-if="errors.name">{{ errors.name }}</span> <button type="submit">Submit</button> </form>
</template> <script setup lang="ts">
import { useForm } from './useForm'; const initialFormValues = { name: '' };
const { form, errors, validate, reset } = useForm(initialFormValues); const handleSubmit = () => { if (validate()) { // 表单验证通过,执行提交逻辑 }
};
</script>
3. 处理窗口大小变化
如果你需要在多个组件中响应窗口大小的变化,可以创建一个自定义 Hook 来监听 resize
事件。
<!-- useWindowSize.ts -->
import {ref,onMounted,onUnmounted
} from 'vue';export function useWindowSize() {const size = ref({width: window.innerWidth,height: window.innerHeight});const updateSize = () => {size.value = {width: window.innerWidth,height: window.innerHeight};};onMounted(() => {window.addEventListener('resize', updateSize);updateSize(); // 初始化尺寸});onUnmounted(() => {window.removeEventListener('resize', updateSize);});return size;
}
在响应窗口大小变化的组件中使用这个自定义 Hook:
<!-- ResponsiveComponent.vue -->
<template> <div> Window width: {{ windowSize.width }}px <br> Window height: {{ windowSize.height }}px </div>
</template> <script setup lang="ts">
import { useWindowSize } from './useWindowSize'; const windowSize = useWindowSize();
</script>
4. 管理定时器
当你需要在组件中设置和管理定时器时,自定义 Hook 可以帮助你封装定时器的创建和清理逻辑。
<!-- useInterval.ts -->
import { ref, onMounted, onUnmounted } from 'vue'; export function useInterval(callback: () => void, delay: number | null) { const intervalId = ref<number | null>(null); const start = () => { if (delay !== null) { intervalId.value = window.setInterval(callback, delay); } }; const stop = () => { if (intervalId.value !== null) { window.clearInterval(intervalId.value); intervalId.value = null; } }; onMounted(start); onUnmounted(stop); return { start, stop };
}
在需要定时器的组件中使用这个自定义 Hook:
<!-- TimerComponent.vue -->
<template> <div> <button @click="toggleTimer">Toggle Timer</button> </div>
</template> <script setup lang="ts">
import { useInterval } from './useInterval'; const toggleTimer = ref(false); const { start, stop } = useInterval(() => { console.log('Timer ticked!');
}, toggleTimer.value ? 1000 : null); const handleToggle = () => { toggleTimer.value = !toggleTimer.value; if (toggleTimer.value) { start(); } else { stop(); }
};
</script>
5. 管理异步状态
在 Vue 组件中处理异步操作(如 API 请求)时,可以使用自定义 Hook 来管理状态。这有助于封装异步逻辑,使得组件代码更加简洁和易于理解。
<!-- useAsyncData.ts -->
import { ref, onMounted, onUnmounted } from 'vue'; export function useAsyncData<T>(fetchData: () => Promise<T>): { data: T | null; error: Error | null; loading: boolean } { const data = ref<T | null>(null); const error = ref<Error | null>(null); const loading = ref(true); const fetch = async () => { try { data.value = await fetchData(); error.value = null; } catch (e) { error.value = e; } finally { loading.value = false; } }; onMounted(fetch); // 如果需要取消请求,可以在这里实现逻辑,例如使用 AbortController // onUnmounted(() => { // // 取消请求 // }); return { data, error, loading };
}
在组件中使用这个自定义 Hook 来处理异步数据:
<!-- AsyncComponent.vue -->
<template> <div> <div v-if="loading">Loading...</div> <div v-else-if="error">Error: {{ error.message }}</div> <div v-else>Data loaded: {{ data }}</div> </div>
</template> <script setup lang="ts">
import { useAsyncData } from './useAsyncData'; const fetchData = async () => { // 模拟异步请求 await new Promise(resolve => setTimeout(resolve, 2000)); return 'Data from API';
}; const { data, error, loading } = useAsyncData(fetchData);
</script>
6. 封装表单处理逻辑
对于常见的表单处理逻辑,如验证、提交等,可以创建一个自定义 Hook 来封装这些功能。
<!-- useForm.ts -->
import { ref, reactive, watch } from 'vue'; export function useForm<T>(initialValues: T) { const form = reactive<T>(initialValues); const errors = reactive<Record<keyof T, string | null>>({} as any); const validateField = (fieldName: keyof T, validator: (value: any) => string | null) => { const error = validator(form[fieldName]); errors[fieldName] = error; return !error; }; const validate = () => { let isValid = true; for (const fieldName of Object.keys(form) as (keyof T)[]) { const validator = (form[fieldName] as any).validator; if (validator) { isValid = validateField(fieldName, validator) && isValid; } } return isValid; }; const reset = () => { Object.keys(form).forEach(key => { form[key as keyof T] = initialValues[key as keyof T]; }); Object.keys(errors).forEach(key => { errors[key as keyof T] = null; }); }; return { form, errors, validate, reset };
}
在表单组件中使用这个自定义 Hook:
<!-- FormComponent.vue -->
<template> <form @submit.prevent="handleSubmit"> <input v-model="form.name" type="text" placeholder="Name" /> <span v-if="errors.name">{{ errors.name }}</span> <button type="submit">Submit</button> </form>
</template> <script setup lang="ts">
import { useForm } from './useForm'; const initialValues = { name: '', nameValidator: (value: string) => (value.trim() === '' ? 'Name is required' : null)
}; const { form, errors, validate, reset } = useForm(initialValues); const handleSubmit = async () => { if (validate()) { // 表单验证通过,提交表单逻辑 console.log('Form submitted:', form); }
7. 封装动画逻辑
动画效果是 Web 应用中常见的交互形式,我们可以使用自定义 Hook 来封装动画逻辑。
<!-- useAnimation.ts -->
import { ref, onMounted, onUnmounted } from 'vue'; export function useAnimation(duration: number) { const progress = ref(0); let animationFrameId: number | null = null; const startAnimation = () => { if (animationFrameId) return; animationFrameId = requestAnimationFrame(animate); }; const stopAnimation = () => { if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; } }; const animate = () => { if (progress.value < 1) { progress.value += 0.01; animationFrameId = requestAnimationFrame(animate); } else { stopAnimation(); } }; onMounted(startAnimation); onUnmounted(stopAnimation); return { progress, startAnimation, stopAnimation };
}
在组件中使用这个自定义 Hook 来添加动画效果:
<!-- AnimatedComponent.vue -->
<template> <div :style="{ opacity: progress }"> <p>This element is animated!</p> </div> <button @click="toggleAnimation">Toggle Animation</button>
</template> <script setup lang="ts">
import { useAnimation } from './useAnimation'; const { progress, startAnimation, stopAnimation } = useAnimation(1000); // 动画时长为 1 秒 const toggleAnimation = () => { if (progress.value === 0) { startAnimation(); } else { stopAnimation(); }
};
</script>