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

如何解决GraphQL循环依赖拆分类型到独立文件的空对象报错

解决方案

问题本质

该报错由CommonJS模块的循环加载机制导致:company.js和user.js互相引入时,先执行的模块会拿到另一个尚未完成导出逻辑的空对象,因此报错预期GraphQL类型却得到空对象。

修复方法(无需合并文件,仅调整导出逻辑)

调整两个类型文件的导入导出顺序,先导出空占位对象,再引入循环依赖、定义类型,最终将定义好的类型挂载到导出对象上即可:

修改后的user.js

// 第一步:导入无循环依赖的基础依赖
const { GraphQLObjectType, GraphQLString, GraphQLInt } = require('graphql')
const axios = require('axios')

// 第二步:先导出空对象作为占位
module.exports = {}

// 第三步:再引入存在循环依赖的模块
const { CompanyType } = require('./company')

// 第四步:定义UserType
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: () => ({
    id: { type: GraphQLString },
    age: { type: GraphQLInt },
    name: { type: GraphQLString },
    company: {
      type: CompanyType,
      async resolve(parentValue, args) {
        console.log(parentValue)
        return await axios(
          `http://localhost:3001/company/${parentValue.companyId}`
        ).then((response) => response.data)
      },
    },
  }),
})

// 第五步:将定义好的类型挂载到导出对象上
module.exports.UserType = UserType

修改后的company.js

// 第一步:导入无循环依赖的基础依赖
const { GraphQLObjectType, GraphQLString, GraphQLList } = require('graphql')
const axios = require('axios')

// 第二步:先导出空对象作为占位
module.exports = {}

// 第三步:再引入存在循环依赖的模块
const { UserType } = require('./user')

// 第四步:定义CompanyType
const CompanyType = new GraphQLObjectType({
  name: 'Company',
  fields: () => ({
    id: { type: GraphQLString },
    name: { type: GraphQLString },
    users: {
      type: new GraphQLList(UserType),
      async resolve(parentValue, args) {
        return await axios(
          `http://localhost:3001/company/${parentValue.id}/users`
        ).then(({ data }) => data)
      },
    },
  }),
})

// 第五步:将定义好的类型挂载到导出对象上
module.exports.CompanyType = CompanyType

修改后的rootquery.js

仅需要调整导入语法,匹配导出的对象结构即可,其余逻辑不变:

const { UserType } = require('./user')
const { CompanyType } = require('./company')
const { GraphQLObjectType, GraphQLString } = require('graphql')
const axios = require('axios')

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryObjectType',
  fields: {
    user: {
      type: UserType,
      args: {
        id: { type: GraphQLString },
      },
      resolve: async (parentValue, args) => {
        return await axios(`http://localhost:3001/users/${args.id}`).then(
          ({ data }) => data
        )
      },
    },
    company: {
      type: CompanyType,
      args: {
        id: { type: GraphQLString },
      },
      resolve: async (parentValue, args) => {
        return await axios(`http://localhost:3001/company/${args.id}`).then(
          ({ data }) => data
        )
      },
    },
  },
})

实现原理

CommonJS为同步执行加载,提前导出空占位对象可以保证两个互相依赖的模块拿到的是同一个引用地址,等模块内所有逻辑执行完成、类型挂载到导出对象后,引用指向的内容就会自动更新。同时GraphQL的fields采用函数返回的设计本身就是为了解决类型循环依赖问题,等实际解析Schema时所有类型都已经完成赋值,不会再出现空对象的问题。


内容的提问来源于stack exchange,提问作者Elroy Toscano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 07:57:03