@jiess/http 介绍与上手
@jiess/http 是一个配置式前端 HTTP 请求库,以分组为单位定义接口,让开发者用尽可能少的代码完成 API 的声明、调用与状态管理。
核心亮点
| 特性 | 说明 |
|---|---|
| 🔥 并发去重分发 | 同一接口并发请求仅发一次网络调用,结果分发给所有等待者 |
| 📝 一行定义接口 | 数组式配置,['GET', '/users/:id'] 即完成一个接口声明 |
| 🚀 请求重试机制 | 支持线性/指数退避重试,可配置次数与延迟 |
| 🛡 安全防护 | Content-Type 校验、URL 编码、请求体大小限制 |
| 📦 请求合并缓存 | 基于 DJB2 哈希的请求体指纹,精准识别重复请求 |
| 🔌 双层拦截器 | 全局 + 接口级拦截器,按固定顺序执行 |
安装
bash
npm install @jiess/http
# 或
yarn add @jiess/http注意:该库默认依赖浏览器环境的
window.fetch或XMLHttpRequest;通过配置customFetch也可在 Node.js 等非浏览器环境使用。
快速开始
第一步:创建 Http 实例
typescript
import Http from '@jiess/http'
const http = new Http({
baseURL: 'https://api.example.com',
timeout: 10000, // 超时时间(毫秒)
headers: { // 全局请求头
'Content-Type': 'application/json'
},
useCache: true, // 启用请求缓存
httpCacheParam: {
waitTime: 5000 // 缓存有效期(毫秒)
}
})第二步:定义 API 接口
typescript
const userApi = http.create('user', {
getUser: ['GET', '/users/:id'],
getUsers: ['GET', '/users'],
createUser: ['POST', '/users'],
updateUser: ['PUT', '/users/:id'],
deleteUser: ['DELETE', '/users/:id']
})第三步:调用接口
typescript
// GET 请求:第一个参数为路径参数
const user = await userApi.getUser(1)
// 请求 URL → /users/1
// GET 带查询参数:传入对象
const users = await userApi.getUsers({ page: 1, limit: 10 })
// 请求 URL → /users?page=1&limit=10
// POST 请求:第一个参数为请求体
const newUser = await userApi.createUser({
name: '张三',
age: 25
})
// PUT 请求:路径参数 + 请求体
await userApi.updateUser(1, { name: '李四' })配置规则详解
每个接口由数组定义,格式如下:
typescript
http.create('分组名', {
接口名: [
请求方法, // 必填:GET / POST / PUT / PATCH / DELETE
请求地址, // 必填:字符串或函数
基础数据?, // 可选:headers / query / body 等默认参数
拦截器? // 可选:request / responseSuccess / responseError
]
})基础配置
typescript
const api = http.create('user', {
getUser: ['GET', '/users/:id'],
createUser: ['POST', '/users'],
updateUser: ['PUT', '/users/:id'],
deleteUser: ['DELETE', '/users/:id']
})带基础数据的配置
typescript
const api = http.create('user', {
searchUsers: ['GET', '/users/search', {
headers: { 'X-Custom-Header': 'search' },
responseType: 'json'
}]
})带拦截器的配置
typescript
const api = http.create('user', {
getUser: [
'GET',
'/users/:id',
{}, // 基础数据
{ // 接口级拦截器
responseError: (error) => {
if (error.response?.status === 404) {
return { defaultUser: true }
}
throw error
}
}
]
})参数传递规则
不同 HTTP 方法的参数语义不同:
| 方法 | 第一个参数 | 第二个参数 |
|---|---|---|
| GET / DELETE / HEAD | query 查询参数 | body 请求体 |
| POST / PUT / PATCH | body 请求体 | query 查询参数 |
typescript
// POST:body → 请求体,query → 查询参数
await api.createUser({ name: '张三' }, { timestamp: Date.now() })
// URL: /users?timestamp=1700000000
// Body: {"name":"张三"}
// GET:query → 查询参数
await api.getUsers({ page: 1, status: 'active' })
// URL: /users?page=1&status=active动态 URL
支持路径参数替换、函数式构建和异步函数:
typescript
const api = http.create('user', {
// 路径参数替换 → /users/1
byId: ['GET', '/users/:id'],
// 同步函数式 → /users/1?name=张三&extra=value
byFunc: ['GET', (id, name) => `/users/${id}?name=${encodeURIComponent(name)}&extra=value`],
// 异步函数式 → 可异步获取参数后再构建
byAsync: ['GET', async (token) => {
const id = await resolveId(token)
return `/users/${id}`
}]
})错误处理
typescript
// 1. try-catch 基本处理
try {
const user = await userApi.getUser(999)
} catch (error) {
console.error('请求失败:', error)
}
// 2. 全局拦截器统一处理
const http = new Http({
interceptors: {
responseError: (error) => {
switch (error.code) {
case 401: /* 跳转登录 */ break
case 403: /* 无权限提示 */ break
case 404: /* 资源不存在 */ break
default: /* 通用错误 */ break
}
return error
}
}
})完整端到端示例
以下是一个从初始化到渲染的完整 React 组件示例,展示 @jiess/http 在实际项目中的典型用法:
typescript
// -------- api/http.ts --------
import Http from '@jiess/http'
export const http = new Http({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
useCache: true,
httpCacheParam: { waitTime: 5000 },
retry: { count: 2, delay: 1000, backoff: 'exponential' },
interceptors: {
request: (config) => {
const token = localStorage.getItem('token')
if (token) config.headers['Authorization'] = `Bearer ${token}`
return config
},
responseError: (error) => {
if (error.code === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
return error
}
}
})
// -------- api/user.ts --------
import { http } from './http'
export const userApi = http.create('user', {
getUser: ['GET', '/users/:id'],
getUsers: ['GET', '/users'],
createUser: ['POST', '/users'],
updateUser: ['PUT', '/users/:id'],
deleteUser: ['DELETE', '/users/:id']
})
// -------- components/UserList.tsx --------
import { useState, useEffect } from 'react'
import { userApi } from '../api/user'
import type { TypeConfig } from '@jiess/http'
function UserList() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const fetchUsers = async (page = 1) => {
setLoading(true)
setError(null)
try {
const result = await userApi.getUsers({ page, limit: 20 })
// 响应结构(fetch 模式):{ responseText, response, config }
// 响应结构(XHR 模式):{ responseText, xhr, config }
setUsers(result.responseText.data || [])
} catch (err) {
setError('获取用户列表失败,请稍后重试')
console.error('请求错误:', err)
} finally {
setLoading(false)
}
}
useEffect(() => { fetchUsers() }, [])
if (error) return <div className="error-msg">{error}</div>
return (
<div>
<h2>用户列表 {loading && '(加载中...)'}</h2>
<table>
<thead>
<tr><th>姓名</th><th>年龄</th><th>操作</th></tr>
</thead>
<tbody>
{users.map((u: any) => (
<tr key={u.id}>
<td>{u.name}</td>
<td>{u.age}</td>
<td>
<button onClick={async () => {
try {
await userApi.deleteUser(u.id)
fetchUsers() // 删除成功后刷新列表
} catch (err) {
alert('删除失败')
}
}}>删除</button>
</td>
</tr>
))}
</tbody>
</table>
{/* 创建用户弹窗 */}
<CreateUserDialog onSuccess={() => fetchUsers()} />
</div>
)
}
// -------- components/CreateUserDialog.tsx --------
function CreateUserDialog({ onSuccess }: { onSuccess: () => void }) {
const [submitting, setSubmitting] = useState(false)
const [formData, setFormData] = useState({ name: '', age: 0 })
const handleSubmit = async () => {
setSubmitting(true)
try {
await userApi.createUser(formData)
onSuccess() // 通知父组件刷新
setFormData({ name: '', age: 0 }) // 重置表单
} catch (err) {
alert('创建失败: ' + (err as Error).message)
} finally {
setSubmitting(false)
}
}
return (
<div className="dialog">
<input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="姓名"
/>
<input
type="number"
value={formData.age}
onChange={(e) => setFormData({ ...formData, age: Number(e.target.value) })}
placeholder="年龄"
/>
<button onClick={handleSubmit} disabled={submitting}>
{submitting ? '提交中...' : '创建'}
</button>
</div>
)
}代码要点:
http实例统一管理 baseURL、timeout、retry 和拦截器userApi按业务域独立文件,create()一行定义增删改查- 组件通过
try/catch/finally控制 loading/error/success 三态 - 删除/创建成功后调用
fetchUsers()刷新,形成完整的 CRUD 闭环
全局配置速查
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
baseURL | string | '' | 全局基础 URL |
timeout | number | undefined | 请求超时(毫秒) |
useFetch | boolean | true | 使用 fetch 或 XHR |
useCache | boolean | true | 启用请求缓存 |
httpCacheParam | object | {} | 缓存参数,含 waitTime |
headers | object | {} | 全局请求头 |
interceptors | object | {} | 全局拦截器 |
customFetch | function | — | 自定义 fetch 实现 |
httpBefore | function | — | 请求前钩子 |
maxBodySize | number | 10MB | 请求体大小限制(字节) |
debug | boolean | false | 调试模式 |
retry | object | — | 重试配置(见下文) |
重试配置
typescript
const http = new Http({
retry: {
count: 3, // 最大重试次数
delay: 1000, // 延迟(毫秒)
backoff: 'exponential', // 'linear' | 'exponential'
onRetry: (error, attempt) => {
console.log(`第 ${attempt} 次重试,错误:`, error)
}
}
})linear:每次延迟相同exponential:延迟 =delay × 2^(attempt-1)
模块化组织
推荐按业务域拆分 API 定义:
typescript
// api/http.ts
import Http from '@jiess/http'
export const http = new Http({
baseURL: 'https://api.example.com',
headers: { 'Content-Type': 'application/json' }
})
// api/user.ts
import { http } from './http'
export const userApi = http.create('user', {
getUser: ['GET', '/users/:id'],
createUser: ['POST', '/users'],
})
// api/order.ts
import { http } from './http'
export const orderApi = http.create('order', {
getOrders: ['GET', '/orders'],
getOrder: ['GET', '/orders/:id'],
})
// api/index.ts
export { userApi } from './user'
export { orderApi } from './order'常见问题
如何处理跨域请求?
@jiess/http 不内置跨域处理,跨域问题需要由服务端配置 CORS 响应头。如果在开发阶段需要绕过跨域,可使用 webpack-dev-server proxy 或 Vite proxy:
typescript
// vite.config.ts
export default {
server: {
proxy: { '/api': 'http://localhost:8080' }
}
}请求超时了会怎样?
超时后,Factory.toRequest() 的 Promise.race 会触发超时 Promise,返回一个 { code: 408, msg: '请求超时' } 结构的错误对象。如果你配置了 retry,超时错误同样会触发重试:
typescript
const http = new Http({
timeout: 5000,
retry: { count: 2, delay: 1000 }
})
// 请求超过 5 秒 → 51000ms 后重试一次 → 最多重试 2 次 → 仍失败则抛出如何取消一个请求?
使用 AbortController:
typescript
const controller = new AbortController()
// 方式一:接口配置中传入 signal
const api = http.create('user', {
getUsers: ['GET', '/users', { signal: controller.signal }]
})
// 方式二:调用时传入 info 参数
api.getUsers({}, {}, { signal: controller.signal })
// 2秒后取消
setTimeout(() => controller.abort(), 2000)GET 和 POST 的参数到底怎么传?
规则很简单——看方法名:
| 调用方式 | URL 效果 | 说明 |
|---|---|---|
api.getUser(1) | /users/1 | 数字/字符串参数自动替换 :id |
api.getUsers({ page: 1 }) | /users?page=1 | 对象参数拼接为 query string |
api.createUser({ name: '张三' }) | POST /users + body {"name":"张三"} | POST/PUT/PATCH 时对象作为请求体 |
api.updateUser(1, { name: '李四' }) | PUT /users/1 + body {"name":"李四"} | 路径参数 + 请求体 |
响应数据怎么取?
接口返回结构因底层实现(fetch / XHR / customFetch)而略有差异,公共字段为 config:
typescript
{
responseText?: any, // 解析后的响应体(fetch 模式下根据 responseType 解析;XHR 模式始终存在)
response?: Response, // fetch 模式:原生 fetch Response 对象
xhr?: XMLHttpRequest, // XHR 模式:原生 XMLHttpRequest 对象
config: TypeConfig // 本次请求的完整配置
}字段说明:
- fetch 模式(
useFetch: true且未配置customFetch):返回{ config, response, responseText? },responseText在responseType对应方法可用时存在- XHR 模式(
useFetch: false):返回{ config, xhr, responseText },注意字段名为xhr而非response- customFetch 模式:返回
{ config, ...customFetch返回值 },由用户自定义结构
取数据时通常使用 .responseText。如果后端返回 { code: 0, data: [...] },则在拦截器中做解包:
typescript
interceptors: {
responseSuccess: (res) => {
const data = res.responseText
if (data.code !== 0) throw new Error(data.msg || '请求失败')
return data.data // 直接返回 data 字段
}
}缓存什么时候生效?
- 仅 GET 请求才会缓存(POST/PUT/PATCH/DELETE 自动跳过)
- 以下情况不会缓存:自定义
type参数、显式useCache: false、请求体包含文件 - 缓存键 =
httpKey + URL + query + headers + body 哈希,参数完全相同时命中 - 缓存有效期由
httpCacheParam.waitTime控制,过期自动清除
最佳实践
- 按业务域拆分:不同业务模块独立定义 API 文件
- 统一错误拦截:在全局拦截器中处理 401/403/500 等通用错误
- 合理使用缓存:数据变化不频繁的接口开启缓存,实时数据关闭缓存
- 设置超时时间:根据接口预期响应时间合理配置,避免无限等待
- 路由级 Loading:结合 Loading 类监听接口状态,提供精确的加载反馈
文档导航
| 文档 | 内容 |
|---|---|
| 资源请求工具 | Http / Factory / Request / Cache 类详解、配置参考、安全机制、错误类型 |
| 资源请求监听 | Loading 类、上传进度、拦截器监听、状态管理集成 |