自定义 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>