React Native中用RTK Query的GraphQLClient传文件报错:$file值无效
问题原因分析
报错Variable "$file" got invalid value, Upload value invalid的核心原因:
- 当前使用的
graphql-request库的GraphQLClient默认以application/json格式发送请求,不支持文件上传必需的multipart/form-data协议,也无法识别apollo-upload-client提供的ReactNativeFile类型变量。 - 后端文件上传端点要求接收符合GraphQL Multipart Request规范的请求,但你的baseQuery没有将文件变量转换为对应的表单格式。
解决办法
方案一:改用Apollo Client作为GraphQL请求客户端(推荐,适配apollo-upload-client)
- 配置带上传支持的Apollo Client
import { ApolloClient, InMemoryCache } from '@apollo/client'; import { createUploadLink } from 'apollo-upload-client'; import { setContext } from '@apollo/client/link/context'; // 替换原httpLink为支持上传的createUploadLink const uploadLink = createUploadLink({ uri: process.env.REACT_APP_GRAPHQL_URL, credentials: 'include', }); // 可添加认证逻辑(按需) const authLink = setContext((_, { headers }) => { return { headers: { ...headers, 'Access-Control-Allow-Credentials': 'true', }, }; }); const client = new ApolloClient({ link: authLink.concat(uploadLink), cache: new InMemoryCache(), });
- 调整RTK Query的baseQuery为Apollo Client适配版本
import { createApi } from '@reduxjs/toolkit/query/react'; import { graphqlBaseQuery } from '@rtk-query/apollo-base-query'; export const api = createApi({ baseQuery: graphqlBaseQuery({ client }), // 使用配置好的Apollo Client endpoints: () => ({}), });
- 原有mutation调用代码无需修改,Apollo Client会自动处理
ReactNativeFile的转换和multipart请求发送
方案二:修改现有graphql-request的baseQuery,手动处理文件上传
如果不想切换客户端,可手动改造baseQuery以支持文件上传:
- 安装依赖(React Native环境)
yarn add react-native-form-data # 或 npm install react-native-form-data
- 修改
rawBaseQueryGQL代码:
import FormData from 'react-native-form-data'; import { ReactNativeFile } from 'apollo-upload-client'; import { print } from 'graphql'; export const rawBaseQueryGQL: BaseQueryFn< IGQLBaseQuery, unknown, Partial<ClientError & { errorCode: number }> > = async ({ document, variables }, api, extraOptions) => { // 检测变量中是否包含ReactNativeFile实例 const hasUpload = Object.values(variables || {}).some(v => v instanceof ReactNativeFile); if (hasUpload) { // 构建multipart/form-data请求 const formData = new FormData(); // 添加GraphQL查询和变量 formData.append('operations', JSON.stringify({ query: print(document), variables: variables, })); // 构建文件映射关系 const fileMap: Record<string, string[]> = {}; let fileIndex = 0; Object.entries(variables || {}).forEach(([key, value]) => { if (value instanceof ReactNativeFile) { const fileKey = `file${fileIndex}`; fileMap[fileKey] = [`variables.${key}`]; formData.append(fileKey, value as any); fileIndex++; } }); formData.append('map', JSON.stringify(fileMap)); // 发送请求 try { const response = await fetch(process.env.REACT_APP_GRAPHQL_URL, { method: 'POST', credentials: 'include', body: formData, headers: { 'Access-Control-Allow-Credentials': 'true', // 无需手动设置Content-Type,FormData会自动添加boundary }, }); const result = await response.json(); if (result.errors) { const { errorMessage = "", errorCode = 500 } = result.errors[0]?.extensions || {}; return { error: { name: 'GraphQLError', message: errorMessage, errorCode, }, }; } return { data: result.data }; } catch (err) { return { error: err as any }; } } // 非文件上传请求,走原有逻辑 const client = new GraphQLClient( process.env.REACT_APP_GRAPHQL_URL, { credentials: "include" } ); client.setHeader("Accept", "application/json"); client.setHeader("Content-Type", "application/json"); client.setHeader("Access-Control-Allow-Credentials", "true"); const query = graphqlRequestBaseQuery< Partial<ClientError & { errorCode: number }> >({ client, customErrors: ({ name, stack, response }) => { const { errorMessage = "", errorCode = 500 } = response?.errors?.length ? response?.errors[0]?.extensions : {}; return { name, message: errorMessage, errorCode, stack }; }, }); return query({ document, variables }, api, extraOptions); };
内容的提问来源于stack exchange,提问作者Zim
相关产品推荐
相关产品推荐

