REAL TALK : документы GraphQL.js не так уж хороши.На мой взгляд, им никогда не следовало использовать примеры с buildSchema
, потому что это понятно, что приводит к путанице такого рода.
GraphQL.js (то есть пакет graphql
) - это реализация JavaScript дляGraphQL.Создание схемы в GraphQL.js выполняется программно, путем создания экземпляра класса GraphQLSchema
:
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-server
и graphql-yoga
.makeExecutableSchema
создает схему из SDL, используя buildSchema
, а затем изменяет результирующий объект, добавляя преобразователи в после факта .
В GraphQL.js, функция resolve
(или определитель) для поля принимает четыре параметра - родительское значение, аргументы поля, контекст и объект GraphQLResolveInfo
.Если мы создаем GraphQLObjectType
как userType
в приведенном выше примере, это дополнительная функция, которую мы можем предоставить для каждого из полей в нашем объекте.Это та же самая функция , которую вы определяете, когда создаете карту преобразователя для использования с graphql-yoga
. Это единственная реализация распознавателя полей.
Так в чем же дело с buildSchema
??
Примеры в документации используют преимущества GraphQL распознаватель полей по умолчанию :
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
возвращает {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
будут переданы корневому объекту в качестве родительского значения.Если я опущу распознаватель для поля user
, распознаватель по умолчанию будет 1) проверять родительский объект для свойства с тем же именем, 2) находить свойство и определять, что это функция, 3) вызывать функцию, 4)преобразовать поле в возвращаемое значение функции.
TADA !
Однако, поскольку функция вызывается преобразователем по умолчанию и не используется в качестве преобразователяСам он получит только три вышеупомянутых параметра вместо 4.
Это отличный способ обойти невозможность на самом деле предоставлять собственные преобразователи для схемы, но она очень ограничена.Он работает только для корневых типов, поэтому мы не можем аналогичным образом предоставить поддельные распознаватели для полей User
или других типов.Мы не можем использовать интерфейсы или объединения в нашей схеме, потому что мы не можем предоставить resolveType
функций.И так далее ...
Надеюсь, это обеспечит некоторую ясность.И, надеюсь, мы сможем обновить документы в ближайшем будущем, чтобы избежать всей этой путаницы.