Vue3中基于Composition API处理网络请求
1. 理解 Composition API
在 Vue3 中,Composition API 是一项重大的更新,它为开发者提供了一种更灵活、高效的方式来组织和复用组件逻辑。传统的 Vue 组件使用选项式 API,将数据、方法、生命周期钩子等分散在不同的选项中。而 Composition API 则允许我们使用函数式的方式来组合逻辑,使代码结构更加清晰,可维护性更高。
1.1 基本概念
Composition API 主要包含以下几个核心概念:
- reactive:用于创建响应式数据。通过
reactive
函数,可以将一个普通的 JavaScript 对象转换为响应式对象,Vue 会追踪对该对象属性的访问和修改,并触发相应的视图更新。例如:
import { reactive } from 'vue';
const state = reactive({
count: 0
});
- ref:同样用于创建响应式数据,与
reactive
不同的是,ref
可以创建一个包含任意类型值的响应式引用。当访问ref
的值时,需要通过.value
来获取,而在模板中使用时,Vue 会自动解包。示例如下:
import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 输出 0
count.value++;
- computed:用于创建计算属性。计算属性会根据其依赖的响应式数据自动缓存,只有当依赖数据发生变化时才会重新计算。例如:
import { ref, computed } from 'vue';
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
- watch:用于监听响应式数据的变化。可以监听单个响应式数据,也可以监听多个响应式数据的变化,并在变化时执行相应的回调函数。例如:
import { ref, watch } from 'vue';
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log(`值从 ${oldValue} 变为 ${newValue}`);
});
count.value++;
1.2 与选项式 API 的对比
选项式 API 在大型项目中可能会遇到一些问题,比如逻辑分散。当一个组件的功能越来越复杂时,数据、方法和生命周期钩子等选项会变得冗长且难以维护。而 Composition API 通过将相关逻辑封装到函数中,使代码更加模块化。例如,在选项式 API 中,数据和方法可能如下定义:
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
使用 Composition API 则可以写成:
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
return {
count,
increment
};
}
};
可以看到,Composition API 使得逻辑更加集中,并且更容易复用。
2. 网络请求基础
在前端开发中,网络请求是获取和更新数据的重要手段。常见的网络请求方式有 fetch
和 axios
。
2.1 fetch
fetch
是现代浏览器提供的原生网络请求 API,它基于 Promise,使用起来相对简洁。以下是一个简单的 fetch
请求示例,用于获取 JSON 数据:
fetch('https://example.com/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求出错:', error));
fetch
的优点是原生支持,不需要额外引入库。但它也有一些不足之处,比如在处理错误时不够友好,默认不会处理 400 或 500 等错误状态码,需要手动检查 response.ok
。例如:
fetch('https://example.com/api/data')
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('请求出错:', error));
2.2 axios
axios
是一个流行的基于 Promise 的 HTTP 客户端,它可以在浏览器和 Node.js 中使用。axios
具有以下优点:
- 简洁易用:语法简单,易于上手。
- 支持多种请求方式:如
get
、post
、put
、delete
等。 - 自动转换请求和响应数据:可以自动将请求数据序列化为 JSON 格式,将响应数据解析为 JavaScript 对象。
- 丰富的拦截器功能:可以在请求发送前和响应接收后进行一些通用的处理,如添加请求头、处理错误等。
以下是一个使用 axios
发送 get
请求的示例:
import axios from 'axios';
axios.get('https://example.com/api/data')
.then(response => console.log(response.data))
.catch(error => console.error('请求出错:', error));
发送 post
请求的示例如下:
import axios from 'axios';
const data = {
name: 'John',
age: 30
};
axios.post('https://example.com/api/data', data)
.then(response => console.log(response.data))
.catch(error => console.error('请求出错:', error));
3. 在 Vue3 中使用 Composition API 处理网络请求
3.1 使用 axios 和 Composition API
首先,确保项目中已经安装了 axios
。可以通过以下命令安装:
npm install axios
假设我们有一个组件,需要从后端获取用户列表数据。使用 Composition API 和 axios
可以这样实现:
<template>
<div>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
const users = ref([]);
const fetchUsers = async () => {
try {
const response = await axios.get('https://example.com/api/users');
users.value = response.data;
} catch (error) {
console.error('获取用户列表出错:', error);
}
};
onMounted(() => {
fetchUsers();
});
</script>
在上述代码中:
- 使用
ref
创建了一个响应式数据users
,用于存储从后端获取的用户列表。 - 定义了
fetchUsers
函数,该函数使用axios
发送get
请求获取用户数据。如果请求成功,将响应数据赋值给users.value
;如果请求失败,打印错误信息。 - 使用
onMounted
生命周期钩子,在组件挂载后调用fetchUsers
函数,从而触发网络请求。
3.2 处理加载状态和错误状态
为了提供更好的用户体验,我们需要处理网络请求的加载状态和错误状态。可以在组件中添加加载状态和错误信息的响应式数据:
<template>
<div>
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
const users = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchUsers = async () => {
loading.value = true;
try {
const response = await axios.get('https://example.com/api/users');
users.value = response.data;
} catch (error) {
error.value = '获取用户列表出错';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUsers();
});
</script>
在这段代码中:
- 使用
ref
创建了loading
和error
两个响应式数据,分别用于表示加载状态和错误信息。 - 在
fetchUsers
函数中,在请求开始时将loading
设置为true
,请求结束后(无论成功或失败)将loading
设置为false
。如果请求失败,将错误信息赋值给error.value
。 - 在模板中,根据
loading
和error
的值来显示加载指示器、错误信息或用户列表。
3.3 复用网络请求逻辑
在实际项目中,可能会有多个组件需要进行相似的网络请求。这时,可以将网络请求逻辑封装成一个可复用的函数。例如,我们创建一个 useUsers.js
文件,用于封装获取用户列表的逻辑:
import { ref, onMounted } from 'vue';
import axios from 'axios';
export const useUsers = () => {
const users = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchUsers = async () => {
loading.value = true;
try {
const response = await axios.get('https://example.com/api/users');
users.value = response.data;
} catch (error) {
error.value = '获取用户列表出错';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUsers();
});
return {
users,
loading,
error
};
};
然后在组件中使用这个复用函数:
<template>
<div>
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>
<script setup>
import { useUsers } from './useUsers.js';
const { users, loading, error } = useUsers();
</script>
通过这种方式,将网络请求逻辑封装成一个自定义的组合函数,提高了代码的复用性,使得不同组件可以方便地使用相同的网络请求逻辑。
4. 处理复杂网络请求场景
4.1 带参数的网络请求
有时候,我们需要根据不同的参数来发送网络请求。例如,获取特定用户的详细信息,需要在请求 URL 中传递用户 ID。我们可以在复用的函数中添加参数支持。修改 useUsers.js
文件如下:
import { ref, onMounted } from 'vue';
import axios from 'axios';
export const useUser = (userId) => {
const user = ref(null);
const loading = ref(false);
const error = ref(null);
const fetchUser = async () => {
loading.value = true;
try {
const response = await axios.get(`https://example.com/api/users/${userId}`);
user.value = response.data;
} catch (error) {
error.value = '获取用户信息出错';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUser();
});
return {
user,
loading,
error
};
};
在组件中使用时,可以传递不同的 userId
参数:
<template>
<div>
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
<div v-else>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
</div>
</div>
</template>
<script setup>
import { useUser } from './useUser.js';
const { user, loading, error } = useUser(1); // 获取 ID 为 1 的用户信息
</script>
4.2 并发网络请求
在某些场景下,我们可能需要同时发送多个网络请求,并在所有请求都完成后进行处理。例如,同时获取用户列表和用户总数。可以使用 Promise.all
来实现并发请求。修改 useUsers.js
文件如下:
import { ref, onMounted } from 'vue';
import axios from 'axios';
export const useUsersAndTotal = () => {
const users = ref([]);
const total = ref(0);
const loading = ref(false);
const error = ref(null);
const fetchData = async () => {
loading.value = true;
try {
const [usersResponse, totalResponse] = await Promise.all([
axios.get('https://example.com/api/users'),
axios.get('https://example.com/api/users/total')
]);
users.value = usersResponse.data;
total.value = totalResponse.data.count;
} catch (error) {
error.value = '获取数据出错';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchData();
});
return {
users,
total,
loading,
error
};
};
在组件中使用这个函数:
<template>
<div>
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
<div v-else>
<p>用户总数: {{ total }}</p>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</div>
</template>
<script setup>
import { useUsersAndTotal } from './useUsersAndTotal.js';
const { users, total, loading, error } = useUsersAndTotal();
</script>
在上述代码中,Promise.all
接收一个包含多个 Promise 的数组,只有当所有 Promise 都 resolved 时,才会继续执行 then
回调,并将所有 resolved 的值以数组形式返回。通过这种方式,我们可以方便地处理并发网络请求。
4.3 条件性网络请求
有时候,我们可能需要根据某些条件来决定是否发送网络请求。例如,只有在用户登录后才获取用户的个人信息。可以在组合函数中添加条件判断。修改 useUserInfo.js
文件如下:
import { ref, onMounted } from 'vue';
import axios from 'axios';
export const useUserInfo = (isLoggedIn) => {
const userInfo = ref(null);
const loading = ref(false);
const error = ref(null);
const fetchUserInfo = async () => {
if (!isLoggedIn) {
return;
}
loading.value = true;
try {
const response = await axios.get('https://example.com/api/user/info');
userInfo.value = response.data;
} catch (error) {
error.value = '获取用户信息出错';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUserInfo();
});
return {
userInfo,
loading,
error
};
};
在组件中使用时,可以根据登录状态来决定是否获取用户信息:
<template>
<div>
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
<div v-else>
<p v-if="userInfo">姓名: {{ userInfo.name }}</p>
<p v-if="userInfo">邮箱: {{ userInfo.email }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useUserInfo } from './useUserInfo.js';
const isLoggedIn = ref(true);
const { userInfo, loading, error } = useUserInfo(isLoggedIn.value);
</script>
通过在组合函数中添加条件判断,我们可以灵活地控制网络请求的发送,避免不必要的请求。
5. 优化网络请求性能
5.1 缓存数据
在一些情况下,我们可以对网络请求的数据进行缓存,以减少重复请求。例如,在短时间内多次获取相同的用户列表数据。可以使用一个简单的缓存机制,在组合函数中添加缓存逻辑。修改 useUsers.js
文件如下:
import { ref, onMounted } from 'vue';
import axios from 'axios';
let userCache = null;
export const useUsers = () => {
const users = ref(userCache || []);
const loading = ref(false);
const error = ref(null);
const fetchUsers = async () => {
if (userCache) {
return;
}
loading.value = true;
try {
const response = await axios.get('https://example.com/api/users');
users.value = response.data;
userCache = response.data;
} catch (error) {
error.value = '获取用户列表出错';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUsers();
});
return {
users,
loading,
error
};
};
在上述代码中,使用了一个全局变量 userCache
来缓存用户列表数据。在每次请求前,先检查 userCache
是否有数据,如果有则直接返回,不再发送网络请求。这样可以有效地减少网络请求次数,提高性能。
5.2 节流和防抖
在一些场景下,用户可能会频繁触发网络请求,比如在搜索框中输入内容时实时搜索。这时,可以使用节流或防抖技术来控制请求频率。
防抖:防抖是指在一定时间内,如果事件被频繁触发,只执行最后一次。可以使用 lodash
库中的 debounce
函数来实现。首先安装 lodash
:
npm install lodash
假设我们有一个搜索组件,根据用户输入实时搜索用户。修改 useSearchUsers.js
文件如下:
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { debounce } from 'lodash';
export const useSearchUsers = () => {
const searchQuery = ref('');
const users = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchSearchUsers = async () => {
if (!searchQuery.value) {
users.value = [];
return;
}
loading.value = true;
try {
const response = await axios.get(`https://example.com/api/users/search?q=${searchQuery.value}`);
users.value = response.data;
} catch (error) {
error.value = '搜索用户出错';
} finally {
loading.value = false;
}
};
const debouncedFetch = debounce(fetchSearchUsers, 300);
onMounted(() => {
searchQuery.value = '';
});
return {
searchQuery,
users,
loading,
error,
debouncedFetch
};
};
在模板中使用:
<template>
<div>
<input v-model="searchQuery" @input="debouncedFetch" placeholder="搜索用户">
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>
<script setup>
import { useSearchUsers } from './useSearchUsers.js';
const { searchQuery, users, loading, error, debouncedFetch } = useSearchUsers();
</script>
在上述代码中,使用 debounce
函数对 fetchSearchUsers
函数进行包装,设置延迟时间为 300 毫秒。这样,当用户在搜索框中输入内容时,只有在停止输入 300 毫秒后才会触发网络请求,避免了频繁请求。
节流:节流是指在一定时间内,无论事件触发多么频繁,都只执行一次。同样可以使用 lodash
库中的 throttle
函数来实现。假设我们有一个滚动加载更多用户的场景,修改 useLoadMoreUsers.js
文件如下:
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { throttle } from 'lodash';
export const useLoadMoreUsers = () => {
const users = ref([]);
const page = ref(1);
const loading = ref(false);
const error = ref(null);
const fetchMoreUsers = async () => {
if (loading.value) {
return;
}
loading.value = true;
try {
const response = await axios.get(`https://example.com/api/users?page=${page.value}`);
users.value = [...users.value, ...response.data];
page.value++;
} catch (error) {
error.value = '加载更多用户出错';
} finally {
loading.value = false;
}
};
const throttledFetch = throttle(fetchMoreUsers, 500);
onMounted(() => {
fetchMoreUsers();
});
return {
users,
loading,
error,
throttledFetch
};
};
在模板中使用:
<template>
<div>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
<button @click="throttledFetch" :disabled="loading">加载更多</button>
<loading v-if="loading"></loading>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>
<script setup>
import { useLoadMoreUsers } from './useLoadMoreUsers.js';
const { users, loading, error, throttledFetch } = useLoadMoreUsers();
</script>
在上述代码中,使用 throttle
函数对 fetchMoreUsers
函数进行包装,设置节流时间为 500 毫秒。这样,当用户点击“加载更多”按钮时,每 500 毫秒只会触发一次网络请求,避免了用户快速点击导致的频繁请求。
通过缓存数据、节流和防抖等技术,可以有效地优化网络请求性能,提升用户体验。
6. 处理网络请求中的安全性问题
6.1 防止 CSRF 攻击
CSRF(Cross - Site Request Forgery,跨站请求伪造)是一种常见的网络攻击方式,攻击者利用用户已登录的状态,在用户不知情的情况下,以用户的名义发送恶意请求。在 Vue 项目中,可以通过在请求头中添加 CSRF 令牌来防止这种攻击。
假设后端使用的是 Django 框架,Django 提供了内置的 CSRF 保护机制。在前端,我们需要在每次请求时将 CSRF 令牌添加到请求头中。可以在 axios
拦截器中统一处理:
import axios from 'axios';
const csrfToken = document.querySelector('meta[name="csrf - token"]').getAttribute('content');
axios.interceptors.request.use(config => {
config.headers['X - CSRFToken'] = csrfToken;
return config;
}, error => {
return Promise.reject(error);
});
在 HTML 模板中,通常会有一个 meta 标签来存储 CSRF 令牌:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta name="csrf - token" content="{{ csrf_token }}">
<title>Vue with CSRF Protection</title>
</head>
<body>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>
通过这种方式,每次网络请求都会携带 CSRF 令牌,后端可以验证令牌的有效性,从而防止 CSRF 攻击。
6.2 处理敏感数据
在网络请求中,可能会涉及到敏感数据,如用户密码、银行卡信息等。对于这些敏感数据,一定要进行加密传输。常见的加密方式有使用 HTTPS 协议,它在传输层对数据进行加密,保证数据在传输过程中的安全性。
另外,在前端代码中,要避免直接在客户端存储敏感数据。如果确实需要存储一些临时的敏感信息,可以使用加密存储,比如使用 crypto - js
库对数据进行加密后存储在 localStorage
中。以下是一个简单的示例:
import CryptoJS from 'crypto - js';
// 加密数据
const sensitiveData = { password: '123456' };
const encryptedData = CryptoJS.AES.encrypt(JSON.stringify(sensitiveData), 'secretKey').toString();
localStorage.setItem('encryptedSensitiveData', encryptedData);
// 解密数据
const storedEncryptedData = localStorage.getItem('encryptedSensitiveData');
const bytes = CryptoJS.AES.decrypt(storedEncryptedData,'secretKey');
const decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
通过加密传输和加密存储,可以有效地保护敏感数据的安全性。
6.3 防止 XSS 攻击
XSS(Cross - Site Scripting,跨站脚本攻击)是攻击者在网页中注入恶意脚本,当用户访问该网页时,恶意脚本会在用户浏览器中执行,从而窃取用户信息或进行其他恶意操作。在 Vue 中,由于模板引擎的特性,已经对大部分 XSS 攻击有了一定的防护。
然而,当我们需要动态渲染用户输入的内容时,比如用户发表评论,就需要特别小心。可以使用 DOMPurify
库来对用户输入进行净化,去除恶意脚本。首先安装 DOMPurify
:
npm install dompurify
假设我们有一个评论组件,在显示评论时使用 DOMPurify
进行净化:
<template>
<div>
<div v - for="comment in comments" :key="comment.id">
<div v - html="sanitizedComment(comment.content)"></div>
</div>
</div>
</template>
<script setup>
import DOMPurify from 'dompurify';
const comments = ref([
{ id: 1, content: '这是一条正常评论' },
{ id: 2, content: '<script>alert("XSS")</script>' }
]);
const sanitizedComment = (content) => {
return DOMPurify.sanitize(content);
};
</script>
在上述代码中,sanitizedComment
函数使用 DOMPurify
对评论内容进行净化,确保渲染到页面上的内容是安全的,从而防止 XSS 攻击。
通过采取这些措施,可以有效地处理网络请求中的安全性问题,保障用户数据的安全和应用的稳定运行。
在 Vue3 中使用 Composition API 处理网络请求,不仅可以使代码结构更加清晰,复用性更高,还能通过合理的优化和安全处理,提升应用的性能和安全性。希望通过本文的介绍,能帮助你更好地掌握这一技术,在实际项目中灵活运用。