graphql

时间:2019-04-25 17:07:43

标签: graphql graphql-js express-graphql

我一直在阅读graphQL文档,发现他们已经以两种方式解释了graphql服务器的实现:一种使用graphql-yoga(这是功能齐全的graphql服务器),另一种是使用graphql,express- graphql和表达。在这两种情况下,我们在创建服务器实例时都会传递架构和解析器功能。

但是解析程序功能的实现有所不同。当使用graphql-yoga时,resolver函数提供了4个参数,其中包含有关父对象,接收到的参数,上下文,信息的信息。而在其他情况下(使用graphql),resolver函数仅获取arguments对象。

为什么会这样?如果我想要信息,上下文对象,该如何获取?

使用graphql-yoga示例:https://graphql.org/learn/execution/

使用graphql示例:https://graphql.github.io/graphql-js/mutations-and-input-types/

//使用graphql的代码示例

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
}
type Mutation {
    addDice(numDice: Int): String
}
`);

var root = {
    rollDice({numDice, numSides}) {
        return [1, 2];
    },
    addDice({numDice}) {
        console.log("Adding something");
        return "Added";
    }
};

var app = express();
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

//使用graphql-yoga的代码示例

let graphqlServer = require("graphql-yoga");

const typeDefs = `
    type Query {
        rollDice(numDice: Int!, numSides: Int): [Int]
    }
    type Mutation {
        addDice(numDice: Int): String
    }
    `;

const resolvers = {
    Query: {
        rollDice(parent, args, context, info) {
            console.log(args.numDice);
            console.log(args.numSides);
            return [1, 2];
        }
    },
    Mutation: {
        addDice(parent, args, context, info) {
            console.log(args.numDice);
            return "Added";
        }
    }
};

const server = new graphqlServer.GraphQLServer({
    typeDefs,
    resolvers
});

server.start(() => {
    console.log("server started on localhost:4000");
});

这两个代码段之间的区别:

在一种情况下,解析器函数存在于适当的类型(即查询,突变)内。在另一种情况下,它们存在于一个根对象内。这意味着在第一种情况下,我可以在Query和Mutation中使用具有相同名称的方法,而在第二种情况下,这是不可能的,因为它们是单个对象的键,并且键应该是唯一的。

为什么会这样?我基本上是缺少什么吗?每个包的实现细节如何不同?

1 个答案:

答案 0 :(得分:1)

实话:GraphQL.js文档不是很好。我认为,他们永远不应该首先使用带有buildSchema的示例,因为它可以理解会导致这种混乱。

GraphQL.js(即graphql包)是GraphQL的JavaScript实现。通过构造GraphQLSchema类的实例,可以以编程方式在GraphQL.js中构建模式:

const userType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: {
      type: GraphQLID,
    },
    email: {
      type: GraphQLString,
    },
  },
});
const queryType = new GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: userType,
      resolve: () => ({ id: 1, email: 'john.doe@example.com' }),
    },
  },
});
const schema = new GraphQLSchema({
  query: queryType,
})

如果我们以模式定义语言(SDL)打印此模式,则它看起来像这样:

type Query {
  user: User
}

type User {
  id: ID
  email: String
}

与SDL相比,编写所有代码要容易得多。 但是,GraphQL.js并未提供从SDL构建功能齐全的架构的方法。它确实提供了buildSchema函数,但该实用程序构造了一个架构没有任何解析器(以及许多其他功能,如联合/接口类型解析)。

graphql-tools软件包提供了一个makeExecutableSchema函数,使您可以从SDL和解析器映射对象构建模式。这是apollo-servergraphql-yoga在幕后使用的。 makeExecutableSchema使用buildSchema从SDL构造模式,然后对结果对象进行突变,在after the fact中添加解析器。

在GraphQL.js中,字段的resolve函数(或解析器)具有四个参数-父值,字段的参数,上下文和GraphQLResolveInfo对象。如果在上例中创建像GraphQLObjectType的{​​{1}},则这是我们可以为对象中每个字段提供的可选功能。这是在构造要与userType一起使用的解析器映射时定义的相同函数。 这是字段解析器的唯一实现。

那么graphql-yoga怎么了?

文档中的示例利用了GraphQL的default field resolver

buildSchema

如您所见,默认解析逻辑将查找与源(父)值上的字段同名的属性。在上面的示例中,export const defaultFieldResolver: GraphQLFieldResolver<any, *> = function( source, args, contextValue, info, ) { if (typeof source === 'object' || typeof source === 'function') { const property = source[info.fieldName]; if (typeof property === 'function') { return source[info.fieldName](args, contextValue, info); } return property; } }; 解析器返回user -这是字段 resolves 的值。该字段的类型为{id: 1, email: 'john.doe@example.com'}。我们没有为User字段定义解析器,因此默认解析器会执行此操作。 id字段解析为id,因为这是解析程序接收的父对象上名为1的属性的值。

但是,父值也可以 是函数而不是对象。如果是函数,则首先调用它,然后使用返回值。该函数调用什么?嗯,它不能传递给它一个父值(由于无限递归),但是它可以传递给它剩余的三个参数(参数,上下文和信息)。这就是它的作用。

现在是魔术了

在我们的示例中,我可以省略id字段的解析器,而是将一个函数传递给根值。

user

根对象只是一个可选对象,它作为父值向下传递给根级别的解析器(例如您的const root = { user: () => ({id: 1, email: 'john.doe@example.com'}) } Query类型)。否则,这些解析器将没有父值。

Mutation是一种可操作的根类型-它充当架构其余部分的“入口点”。 Query类型的任何字段都将作为根值传递给根对象。如果我省略了Query字段的解析器,则默认解析器将1)检查父对象中具有相同名称的属性,2)找到一个属性并确定它是一个函数,3)调用该函数, 4)将字段解析为函数的返回值。

TADA

但是,由于该函数由默认解析器调用,并且自身不用作解析器,因此它将仅接收上述三个参数,而不是4。

这是解决无法实际为架构提供自定义解析器的一种巧妙方法,但这非常有限。它仅适用于根类型,因此我们无法类似地为user字段或其他类型提供伪解析器。由于无法提供User函数,因此无法在架构中使用接口或联合。等等...

希望能提供一些清晰度。希望我们能在不久的将来更新文档,从而避免所有这些混乱。