vue3从精通到入门22:自定义 Hooks

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

相关推荐

  1. vue3精通入门22定义 Hooks

    2024-04-12 03:08:02       37 阅读
  2. vue3精通入门21定义指令directive

    2024-04-12 03:08:02       29 阅读
  3. Vue3入门实战笔记04--生命周期和定义hook

    2024-04-12 03:08:02       57 阅读
  4. 深入React Hoooks基础定义 Hooks

    2024-04-12 03:08:02       24 阅读
  5. vue3定义hooks

    2024-04-12 03:08:02       52 阅读
  6. vue3精通入门8:reactive的使用

    2024-04-12 03:08:02       39 阅读
  7. vue3精通入门10:侦听器watch

    2024-04-12 03:08:02       40 阅读
  8. vue3精通入门11:高级侦听器watchEffect

    2024-04-12 03:08:02       42 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-12 03:08:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-12 03:08:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-12 03:08:02       82 阅读
  4. Python语言-面向对象

    2024-04-12 03:08:02       91 阅读

热门阅读

  1. 登录接口设计的优缺点——开发经验(3)

    2024-04-12 03:08:02       37 阅读
  2. 关于搭建elk日志平台

    2024-04-12 03:08:02       40 阅读
  3. 设计模式:命令模式示例

    2024-04-12 03:08:02       37 阅读
  4. [RK3399 Linux] 移植U-Boot 2023.04 & Linux 6.3

    2024-04-12 03:08:02       40 阅读
  5. 《1w实盘and大盘基金预测 day19》

    2024-04-12 03:08:02       38 阅读
  6. React中State管理的4 个关键解决方案

    2024-04-12 03:08:02       39 阅读
  7. vue2-vue3面试

    2024-04-12 03:08:02       29 阅读
  8. Animation动画控制脚本

    2024-04-12 03:08:02       38 阅读