先记住这个答案
Interface 定义一组必须字段,所有实现该接口的类型都必须包含这些字段,因此若客户端只需公共字段,可直接选择,无需关心具体类型。Union 只列出可能成员类型,不声明任何共享字段,所以查询时必须用内联片段按具体类型选择字段。当多个类型有相同且稳定的字段时用 Interface;当类型之间无重叠且需要独立展示时用 Union。服务端通过 resolveType 或元数据判断实际类型。
- Interface 共享公共字段,Union 全无共享字段
- 查询公共属性用 Interface,异构展示用 Union
- Union 必须为每个成员写内联片段
机制差异:字段约束与类型解析
GraphQL 的 Interface 类型要求所有实现它的对象类型必须包含它声明的全部字段。在 SDL 中,interface Node { id: ID! } 之后,任何 type User implements Node 都必须定义 id。因此,当查询返回类型是 Node 时,客户端可以直接选择 id,不需要知道返回的是 User 还是 Post。Union 类型则不同,它仅列出可能出现的对象类型,并不定义任何公共字段。例如 union Media = Image | Video,但 Image 和 Video 共享 url、size 等字段也必须在各自类型里重复,而查询 media 字段时必须写成 ... on Image { url } 这样的内联片段,不能直接 media { url }。
从执行器的角度看,二者都依赖 __typename 或服务端的 resolveType 方法确定具体对象,但 Interface 允许查询书在 schema 验证阶段就知道有个统一入口放子字段,而 Union 没有这个入口。也就是说,凡是需要从不同对象里提取同名字段的场景,Union 迫使客户端对每个对象复制一遍选择集,而 Interface 只需一次。这是二者最直观的工程差异。
搜索结果的异构返回
例如构建一个搜索接口,返回结果可能是文章、作者和话题。文章有 title、publishedAt;作者有 nickname、followers;话题有 topicName、count。三个类型有各自属性和字段名,不具统一的展示字段。当客户端只想渲染一个混合列表时,可选方案是定义 union SearchResult = Article | Author | Topic,查询中必须写三段行内片段去取各自的标签和元信息,组件中也要按 __typename 做 if/else。
如果产品又决定为所有搜索结果展示 breadcrumb 和 summary 两个公共字段,并希望客户端统一调用,那么更合适的做法是定义 interface SearchHit,并让三个类型都实现它。客户端可直接写 search { hits { breadcrumb summary } },一旦新类型加入,只要也实现该接口就不会破坏查询。但这个前提是公共字段长期稳定;若未来引入一个没有任何标题或摘要的‘算法解释’类型,就无法实现该接口,此时被迫改成 Union 或将接口字段设为 nullable,造成类型语义被扭曲。
字段变化与抽象选择
最典型的选择误区是看见多个类型有少数同名字段就立刻用 Interface,忽视这些字段未来可能被移除或变型。比如 content 字段在文章里是正文,在问题里是标题加详情,强行统一成字符串会丢失类型细节。另一方面,若只是临时有几个相同字段,用 Union 并让客户端用 ... on TypeA { commonField } 这样重复写,虽然啰嗦但能保持各类型独立演进。
还需要注意 Union 的成员只能是具体对象类型,不能是 Interface 或另一个 Union。因此无法通过将多个 Interface 作为成员来构造抽象。为了兼顾公共字段和异构扩展,常见手法是同时导出 Interface 和 Union,但 Union 的成员仍然需是对象类型;客户端若要公共字段就用 Interface 片段,若需要完整分支则用 Union。这种写法增加了 schema 的层级,也要求客户端正确选择片段,否则无法获得类型信息。
容易答错的地方
- Union 也有共享字段
- 错误认为 Union 成员可以像接口一样定义公共字段。实际上 GraphQL 规范规定 Union 不提供任何字段定义,客户端不能直接选择 Union 上的字段,必须用内联片段挨个成员选择。若确实有公共字段,应使用 Interface 而不是 Union。
- Interface 只能用于同一继承链
- 误解 Interface 必须映射领域模型的继承。实际上 GraphQL Interface 只是结构约束,任何对象只要包含接口所列字段即可实现,不要求后端类有父子关系,因此它适合表达行为能力而非领域归类。
面试官还会怎么问?
服务端如何判断具体类型?
对于 Union 或 Interface,解析器可返回对象后由框架调用 resolveType,也可依赖 __typename 返回的字符串。实际开发中由解析函数决定是否额外实现 resolveType;通常使用对象的 type 字段或 constructor.name。
一个类型能同时实现多个 Interface 吗?
可以。对象类型可以实现多个 Interface 以表达多重能力,查询时可通过不同 Interface 的名称用片段访问各自字段,服务端执行器会按需并行解析。
字段重叠很多,但有个别特殊类型怎么办?
优先用 Interface 承载主要公共字段,再单独用 Union 包装那些不满足接口的异构类型,或者将特殊类型改为 nullable 字段补齐,但必须评估破坏性改动成本。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。