什么是hooks
自定义hooks 是Vue3 组合式函数的别称。
在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
- 命名规范
- 组合式函数约定用驼峰命名法命名,并以“use”作为开头。以便识别它们是可复用的逻辑单元。
- 例如,
useCounter
、useFetchData
等。
- 函数结构
- 组合式函数可以接收一些参数,这些参数可以是初始值、配置选项等。
- 函数内部可以使用 Vue 的响应式 API(如
ref
、reactive
等)来创建响应式数据,并定义一些方法来操作这些数据。 - 最后,函数返回一个包含响应式数据和方法的对象。
- 返回值
- Vue官方推荐组合式函数始终返回一个包含多个
ref
的普通的非响应式对象,该对象在组件中被解构为ref
之后仍可以保持响应性:
当组合式函数返回一个包含多个ref
的对象时,在组件中解构这个对象后,每个解构出来的ref
仍然保持响应性。这意味着如果ref
的值发生变化,组件会自动重新渲染以反映这个变化。 - 可以使用
reactive()
包装返回的对象:
- Vue官方推荐组合式函数始终返回一个包含多个
const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)
- 使用方式
- 在 Vue 组件中,使用
import
语句导入 hooks ,并像调用普通函数一样调用它们。
例如:import { useMouse } from '@/hooks/mouse.js';
- 在使用时,显式的将响应式变量或者方法解构暴露出来
例如:const { x, y } = useMouse()
- 在 Vue 组件中,使用
使用
一个简单的加减功能
useCounter.ts
:
import { ref } from 'vue';
// 按照惯例,组合式函数名以“use”开头
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
// 通过返回值暴露响应式变量、方法
return {
count,
increment,
decrement,
};
}
在.vue
中使用:
<template>
<div>
<div>Count: {{ count }}</div>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>
<script setup lang="ts" >
import { useCounter } from '@/hooks/useCounter.ts';
const { count, increment, decrement } = useCounter();
</script>
鼠标跟踪功能
把功能实现封装到mouse.ts
中:
// mouse.ts
import { onMounted, onUnmounted, ref } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event:any) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
在.vue
中使用:
<template>Mouse position is at: {{ x }}, {{ y }}</template>
<script setup lang="ts">
import { useMouse } from '@/hooks/mouse.js'
const { x, y } = useMouse()
</script>
每一个调用 useMouse()
的组件实例会创建其独有的 x
、y
状态拷贝,因此他们不会互相影响。
一个组合式函数可以调用一个或多个其他的组合式函数。可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。这是组合式 API的本质。
封装 添加和清除 DOM 事件监听器
// event.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
有了event.js
,之前的 useMouse()
组合式函数可以被简化为:
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'
export function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', (event) => {
x.value = event.pageX
y.value = event.pageY
})
return { x, y }
}
将本地图片转化为base64格式
import { ref } from 'vue';
export default function useImageToBase64() {
const base64Image = ref<string | null>('');
const convertImageToBase64 = async (imageFile:File) => {
if (imageFile) {
// 创建FileReader实例对象reader
const reader = new FileReader();
// 开始读取图片文件,将其转换为 data URL(一种 base64 编码的字符串表示形式)
reader.readAsDataURL(imageFile);
reader.onload = (event) => {
base64Image.value = event.target?.result as string;
};
} else {
base64Image.value = null;
}
};
return {
base64Image,
convertImageToBase64,
};
}
在这个示例中,自定义 Hook useImageToBase64
提供了一个方法convertImageToBase64
来将本地图片文件转换为 base64
格式,并将结果存储在base64Image
响应式变量中。在组件中,可以通过文件输入的变化事件调用这个方法,并在模板中显示转换后的图片。
<template>
<div style="font-size: 24px;text-align: center;">首页</div>
<div>将本地图片转换为 base64 格式的自定义 Hook 的示例:</div>
<input type="file" @change="handleFileInputChange" />
<img v-if="base64Image" :src="base64Image" />
</template>
<script setup>
import useImageToBase64 from '@/hooks/useImageToBase64';
const { base64Image, convertImageToBase64 } = useImageToBase64();
const handleFileInputChange = (event) => {
const file = event.target.files[0];
convertImageToBase64(file);
};
</script>
异步示例
// useImageToBase64.ts
export default function useImageToBase64() {
const convertImageToBase64 = async (imageFile: File): Promise<string> => {
return new Promise<string>((resolve, reject) => {
// imageFile 图片文件
if (imageFile) {
// 创建FileReader实例对象reader
const reader = new FileReader();
// 开始读取图片文件,将其转换为 data URL(一种 base64 编码的字符串表示形式)
reader.readAsDataURL(imageFile);
// 当图片加载完成时触发load事件
reader.onload = (event) => {
resolve(event.target?.result as string);
};
reader.onerror = (error) => {
reject(error)
}
} else {
// 调用 reject 方法来拒绝 Promise
reject(new Error('没有 File'));
}
})
};
return {
convertImageToBase64,
};
}
这个自定义钩子函数 useImageToBase64
的主要功能是将传入的 File
类型的图片文件转换为 base64
编码的字符串。它使用了 FileReader
对象来异步读取图片文件,并在读取成功时将结果以 Promise
的方式返回。
<template>
<div>将本地图片转换为 base64 格式的自定义 Hook 的示例:</div>
<input type="file" @change="handleFileInputChange" />
<img v-if="base64Image" :src="base64Image" />
</template>
<script setup lang="ts">
import useImageToBase64 from '@/hooks/useImageToBase64';
import { ref } from 'vue';
const { convertImageToBase64 } = useImageToBase64();
let base64Image = ref('')
const handleFileInputChange = async (event) => {
const file = event.target.files[0];
console.log(file)
try {
base64Image.value = await convertImageToBase64(file);
console.log(base64Image.value)
}catch(err) {
console.log(err)
}
};
</script>
在选项式 API 中使用hooks
在选项式 API 中使用 hooks,hooks 必须在 setup()
中调用,且其返回的绑定必须在 setup()
中返回,以便暴露给 this
及其模板:
import { useMouse } from '@/hooks/mouse.js'
import { useFetch } from '@/hooks/fetch.js'
export default {
setup() {
const { x, y } = useMouse()
const { data, error } = useFetch('...')
return { x, y, data, error }
},
mounted() {
// setup() 暴露的属性可以在通过 `this` 访问到
console.log(this.x)
}
// ...其他选项
}
Vue hooks库
- VueUse :一个基于 Vue 3 Composition API 的高质量 Hooks 库。
- 安装:
npm i @vueuse/core
- 安装:
hooks优点
- hooks 作为独立逻辑的组件封装,其内部的属性、函数等和外部组件具有响应式依附的作用。
- hooks 内部可以使用 Vue 3 的响应式 API(如
ref
、reactive
等)来创建响应式数据。当这些 hooks 被外部组件调用时,它们返回的响应式数据可以在组件中直接使用,并且与外部组件的响应式系统相连接。 - Vue 的响应式系统会自动收集对响应式数据的依赖关系。当 hooks 内部的响应式数据发生变化时,所有依赖于这些数据的组件都会自动触发更新。
- hooks 内部可以使用 Vue 3 的响应式 API(如
- hooks 可以与 Vue 3 的组合式 API 无缝结合,高内聚低耦合。
- hooks 是独立的函数,可以更容易地对其进行单元测试。
使用限制
组合式函数只能在 <script setup>
或 setup()
钩子中被调用。在这些上下文中,它们也只能被同步调用。在某些情况下,你也可以在像 onMounted()
这样的生命周期钩子中调用它们。
- 组合式函数主要设计为在
<script setup>
或setup()
钩子中被调用。这是因为在这些上下文中,Vue 能够明确地确定当前正在设置的组件实例。 - 在这些位置调用组合式函数可以确保正确地与组件的生命周期和响应式系统集成。例如,可以在这些地方访问组件的响应式数据和方法,并将计算属性和监听器注册到组件实例上。
- 在某些情况下,可以在像
onMounted()
这样的生命周期钩子中调用组合式函数。这通常是在需要在特定的生命周期阶段执行一些逻辑时使用。 - 需要注意的是,在生命周期钩子中调用组合式函数也应该遵循同步调用的原则,除非是在
<script setup>
中使用await
之后的特殊情况。 - 组合式函数只能被同步调用是为了确保代码的执行顺序是可预测的。如果允许异步调用组合式函数,可能会导致难以理解的代码执行顺序和潜在的错误。
- 在
<script setup>
中是唯一可以在调用await
之后仍可调用组合式函数的地方。这是因为 Vue 的编译器会在异步操作之后自动为你恢复当前的组件实例。