You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 08:06:04