如何将Relay返回的GraphQL数据与Typescript自定义接口统一
方案1:封装通用的连接结构转换函数
这是成本最低、适用场景最广的方案,你可以写一个泛型工具函数专门处理Relay的Connection结构(也就是带edges/node的嵌套结构),复用转换逻辑避免每次手动构造对象:
// 通用连接类型定义 type RelayConnection<T> = { edges?: Array<{ node: T }> | null } // 拍平连接为数组 const unwrapRelayConnection = <T>(conn: RelayConnection<T> | undefined | null): T[] => { return conn?.edges?.map(edge => edge.node) ?? [] } // 针对你的User结构的转换示例 const convertRelayUserToCustom = ( relayUser: NonNullable<ReturnType<typeof useUserQuery>['data']['users']>['edges'][number]['node'] ): User => { return { id: relayUser.id, name: relayUser.name, // 如果你的credentials是单条,直接取数组第一项,是列表就直接返回unwrap的结果 credentials: unwrapRelayConnection(relayUser.credentials)[0] } }
方案2:全局配置Relay自动转换返回结构
如果你的项目里所有连接结构都不需要用到cursor、pageInfo等分页元数据,可以直接在Relay的环境配置里添加响应转换器,全局自动把所有Connection结构拍平,你拿到的查询结果直接就是符合你自定义接口的结构,不需要手动调用转换函数:
import { Environment, Network, RecordSource, Store } from 'relay-runtime' // 递归拍平所有连接结构 const flattenRelayConnections = (data: any): any => { if (Array.isArray(data)) return data.map(flattenRelayConnections) if (typeof data !== 'object' || data === null) return data // 识别到edges字段就拍平 if ('edges' in data && Array.isArray(data.edges)) { return data.edges.map(edge => flattenRelayConnections(edge.node)) } return Object.fromEntries( Object.entries(data).map(([k, v]) => [k, flattenRelayConnections(v)]) ) } // 配置到Relay Network层 const network = Network.create(async (operation, variables) => { const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: operation.text, variables }) }) const result = await response.json() // 转换返回结果 if (result.data) result.data = flattenRelayConnections(result.data) return result }) export const environment = new Environment({ network, store: new Store(new RecordSource()) })
方案3:基于Relay生成的类型派生自定义接口
如果你只是不想手动维护两套类型定义,不需要转换实际数据结构,可以直接从Relay自动生成的查询类型中派生你的自定义接口,保证类型永远和查询同步,避免手动写接口导致的类型不一致问题:
// 引入Relay自动生成的查询类型 import type { UserQuery$data } from './__generated__/UserQuery.graphql' type RelayUser = NonNullable<UserQuery$data['users']>['edges'][number]['node'] type RelayCredentials = NonNullable<RelayUser['credentials']>['edges'][number]['node'] // 直接派生你的自定义接口 interface User extends Pick<RelayUser, 'id' | 'name'> { credentials: UserCredentials } interface UserCredentials extends Pick<RelayCredentials, 'id' | 'value'> {}
关于是否直接遍历edges渲染的问题
大部分业务场景下如果需要用到分页功能,确实会直接遍历edges,因为edges中携带的cursor字段是实现Relay分页的必要参数,拍平结构会导致这部分元数据丢失。如果你的列表不需要分页,或者不需要在渲染层处理分页逻辑,用上面的转换方案拍平结构是完全没问题的,也是很多团队的常用实践。
内容的提问来源于stack exchange,提问作者arano879n
相关产品推荐
相关产品推荐

