RTK Query GraphQL如何实现后端要求的文件上传
RTK Query + graphql-request 实现GraphQL文件上传方案
你当前用的@rtk-query/graphql-request-base-query@2.1.0默认仅发送JSON格式请求,不支持GraphQL multipart上传规范,需要自定义baseQuery兼容文件上传逻辑,实现后可完全兼容你已有的普通query/mutation调用,无需修改已有接口代码。
前置依赖安装
先安装提取文件所需的工具包:
npm i extract-files
这个包的作用是递归遍历GraphQL变量,提取其中的File/Blob类型值,同时记录每个文件在变量结构中的路径,用于构造符合规范的multipart请求体。
第一步:编写支持文件上传的自定义baseQuery
替换默认的graphqlRequestBaseQuery,逻辑为:检测到变量中存在文件时自动转multipart格式请求,无文件时走原有JSON请求逻辑。
import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react'; import { ClientError, GraphQLClient } from 'graphql-request'; import { extractFiles } from 'extract-files'; // 初始化GraphQL客户端,保留你之前的所有配置(鉴权、headers、接口地址等) const gqlClient = new GraphQLClient('/graphql', { credentials: 'include', headers: { // 例:Authorization: `Bearer ${localStorage.getItem('token')}` } }); const customGqlBaseQuery = ({ client }: { client: GraphQLClient }): BaseQueryFn< { document: string; variables?: Record<string, unknown> }, unknown, Pick<ClientError, 'message' | 'response' | 'request'> > => async ({ document, variables }) => { try { // 提取变量中的所有文件类型值 const { clone: processedVariables, files } = extractFiles( variables ?? {}, (value) => value instanceof File || value instanceof Blob, 'variables' ); // 无文件时走原有普通请求逻辑,和之前的调用行为完全一致 if (files.size === 0) { const data = await client.request(document, variables); return { data }; } // 构造符合GraphQL multipart上传规范的FormData const formData = new FormData(); // 写入operations字段,包含query语句和替换文件为null的变量 formData.append( 'operations', JSON.stringify({ query: document, variables: processedVariables }) ); // 写入map字段,映射文件索引到变量中的对应路径 const fileMap: Record<string, string[]> = {}; let index = 0; files.forEach((paths) => { fileMap[index++] = paths; }); formData.append('map', JSON.stringify(fileMap)); // 写入所有文件字段,索引和map中的key一一对应 index = 0; files.forEach((_, file) => { formData.append(`${index++}`, file as Blob, (file as File).name); }); // 发送请求,复用client原有配置 const res = await fetch(client.url, { method: 'POST', headers: client.headers as HeadersInit, credentials: 'include', body: formData }); const result = await res.json(); if (result.errors) { throw new ClientError( { ...result, status: res.status }, { query: document, variables: processedVariables } ); } return { data: result.data }; } catch (err) { const error = err as ClientError; return { error: { message: error.message, response: error.response, request: error.request } }; } };
第二步:配置RTK Query的API服务
在createApi中使用自定义的baseQuery,普通接口和上传接口写法完全一致,不需要特殊区分:
export const gqlApi = createApi({ reducerPath: 'gqlApi', baseQuery: customGqlBaseQuery({ client: gqlClient }), endpoints: (builder) => ({ // 原有普通query/mutation不需要任何修改,直接保留即可 // getXXX: builder.query({...}), // updateXXX: builder.mutation({...}), // 文件上传mutation示例,入参直接传File对象即可 uploadUserAvatar: builder.mutation< { uploadAvatar: { avatarUrl: string } }, { userId: string; avatar: File } >({ query: (params) => ({ document: ` mutation UploadUserAvatar($userId: String!, $avatar: Upload!) { uploadAvatar(userId: $userId, file: $avatar) { avatarUrl } } `, variables: params }) }) }) }); // 导出自动生成的hook export const { useUploadUserAvatarMutation } = gqlApi;
第三步:组件中调用上传接口
和普通mutation的调用方式完全相同,直接将选中的File对象传入参数即可:
const AvatarUpload = () => { const [uploadAvatar] = useUploadUserAvatarMutation(); const handleSelectFile = async (e: React.ChangeEvent<HTMLInputElement>) => { const selectedFile = e.target.files?.[0]; if (!selectedFile) return; const result = await uploadAvatar({ userId: '当前登录用户ID', avatar: selectedFile }); if ('data' in result) { console.log('上传成功,头像地址:', result.data.uploadAvatar.avatarUrl); } }; return <input type="file" accept="image/*" onChange={handleSelectFile} />; };
注意事项
- 该实现完全兼容你已调试通过的所有普通query、mutation,无文件时不会触发multipart格式转换,不会影响原有接口的正常调用
- 你当前使用的
graphql-request@4.2.0版本可直接运行上述代码,若后续升级graphql-request到5.x以上大版本,需要对应调整GraphQLClient实例的属性读取逻辑 - 后端无需做额外适配,只要支持标准GraphQL Upload类型(和你之前用Apollo Client对接时的后端配置完全一致)即可正常接收文件
内容的提问来源于stack exchange,提问作者user9013856
相关产品推荐
相关产品推荐

