写作背景:
到目前为止 Vue 为我们提供了两种开发组件的 API 风格,选项式 API 和组合式 API。组合式 API 可以由我们导入不同的 API 函数来描述组件的逻辑,在 SFC 组件中通常还会在 script 标签显示标注setup来使用。
当在一个 SFC 组件中使用组合式 API 开发一段时间后你会发现,这一个组件里面包含的了不少的功能,如何来复用这些通用的代码块就成了一个问题?
为了可以将这些组合式 API 实现的功能进行整合,Vue 给我们提供了一种组合式函数的概念。我们可以利用组合式 API 来打包(封装和复用)这些有状态逻辑的函数。
编写组合式函数的约定:
编写开始前需要了解一下,编写组合式函数的约定:
- 命名方式:使用驼峰命名法命名,函数的前缀统一使用“use”;
- 输入参数:组合式函数应兼容支持 ref 参数,
unref()
可以帮助我们轻松的得到原始值。如果我们明确接收的参数存在响应式特点,我们应该使用watch()
或watchEffect()
进行跟踪。 - 返回值:默认遵守返回的结果为 ref 对象,这样可以保证该函数在组件中解构后仍保持响应性。如果依然想通过对象.属性的形式获取返回的结果,我们可以使用
reactive()
来包装这个函数,这样其中的 ref 会被自动解包。实时显示鼠标位置: 同官方例子,我们通过监听mousemove事件来实时获取鼠标的位置,并更新到页面。
第一次使用组合式 API 开发:
下面的代码就是我们使用组合式 API 在 SCF 组件中的实现了,其中增加了许多注释来帮助第一次使用的伙伴理解。
代码语言:javascript复制<!-- 添加setup表示我们要在 SFC 组件中使用组合式 API -->
<script setup>
// 导入对应的组合式 API
import { onMounted, onUnmounted, ref } from "vue";
// 声明一对响应式坐标
const x = ref(0);
const y = ref(0);
/**
* 更新页面实现的鼠标坐标
* @param {*} e
*/
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
// 在组件挂在后添加鼠标移动的监听事件
onMounted(() => window.addEventListener("mousemove", update));
// 在组件写在后移除鼠标移动的监听事件
onUnmounted(() => window.removeEventListener("mousemove", update));
</script>
<template>
<p>pageX:{{ x }}</p>
<p>pageY:{{ y }}</p>
</template>
使用组合式函数复用功能:
为了达到上述代码的可复用性,我们选择使用组合式函数进行打包封装。
- 新建一个mouse.js,并导出一个名为useMouse的函数;
- 移植 SCF 组件中script块中的全部内容到useMouse函数(导入的依赖除外);
- 我们使用useMouse函数最终得到的结果应该是鼠标的当前坐标,所以返回 x 和 y 组成的 ref 对象组合。
完整的useMouse函数代码如下:
代码语言:javascript复制// 导入对应的组合式 API
import { onMounted, onUnmounted, ref } from "vue";
export function useMouse() {
// 声明一对响应式坐标
const x = ref(0);
const y = ref(0);
/**
* 更新页面实现的鼠标坐标
* @param {*} e
*/
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
// 在组件挂在后添加鼠标移动的监听事件
onMounted(() => window.addEventListener("mousemove", update));
// 在组件写在后移除鼠标移动的监听事件
onUnmounted(() => window.removeEventListener("mousemove", update));
return { x, y };
}
使用useMouse函数:
代码语言:javascript复制<!-- 添加setup表示我们要在 SFC 组件中使用组合式 API -->
<script setup>
import { useMouse } from "./uses/mouse";
const { x, y } = useMouse();
</script>
<template>
<p>pageX:{{ x }}</p>
<p>pageY:{{ y }}</p>
</template>
组合式函数中使用组合式函数:
在上面的案例中,关于事件的注册和反注册对于更多的场景来说抽取出来是很不错的一件事,所以我们任然可以使用组合式函数对这一部分进行抽取,并在原来的组合式函数中使用新抽取的这个函数。
代码语言:javascript复制import { onMounted, onUnmounted } from "vue";
export function useEventListener(type, listener) {
// 在组件挂在后添加鼠标移动的监听事件
onMounted(() => window.addEventListener(type, listener));
// 在组件写在后移除鼠标移动的监听事件
onUnmounted(() => window.removeEventListener(type, listener));
}
使用的时候直接导入后使用即可:
代码语言:javascript复制export function useMouse() {
...
// 使用useEventListener
useEventListener("mousemove", update);
return { x, y };
}
异步请求数据:
我们在开发中必不可少的一个环节将是获取服务器提供给我们的数据,当然我们也通常会对获取数据的工具进行封装处理,如:封装 axios,我们这里通过官网的演示(fetch)来讲述。
使用组合式函数编写完的结果如下,我们在 SFC 组件中使用的时候将变得很方便。
代码语言:javascript复制import { ref } from "vue";
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err));
return { data, error };
}
但是如果我们要根据 url 的变化来主动发起新的请求应该怎么办呢?也就是当传入的 url 是一个响应式的 ref 而非原始值。
代码语言:javascript复制import { isRef, ref, unref, watchEffect } from "vue";
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
function doFetch() {
// unref 如果参数是 ref,则返回内部值,否则返回参数本身
fetch(unref(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err));
}
if (isRef(url)) {
// watchEffect可以帮助我们立即执行传入的函数,并响应式的进行追踪其依赖
watchEffect(doFetch);
} else {
doFetch(); // 原始值的话直接发起数据请求
}
return { data, error };
}
在组件中使用:
代码语言:javascript复制<script setup>
import { ref } from "vue";
import { useFetch } from "./uses/fetch";
const url = ref("https://jsonplaceholder.typicode.com/todos/1");
const { data, error } = useFetch(url);
function change() {
url.value = `https://jsonplaceholder.typicode.com/todos/${Math.floor(
Math.random() * 10 1
)}`;
}
</script>
<template>
<p>data:{{ data }}</p>
<p>error:{{ error }}</p>
<button @click="change">change url</button>
</template>
写在最后:
组合式 API 同选项式 API 两种风格都能够欧覆盖大部分的应用场景,选项式 API 也是组合式 API 的实现,在官网的描述中我们可以在构建工具或复杂度高的场景下使用组合式 API,在其他时候按我们先来先得的选项式 API 就完全可以满足。