MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Qwik API调用:fetch与axios的高效使用技巧

2023-09-116.9k 阅读

Qwik 中使用原生 fetch 进行 API 调用

在 Qwik 项目中,使用原生的 fetch 进行 API 调用是一种基础且高效的方式。fetch 是现代浏览器提供的用于进行网络请求的 API,它基于 Promise,使用起来相对简洁。

基本的 GET 请求

首先,来看一个简单的 GET 请求示例。假设我们有一个 API 端点,它返回一些用户数据。

import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const users = useTask$(async () => {
    const response = await fetch('https://example.com/api/users');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

在这个例子中,我们使用 useTask$ 来执行异步任务。fetch 发起一个 GET 请求到指定的 API 端点。如果响应状态码不是 2xx,我们抛出一个错误。否则,我们将响应解析为 JSON 格式的数据。

带参数的 GET 请求

有时候,我们需要在 GET 请求中传递参数。这可以通过构建 URL 来实现。

import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const searchTerm = 'example';
  const users = useTask$(async () => {
    const url = new URL('https://example.com/api/search-users');
    url.searchParams.append('q', searchTerm);
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

这里,我们使用 URL 对象来构建包含参数的 URL。searchParams 用于添加参数,在这个例子中,我们添加了一个名为 q 的参数,其值为 searchTerm

POST 请求

对于 POST 请求,我们需要在 fetch 的第二个参数中指定请求方法和请求体。

import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const newUser = { name: 'John Doe', age: 30 };
  const createUser = useTask$(async () => {
    const response = await fetch('https://example.com/api/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(newUser)
    });
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  });

  return (
    <div>
      {createUser.value && (
        <p>User created: {createUser.value.name}</p>
      )}
    </div>
  );
});

在这个示例中,我们设置 methodPOST,并在 headers 中指定 Content - Typeapplication/json,因为我们要发送 JSON 格式的数据。body 则是将 newUser 对象转换为 JSON 字符串。

PUT 和 DELETE 请求

PUT 和 DELETE 请求的使用方式与 POST 请求类似,只是 method 不同。

// PUT 请求示例
import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const updatedUser = { id: 1, name: 'Updated Name', age: 31 };
  const updateUser = useTask$(async () => {
    const response = await fetch(`https://example.com/api/users/${updatedUser.id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(updatedUser)
    });
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  });

  return (
    <div>
      {updateUser.value && (
        <p>User updated: {updateUser.value.name}</p>
      )}
    </div>
  );
});

// DELETE 请求示例
import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const userIdToDelete = 1;
  const deleteUser = useTask$(async () => {
    const response = await fetch(`https://example.com/api/users/${userIdToDelete}`, {
      method: 'DELETE'
    });
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return 'User deleted successfully';
  });

  return (
    <div>
      {deleteUser.value && (
        <p>{deleteUser.value}</p>
      )}
    </div>
  );
});

在 PUT 请求示例中,我们发送更新后的用户数据到指定的用户 ID 端点。DELETE 请求示例则是简单地发送一个 DELETE 请求到指定用户 ID 的端点。

使用 axios 在 Qwik 中进行 API 调用

axios 是一个流行的基于 Promise 的 HTTP 客户端,它可以在浏览器和 Node.js 中使用。在 Qwik 项目中使用 axios 可以提供更多的功能和便利。

安装 axios

首先,需要在项目中安装 axios。如果使用 npm,可以运行以下命令:

npm install axios

如果使用 yarn:

yarn add axios

基本的 GET 请求

import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const users = useTask$(async () => {
    const response = await axios.get('https://example.com/api/users');
    return response.data;
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

与原生 fetch 不同,axios.get 直接返回解析后的 JSON 数据,通过 response.data 可以获取到。

带参数的 GET 请求

import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const searchTerm = 'example';
  const users = useTask$(async () => {
    const response = await axios.get('https://example.com/api/search-users', {
      params: {
        q: searchTerm
      }
    });
    return response.data;
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

axios 通过 params 选项来传递 GET 请求的参数,这比原生 fetch 构建 URL 更加简洁。

POST 请求

import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const newUser = { name: 'John Doe', age: 30 };
  const createUser = useTask$(async () => {
    const response = await axios.post('https://example.com/api/users', newUser);
    return response.data;
  });

  return (
    <div>
      {createUser.value && (
        <p>User created: {createUser.value.name}</p>
      )}
    </div>
  );
});

axios.post 的第一个参数是 URL,第二个参数就是请求体,它会自动设置 Content - Typeapplication/json 并将数据序列化。

PUT 和 DELETE 请求

// PUT 请求示例
import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const updatedUser = { id: 1, name: 'Updated Name', age: 31 };
  const updateUser = useTask$(async () => {
    const response = await axios.put(`https://example.com/api/users/${updatedUser.id}`, updatedUser);
    return response.data;
  });

  return (
    <div>
      {updateUser.value && (
        <p>User updated: {updateUser.value.name}</p>
      )}
    </div>
  );
});

// DELETE 请求示例
import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const userIdToDelete = 1;
  const deleteUser = useTask$(async () => {
    const response = await axios.delete(`https://example.com/api/users/${userIdToDelete}`);
    return 'User deleted successfully';
  });

  return (
    <div>
      {deleteUser.value && (
        <p>{deleteUser.value}</p>
      )}
    </div>
  );
});

PUT 和 DELETE 请求的用法与 axios.post 类似,只是方法名不同。

fetch 与 axios 的比较

功能特性

  • fetch:是浏览器原生 API,简洁轻量。它基于 Promise,支持流式处理响应数据。但是,它的错误处理相对较为底层,例如,网络请求失败(如 404、500 等状态码)并不会直接抛出错误,需要手动检查 response.ok
  • axios:功能更为丰富,提供了诸如自动转换请求和响应数据、拦截器、请求取消等功能。它的错误处理更加友好,请求失败(包括非 2xx 状态码)会直接抛出错误。

性能

  • fetch:由于是原生 API,在性能上理论上会更好一些,因为不需要引入额外的库。不过,实际应用中,这种性能差异在大多数情况下并不明显。
  • axios:虽然引入了额外的库,但它的优化也做得很好。而且,axios 提供的功能可以减少开发者的代码量,从开发效率角度来看可能更有优势。

兼容性

  • fetch:现代浏览器基本都支持,但在一些较旧的浏览器(如 IE)中需要使用 polyfill 来实现兼容。
  • axios:由于其在 Node.js 中也能使用,并且对各种环境的兼容性都做了较好的处理,所以在兼容性方面表现较好。

选择建议

  • 如果项目对性能要求极高,且目标浏览器都是现代浏览器,对功能需求不是特别复杂,那么使用原生 fetch 是个不错的选择。
  • 如果项目需要兼容较旧的浏览器,或者需要诸如拦截器、请求取消等高级功能,以及更友好的错误处理,axios 会是更好的选择。

在 Qwik 中处理 API 调用的错误

无论是使用 fetch 还是 axios,在 Qwik 中处理 API 调用的错误都非常重要。

使用原生 fetch 处理错误

import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const users = useTask$(async () => {
    try {
      const response = await fetch('https://example.com/api/users');
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    } catch (error) {
      console.error('Error fetching users:', error);
      return [];
    }
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

在这个例子中,我们使用 try - catch 块来捕获 fetch 过程中可能出现的错误。如果响应状态码不是 2xx,我们手动抛出一个错误并在 catch 块中处理。

使用 axios 处理错误

import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const users = useTask$(async () => {
    try {
      const response = await axios.get('https://example.com/api/users');
      return response.data;
    } catch (error) {
      console.error('Error fetching users:', error);
      return [];
    }
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

axios 会在请求失败(包括非 2xx 状态码)时直接抛出错误,所以同样可以使用 try - catch 块来捕获并处理错误。

优化 API 调用性能

缓存策略

  • 客户端缓存:可以在客户端实现简单的缓存机制。例如,对于一些不经常变化的数据,可以在第一次请求成功后将数据存储在本地(如 localStorage 或内存中),后续请求先检查缓存,如果缓存中有数据且未过期,则直接使用缓存数据,避免重复的网络请求。
import { component$, useTask$ } from '@builder.io/qwik';

export default component$(() => {
  const cache = {};
  const users = useTask$(async () => {
    if (cache.users && Date.now() - cache.timestamp < 60 * 1000) { // 缓存有效期 1 分钟
      return cache.users;
    }
    const response = await fetch('https://example.com/api/users');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    cache.users = data;
    cache.timestamp = Date.now();
    return data;
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});
  • 服务器端缓存:如果可能,在服务器端设置缓存。例如,使用诸如 Redis 等缓存工具来缓存 API 响应数据。这样,当多个客户端请求相同的数据时,服务器可以直接从缓存中返回数据,减少数据库查询等操作,提高响应速度。

批量请求

当需要从多个 API 端点获取数据时,可以考虑批量请求。例如,使用 Promise.all 来同时发起多个 fetchaxios 请求。

import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const [users, posts] = useTask$(async () => {
    const [usersResponse, postsResponse] = await Promise.all([
      axios.get('https://example.com/api/users'),
      axios.get('https://example.com/api/posts')
    ]);
    return [usersResponse.data, postsResponse.data];
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
      {posts.value && (
        <ul>
          {posts.value.map(post => (
            <li key={post.id}>{post.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

这样可以减少网络请求次数,提高整体性能。

减少不必要的请求

在进行 API 调用之前,先检查是否真的需要获取数据。例如,如果页面上的数据没有发生变化,或者用户没有触发特定的操作,就不需要重复请求数据。可以通过一些状态变量来控制 API 调用的时机。

import { component$, useTask$, useSignal } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const shouldFetch = useSignal(true);
  const users = useTask$(async () => {
    if (!shouldFetch.value) {
      return [];
    }
    const response = await axios.get('https://example.com/api/users');
    return response.data;
  });

  return (
    <div>
      <button onClick={() => shouldFetch.value = true}>Fetch Users</button>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

在这个例子中,只有当 shouldFetchtrue 时才会发起 API 请求。

在 Qwik 中使用 API 调用的最佳实践

封装 API 调用

为了提高代码的可维护性和复用性,建议将 API 调用封装成独立的函数或模块。

// api.js
import axios from 'axios';

const BASE_URL = 'https://example.com/api';

export const getUsers = async () => {
  const response = await axios.get(`${BASE_URL}/users`);
  return response.data;
};

export const createUser = async (userData) => {
  const response = await axios.post(`${BASE_URL}/users`, userData);
  return response.data;
};

// component.js
import { component$, useTask$ } from '@builder.io/qwik';
import { getUsers, createUser } from './api';

export default component$(() => {
  const users = useTask$(getUsers);
  const newUser = { name: 'John Doe', age: 30 };
  const createNewUser = useTask$(() => createUser(newUser));

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
      {createNewUser.value && (
        <p>User created: {createNewUser.value.name}</p>
      )}
    </div>
  );
});

这样,当 API 端点或请求逻辑发生变化时,只需要在封装的函数中进行修改,而不会影响到多个使用该 API 调用的组件。

处理响应数据

在获取到 API 响应数据后,要对数据进行适当的处理。例如,验证数据格式、提取需要的字段、进行数据转换等。

import { component$, useTask$ } from '@builder.io/qwik';
import axios from 'axios';

export default component$(() => {
  const users = useTask$(async () => {
    const response = await axios.get('https://example.com/api/users');
    const validUsers = response.data.filter(user => user.name && user.age);
    return validUsers.map(user => ({
      id: user.id,
      displayName: `${user.name} (${user.age})`
    }));
  });

  return (
    <div>
      {users.value && (
        <ul>
          {users.value.map(user => (
            <li key={user.id}>{user.displayName}</li>
          ))}
        </ul>
      )}
    </div>
  );
});

在这个例子中,我们先过滤掉不符合格式的数据,然后对符合格式的数据进行转换,生成更适合在页面上展示的数据结构。

安全考虑

  • 防止 CSRF 攻击:如果 API 涉及用户认证和状态修改等操作,要防止跨站请求伪造(CSRF)攻击。可以在请求中添加 CSRF 令牌,服务器端进行验证。
  • 数据验证和过滤:在服务器端对 API 接收到的数据进行严格的验证和过滤,防止 SQL 注入、XSS 等攻击。在客户端也要对输入数据进行适当的验证,避免恶意数据被发送到服务器。

通过以上这些方法,可以在 Qwik 项目中高效、安全地使用 fetchaxios 进行 API 调用,提升项目的整体质量和性能。