- 前端
- Web框架
- SSR
- 前端构建
- 插件系统
- 微前端
- 跨平台
【免费下载链接】ice
🚀 ice.js: The Progressive App Framework Based On React(基于 React 的渐进式应用框架)
导读
前端应用大多通过 HTTP(s) 协议与后端服务通信,请求的发起、状态管理、错误处理与全局配置往往散落在各页面代码中,难以维护。ice.js 提供了一套从 UI 交互到请求服务端数据的完整方案:@ice/plugin-request插件基于 axios 封装request能力,并基于 ahooks 的useRequest封装了请求状态管理,通过约定式services目录和requestConfig全局配置,以切面编程的方式统一了数据请求管理。读完本文,你将掌握插件安装、services 目录约定、模型/视图两种消费方式、request与useRequest的完整 API,以及拦截器、多实例、环境化 baseURL 等实战配置。
安装 request 插件
网络请求是 ice.js 的可选能力,使用前需要单独安装@ice/plugin-request插件:
npm i @ice/plugin-request -D安装后在应用配置文件ice.config.mts中注册插件:
import { defineConfig } from '@ice/app'; import request from '@ice/plugin-request'; export default defineConfig(() => ({ plugins: [ request(), ], }));插件被注册后,框架会在构建期通过generator.addExport将useRequest与request两个导出注入到ice包入口,这也是后续代码中可以直接import { request, useRequest } from 'ice'的原理(见 插件入口实现)。插件同时声明了runtime: '@ice/plugin-request/runtime'与staticRuntime: true,表示它需要在应用启动阶段执行运行时逻辑(解析requestConfig并初始化 axios 实例)。
仓库中提供了可直接运行的完整示例 examples/with-request,其package.json中已声明@ice/plugin-request依赖,可通过npm start启动本地开发、npm run build构建验证效果。
目录约定
框架约定services目录用于收敛请求逻辑,支持全局级与页面级两种粒度:
src ├── models +├── services // 定义全局数据请求,非必须 +│ └── user.ts └── pages | ├── home | │ ├── models +| │ ├── services // 定义页面级数据请求 +| │ | └── repo.ts | │ └── components | ├── about | │ ├── services | │ ├── components | │ └── index.tsx └── app.ts在services文件中通过request定义数据请求,例如页面级服务pages/home/service/repo.ts:
import { request } from 'ice'; export default { // 简单场景 async getUser() { return await request('/api/user'); }, // 参数场景 async getRepo(id) { return await request(`/api/repo/${id}`); }, // 格式化返回值 async getDetail(params) { const data = await request({ url: `/api/detail`, params }); return data.map(item => { return { ...item, price: item.oldPrice, text: item.status === '1' ? '确定' : '取消' }; }); } }getDetail展示了把"格式化返回值"的职责收敛在 service 层的典型做法:页面只关心最终数据形态,业务字段映射统一在 service 内完成。该写法与仓库示例 examples/with-request/src/service.ts 保持一致。
消费 service
消费 service 主要有两种方式:
- 在模型中调用 service:
service->model->view - 在视图中调用 service:
service->view
在模型中调用 service
该方式需要结合 ice.js 的状态管理 store 一起使用。
service:约定数据请求统一管理在 services 目录下;model:约定数据请求统一在 models 里进行调用;view:最终在视图里通过调用 models 的 effects 方法触发数据请求。
在模型中调用定义好的 service:
import userService from '@/services/user'; // src/models/user.ts export default { state: { name: 'taoxiaobao', age: 20, }, reducers: { update(prevState, payload) { return { ...prevState, ...payload }; }, }, effects: (dispatch) => ({ async fetchUserInfo() { const data = await userService.getUser(); dispatch.user.update(data); }, }), };数据流是单向的:service 负责发请求拿数据,model 的effects调用 service 并把结果通过reducers写入 state,view 只负责通过 dispatchers 触发方法并消费状态。
在视图中调用模型方法:
import React, { useEffect } from 'react'; import store from '@/store'; const HomePage = () => { // 调用定义的 user 模型 const [userState, userDispatchers] = store.useModel('user'); useEffect(() => { // 调用 user 模型中的 fetchUserInfo 方法 userDispatchers.fetchUserInfo(); }, []); return <>Home</>; };在视图中调用 service
service:约定数据请求统一管理在 services 目录下;view:最终在视图里通过useRequest直接调用 service 触发数据请求。
import React, { useEffect } from 'react'; import { useRequest } from 'ice'; import userService from '@/services/user'; export default function HomePage() { // 调用 service const { data, error, loading, request } = useRequest(userService.getUser); useEffect(() => { // 触发数据请求 request(); }, []); return <>Home</>; }该模式无需引入 store,适合请求结果不需要跨页面共享的场景。仓库示例 examples/with-request/src/pages/home.tsx 完整演示了结合loading/error/data渲染加载中、失败与成功三种状态的做法。
API
request
request基于 axios 进行封装,使用上与 axios 整体保持一致,差异点如下:
- 默认只返回服务端响应的数据
Response.data,而不是整个 Response;如需返回整个 Response,请通过withFullResponse参数开启; - 在 axios 基础上默认支持了多请求实例的能力(通过
instanceName指定)。
使用方式如下:
import { request } from 'ice'; async function getList() { const resData = await request({ url: '/api/user', }); console.log(resData.list); const { status, statusText, data } = await request({ url: '/api/user', withFullResponse: true }); console.log(data.list); }常用调用形式:
request(RequestConfig); request.get('/user', RequestConfig); request.post('/user', data, RequestConfig);request同时支持get/delete/head/options/post/put/patch等快捷方法,并在实例上暴露了Cancel、CancelToken、isCancel,用于取消请求等 axios 高级能力(见 request 实现)。
RequestConfig 完整字段:
{ // `url` is the server URL that will be used for the request url: '/user', // `method` is the request method to be used when making the request method: 'get', // default // `headers` are custom headers to be sent headers: {'X-Requested-With': 'XMLHttpRequest'}, // `params` are the URL parameters to be sent with the request // Must be a plain object or a URLSearchParams object params: { ID: 12345 }, // `data` is the data to be sent as the request body // Only applicable for request methods 'PUT', 'POST', and 'PATCH' data: { firstName: 'Fred' }, // `timeout` specifies the number of milliseconds before the request times out. // If the request takes longer than `timeout`, the request will be aborted. timeout: 1000, // default is `0` (no timeout) // `withCredentials` indicates whether or not cross-site Access-Control requests // should be made using credentials withCredentials: false, // default // `responseType` indicates the type of data that the server will respond with // options are: 'arraybuffer', 'document', 'json', 'text', 'stream' responseType: 'json', // default // should be made return full response withFullResponse: false, // request instance name instanceName: 'request2' }RequestConfig其余字段与 axios 请求配置完全对齐。需要说明的是,instanceName与withFullResponse是插件扩展的两个 ice.js 专属字段,底层 axios 实例内部维护了一个以default为默认键的实例表,request每次调用时根据instanceName取对应实例(见 request.ts)。
开启withFullResponse后返回的完整 Response Scheme 如下:
{ // `data` is the response that was provided by the server data: {}, // `status` is the HTTP status code from the server response status: 200, // `statusText` is the HTTP status message from the server response statusText: 'OK', // `headers` the HTTP headers that the server responded with // All header names are lower cased and can be accessed using the bracket notation. // Example: `response.headers['content-type']` headers: {}, // `config` is the config that was provided to `axios` for the request config: {}, // `request` is the request that generated this response // It is the last ClientRequest instance in node.js (in redirects) // and an XMLHttpRequest instance in the browser request: {} }useRequest
useRequest用于简化请求状态管理,它基于 ahooks 的useRequest封装,差异点有三处:
- 将
requestMethod参数默认设置为上述的request(即 axios),保证框架使用的一致性; manual参数默认值从false改为true,因为实际业务中更多是手动触发请求;- 返回值
run改名为request,语义更贴近"发起请求";同时runAsync对应改名为requestAsync。
这些差异在源码 hooks.ts 中有直接体现:useRequest会先把字符串地址或 Axios 配置对象归一化为 service 函数,再调用 ahooks 的useRequest(默认manual: true),最后把run/runAsync重命名为request/requestAsync返回。
API
const { // 请求返回的数据,默认为 undefined data, // 请求抛出的异常,默认为 undefined error, // 请求状态 loading, // 手动触发请求,参数会传递给 service request, // 当次执行请求的参数数组 params, // 取消当前请求,如果有轮询,停止 cancel, // 使用上一次的 params,重新执行请求 refresh, // 直接修改 data mutate, // 默认情况下,新请求会覆盖旧请求。如果设置了 fetchKey,则可以实现多个请求并行,fetches 存储了多个请求的状态 fetches } = useRequest(service, { // 默认为 true 即需要手动执行请求 manual, // 初始化的 data initialData, // 请求成功时触发,参数为 data 和 params onSuccess, // 请求报错时触发,参数为 error 和 params onError, // 格式化请求结果 formatResult, // 请求唯一标识 cacheKey, // 设置显示 loading 的延迟时间,避免闪烁 loadingDelay, // 默认参数 defaultParams, // 轮询间隔,单位为毫秒 pollingInterval, // 在页面隐藏时,是否继续轮询,默认为 true,即不会停止轮询 pollingWhenHidden, // 根据 params,获取当前请求的 key fetchKey, // 在屏幕重新获取焦点或重新显示时,是否重新发起请求。默认为 false,即不会重新发起请求 refreshOnWindowFocus, // 屏幕重新聚焦,如果每次都重新发起请求,不是很好,我们需要有一个时间间隔,在当前时间间隔内,不会重新发起请求,需要配置 refreshOnWindowFocus 使用 focusTimespan, // 防抖间隔, 单位为毫秒,设置后,请求进入防抖模式 debounceInterval, // 节流间隔, 单位为毫秒,设置后,请求进入节流模式。 throttleInterval, // 只有当 ready 为 true 时,才会发起请求 ready, // 在 manual = false 时,refreshDeps 变化,会触发请求重新执行 refreshDeps, });常用使用方式
useRequest的第一个参数支持三种形态:请求地址字符串、Axios 配置对象、service 函数。
import { useRequest } from 'ice'; // 用法 1:传入请求地址 const { data, error, loading, request } = useRequest('/api/repo'); request(); // 用法 2:传入 Axios 配置对象 const { data, error, loading, request } = useRequest({ url: '/api/repo', method: 'get', }); request(); // 用法 3:传入 service 函数 const { data, error, loading, request } = useRequest((id) => Promise.resolve({ url: '/api/repo', method: 'get', data: { id }, })); request();service函数的返回值可以是 Promise 数据,也可以是 Axios 配置对象;若返回配置对象,useRequest会将其透传给request发起真实请求。其余能力(如分页、加载更多等)与 ahooksuseRequest保持一致。
请求配置
实际项目中通常需要对请求进行全局统一的封装,例如配置baseURL、统一 header、拦截请求和响应等,此时只需在应用的appConfig中导出requestConfig即可。插件在运行时阶段会读取应用导出的requestConfig,并用它初始化 axios 实例与拦截器(见 runtime.ts)。
import { defineRequestConfig } from '@ice/plugin-request/types'; export const requestConfig = defineRequestConfig({ // 可选的,全局设置 request 是否返回 response 对象,默认为 false withFullResponse: false, baseURL: '/api', headers: {}, // ...RequestConfig 其他参数 // 拦截器 interceptors: { request: { onConfig: (config) => { // 发送请求前:可以对 RequestConfig 做一些统一处理 config.headers = { a: 1 }; return config; }, onError: (error) => { return Promise.reject(error); }, }, response: { onConfig: (response) => { // 请求成功:可以做全局的 toast 展示,或者对 response 做一些格式化 if (!response.data.status !== 1) { alert('请求失败'); } return response; }, onError: (error) => { // 请求出错:服务端返回错误状态码 console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); return Promise.reject(error); }, }, }, });defineRequestConfig是插件提供的类型辅助函数,接受配置对象或返回配置对象的函数,并做类型收窄(见 types.ts)。从底层实现看,setAxiosInstance会把interceptors之外的字段逐一写入 axios 实例的defaults,再注册请求/响应拦截器;同时通过比对fulfilled/rejected函数引用来避免重复注册(见 request.ts)。
仓库示例 examples/with-request/src/app.tsx 提供了完整的requestConfig示例,其中响应onError演示了针对特定接口(/api/user)注入 Mock 数据回填的做法,可作为联调参考。
多个请求配置
在某些复杂场景下(如需要对接多个后端服务),可以配置多个请求实例,每个配置对应一个独立的 axios 实例对象。
import { defineRequestConfig } from '@ice/plugin-request/types'; export const requestConfig = defineRequestConfig([ { baseURL: '/api', // ...RequestConfig 其他参数 }, { // 配置 request 实例名称,如果不配默认使用内置的 request 实例 instanceName: 'request2', baseURL: '/api2', // ...RequestConfig 其他参数 } ]);从源码看,当requestConfig是数组时,运行时会对每一项依次执行createAxiosInstance(instanceName)与setAxiosInstance,实例名缺省时统一落到default实例上(见 runtime.ts)。
使用示例:
import { request } from 'ice'; export default { // 使用默认的请求方法,即调用 /api/user 接口 async getUser() { return await request({ url: '/user', }); }, // 使用自定义的 request 请求方法,即调用接口 /api2/user async getRepo(id) { return await request({ instanceName: 'request2', url: `/repo/${id}`, }); }, };异常处理
无论是拦截器里的错误参数,还是request/useRequest返回的错误对象,都符合以下统一结构:
const error = { // 服务端返回错误状态码时则存在该字段 response: { data: {}, status: {}, headers: {} }, // 服务端未返回结构时则存在该字段 request: XMLHttpRequest, // 一定存在,即 RequestConfig config: { }, // 一定存在 message: '' }该结构继承自 axios 的错误对象。request内部捕获异常后会先console.error再抛出(见 request.ts),因此在业务层既可捕获error.response判断服务端错误码,也可依赖error.message做兜底提示。建议在interceptors.response.onError中统一处理鉴权失效、网络异常等全局逻辑,避免每个页面重复编写错误分支。
高阶用法
Mock 接口
项目开发初期,后端接口可能还没开发好或不够稳定,此时前端可以通过 Mock 的方式来模拟接口,参考文档 本地 Mock 能力。此外,也可以如 examples/with-request/src/app.tsx 所示,在响应拦截器的onError分支里针对特定 URL 返回模拟数据,作为联调兜底。
如何解决接口跨域问题
当访问页面地址和请求接口地址的域名或端口不一致时,会因浏览器的同源策略产生跨域问题。推荐做法是由后端接口通过 CORS 支持信任域名的跨域访问(如配置Access-Control-Allow-Origin响应头),前端配合withCredentials处理带凭证的跨域请求。CORS 的具体机制可参考 MDN 的 HTTP 访问控制(CORS)相关文档。
根据环境配置不同的 baseURL
大多数情况下,前端代码里写的后端接口都是相对路径(如/api/getFoo.json),访问不同环境时浏览器会根据当前域名发起对应请求。如果实际请求的接口域名与页面域名不一致,则需要通过request.baseURL配置:
import { defineRequestConfig } from '@ice/plugin-request/types'; export const requestConfig = defineRequestConfig({ baseURL: '//service.example.com/api', });结合 ice.js 的构建环境配置即可实现不同环境使用不同的 baseURL:先在各环境的 env 文件中声明变量:
# The should not be committed. ICE_BASE_URL=http://localhost:9999/apiICE_BASE_URL=https://example.com/api再在src/app.tsx中通过process.env.ICE_BASE_URL引用:
import { defineRequestConfig } from '@ice/plugin-request/types'; export const requestConfig = defineRequestConfig({ baseURL: process.env.ICE_BASE_URL, });这样本地开发、测试、生产环境各自使用独立的接口域名,且.env.local不应提交到版本库,避免本地配置污染公共代码。
小结
@ice/plugin-request为 ice.js 应用提供了"约定优先、配置统一"的数据请求方案:services目录约定收敛请求逻辑,request负责发请求并默认解包Response.data,useRequest负责请求状态与生命周期管理,requestConfig负责全局拦截器与多实例配置。本文涉及的源码实现位于 packages/plugin-request/src,可直接运行的完整示例位于 examples/with-request,建议结合示例代码动手实践,理解拦截器、多实例与环境化 baseURL 等配置的实际效果。
- 前端
- Web框架
- SSR
- 前端构建
- 插件系统
- 微前端
- 跨平台
【免费下载链接】ice
🚀 ice.js: The Progressive App Framework Based On React(基于 React 的渐进式应用框架)
相关推荐
HTTP请求库:使用Java轻松处理HTTP请求
HTTP请求库:使用Java轻松处理HTTP请求 如果你正在寻找一个易于使用的Java库来处理HTTP请求,那么这个名为"http request"的项目可能是
后端OpenRL强化学习框架:一站式解决单智能体、多智能体与自然语言任务训练
OpenRL强化学习框架:一站式解决单智能体、多智能体与自然语言任务训练 OpenRL是一个开源的通用强化学习框架,专为简化人工智能训练而设计。这款强大的 Op
Hasura v3 pre-ndc-request 插件示例实战:用 pre-ndc-request-plugin-example 在请求到达数据连接器前改写 NDC 请求
Hasura v3 pre ndc request 插件示例实战:用 pre ndc request plugin example 在请求到达数据连接器前改写
后端API网关数据库GraphQL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考