Fragments 您所在的位置:网站首页 apollographql Fragments

Fragments

#Fragments| 来源: 网络整理| 查看: 265

A GraphQL fragment is a piece of logic that can be shared between multiple queries and mutations.

Here's the declaration of a NameParts fragment that can be used with any Person object:

1fragment NameParts on Person {2 firstName3 lastName4}

Every fragment includes a subset of the fields that belong to its associated type. In the above example, the Person type must declare firstName and lastName fields for the NameParts fragment to be valid.

We can now include the NameParts fragment in any number of queries and mutations that refer to Person objects, like so:

1query GetPerson {2 people(id: "7") {3 ...NameParts4 avatar(size: LARGE)5 }6}

You precede an included fragment with three periods (...), much like JavaScript spread syntax.

Based on our NameParts definition, the above query is equivalent to:

1query GetPerson {2 people(id: "7") {3 firstName4 lastName5 avatar(size: LARGE)6 }7}

If we later change which fields are included in the NameParts fragment, we automatically change which fields are included in operations that use the fragment. This reduces the effort required to keep fields consistent across a set of operations.

Example usage

Let's say we have a blog application that executes several GraphQL operations related to comments (submitting a comment, fetching a post's comments, etc.). These operations probably all include certain fields of a Comment type.

To specify this core set of fields, we can define a fragment on the Comment type, like so:

fragments.js1import { gql } from '@apollo/client';2 3export const CORE_COMMENT_FIELDS = gql`4 fragment CoreCommentFields on Comment {5 id6 postedBy {7 username8 displayName9 }10 createdAt11 content12 }13`;

You can declare fragments in any file of your application. The example above exports the fragment from a fragments.js file.

We can then include the CoreCommentFields fragment in a GraphQL operation like so:

PostDetails.jsx1import { gql } from '@apollo/client';2import { CORE_COMMENT_FIELDS } from './fragments';3 4export const GET_POST_DETAILS = gql`5 ${CORE_COMMENT_FIELDS}6 query CommentsForPost($postId: ID!) {7 post(postId: $postId) {8 title9 body10 author11 comments {12 ...CoreCommentFields13 }14 }15 }16`;17 18// ...PostDetails component definition... We first import CORE_COMMENT_FIELDS because it's declared in another file.We add our fragment definition to the GET_POST_DETAILS gql template literal via a placeholder (${CORE_COMMENT_FIELDS})We include the CoreCommentFields fragment in our query with standard ... notation. Registering named fragments using createFragmentRegistry

Starting in Apollo Client 3.7, fragments can be registered with your InMemoryCache so that they can be referred to by name in any query or InMemoryCache operation (such as cache.readFragment, cache.readQuery and cache.watch) without needing to interpolate their declarations.

Let's look at an example in React.

index.js1import { ApolloClient, gql, InMemoryCache } from "@apollo/client";2import { createFragmentRegistry } from "@apollo/client/cache";3 4const client = new ApolloClient({5 uri: "http://localhost:4000/graphql"6 cache: new InMemoryCache({7 fragments: createFragmentRegistry(gql`8 fragment ItemFragment on Item {9 id10 text11 }12 `)13 })14});

Since ItemFragment was registered with InMemoryCache, it can be referenced by name as seen below with the fragment spread inside of the GetItemList query.

ItemList.jsx1const listQuery = gql`2 query GetItemList {3 list {4 ...ItemFragment5 }6 }7`;8function ToDoList() {9 const { data } = useQuery(listQuery);10 return (11 12 {data?.list.map(item => (13 14 ))}15 16 );17} Overriding registered fragments with local versions

Queries can declare their own local versions of named fragments which will take precendence over ones registered via createFragmentRegistry, even if the local fragment is only indirectly referenced by other registered fragments. Take the following example:

index.js1import { ApolloClient, gql, InMemoryCache } from "@apollo/client";2import { createFragmentRegistry } from "@apollo/client/cache";3 4const client = new ApolloClient({5 uri: "http://localhost:4000/graphql"6 cache: new InMemoryCache({7 fragments: createFragmentRegistry(gql`8 fragment ItemFragment on Item {9 id10 text11 ...ExtraFields12 }13 14 fragment ExtraFields on Item {15 isCompleted16 }17 `)18 })19});

The local version of the ExtraFields fragment declared in ItemList.jsx takes precedence over the ExtraFields originally registered with the InMemoryCache. Thus, its definition will be used when the ExtraFields fragment spread is parsed inside of the registered ItemFragment only when GetItemList query is executed, because explicit definitions take precedence over registered fragments.

ItemList.jsx1const listQuery = gql`2 query GetItemList {3 list {4 ...ItemFragment5 }6 }7 8 fragment ExtraFields on Item {9 createdBy10 }11`;12function ToDoList() {13 const { data } = useQuery(listQuery);14 return (15 16 {data?.list.map((item) => (17 {/* `createdBy` exists on the returned items, `isCompleted` does not */}18 19 ))}20 21 );22} Colocating fragments

The tree-like structure of a GraphQL response resembles the hierarchy of a frontend's rendered components. Because of this similarity, you can use fragments to split query logic up between components, so that each component requests exactly the fields that it uses. This helps you make your component logic more succinct.

Consider the following view hierarchy for an app:

1FeedPage2└── Feed3 └── FeedEntry4 ├── EntryInfo5 └── VoteButtons

In this app, the FeedPage component executes a query to fetch a list of FeedEntry objects. The EntryInfo and VoteButtons subcomponents need specific fields from the enclosing FeedEntry object.

Creating colocated fragments

A colocated fragment is just like any other fragment, except it's attached to a particular component that uses the fragment's fields. For example, the VoteButtons child component of FeedPage might use the fields score and vote { choice } from the FeedEntry object:

VoteButtons.jsx1VoteButtons.fragments = {2 entry: gql`3 fragment VoteButtonsFragment on FeedEntry {4 score5 vote {6 choice7 }8 }9 `,10};

After you define a fragment in a child component, the parent component can refer to it in its own colocated fragments, like so:

FeedEntry.jsx1FeedEntry.fragments = {2 entry: gql`3 fragment FeedEntryFragment on FeedEntry {4 commentCount5 repository {6 full_name7 html_url8 owner {9 avatar_url10 }11 }12 ...VoteButtonsFragment13 ...EntryInfoFragment14 }15 ${VoteButtons.fragments.entry}16 ${EntryInfo.fragments.entry}17 `,18};

There's nothing special about the naming of VoteButtons.fragments.entry or EntryInfo.fragments.entry. Any naming convention works as long as you can retrieve a component's fragments given the component.

Importing fragments when using Webpack

When loading .graphql files with graphql-tag/loader, we can include fragments using import statements. For example:

1#import "./someFragment.graphql"

This makes the contents of someFragment.graphql available to the current file. See the Webpack Fragments section for additional details.

Using fragments with unions and interfaces

You can define fragments on unions and interfaces.

Here's an example of a query that includes three in-line fragments:

1query AllCharacters {2 all_characters {3 4 ... on Character {5 name6 }7 8 ... on Jedi {9 side10 }11 12 ... on Droid {13 model14 }15 }16}

The all_characters query above returns a list of Character objects. The Character type is an interface that both the Jedi and Droid types implement. Each item in the list includes a side field if it's an object of type Jedi, and it includes a model field if it's of type Droid.

However, for this query to work, your client needs to understand the polymorphic relationship between the Character interface and the types that implement it. To inform the client about these relationships, you can pass a possibleTypes option when you initialize your InMemoryCache.

Defining possibleTypes manually

The possibleTypes option is available in Apollo Client 3.0 and later.

You can pass a possibleTypes option to the InMemoryCache constructor to specify supertype-subtype relationships in your schema. This object maps the name of an interface or union type (the supertype) to the types that implement or belong to it (the subtypes).

Here's an example possibleTypes declaration:

1const cache = new InMemoryCache({2 possibleTypes: {3 Character: ["Jedi", "Droid"],4 Test: ["PassingTest", "FailingTest", "SkippedTest"],5 Snake: ["Viper", "Python"],6 },7});

This example lists three interfaces (Character, Test, and Snake) and the object types that implement them.

If your schema includes only a few unions and interfaces, you can probably specify your possibleTypes manually without issue. However, as your schema grows in size and complexity, you should consider generating possibleTypes automatically from your schema.

Generating possibleTypes automatically

The following example script translates a GraphQL introspection query into a possibleTypes configuration object:

1const fetch = require('cross-fetch');2const fs = require('fs');3 4fetch(`${YOUR_API_HOST}/graphql`, {5 method: 'POST',6 headers: { 'Content-Type': 'application/json' },7 body: JSON.stringify({8 variables: {},9 query: `10 {11 __schema {12 types {13 kind14 name15 possibleTypes {16 name17 }18 }19 }20 }21 `,22 }),23}).then(result => result.json())24 .then(result => {25 const possibleTypes = {};26 27 result.data.__schema.types.forEach(supertype => {28 if (supertype.possibleTypes) {29 possibleTypes[supertype.name] =30 supertype.possibleTypes.map(subtype => subtype.name);31 }32 });33 34 fs.writeFile('./possibleTypes.json', JSON.stringify(possibleTypes), err => {35 if (err) {36 console.error('Error writing possibleTypes.json', err);37 } else {38 console.log('Fragment types successfully extracted!');39 }40 });41 });

You can then import the generated possibleTypes JSON module into the file where you create your InMemoryCache:

1import possibleTypes from './path/to/possibleTypes.json';2 3const cache = new InMemoryCache({4 possibleTypes,5}); useFragment

The useFragment hook represents a lightweight live binding into the Apollo Client Cache. It enables Apollo Client to broadcast specific fragment results to individual components. This hook returns an always-up-to-date view of whatever data the cache currently contains for a given fragment. useFragment never triggers network requests of its own.

The useQuery hook remains the primary hook responsible for querying and populating data in the cache (see the API reference). As a result, the component reading the fragment data via useFragment is still subscribed to all changes in the query data, but receives updates only when that fragment's specific data change.

Note: this hook was introduced in 3.7.0 as experimental but stabilized in 3.8.0. In 3.7.x and 3.8.0-alpha.x releases, this hook is exported as useFragment_experimental. Starting with 3.8.0-beta.0 and greater the _experimental suffix was removed in its named export.

Example

Given the following fragment definition:

1const ItemFragment = gql`2 fragment ItemFragment on Item {3 text4 }5`;

We can first use the useQuery hook to retrieve a list of items with ids as well as any fields selected on the named ItemFragment fragment by spreading ItemFragment inside of list in ListQuery.

1const listQuery = gql`2 query GetItemList {3 list {4 id5 ...ItemFragment6 }7 }8 ${ItemFragment}9`;10 11function List() {12 const { loading, data } = useQuery(listQuery);13 14 return (15 16 {data?.list.map(item => (17 18 ))}19 20 );21}

Note: Instead of interpolating fragments within each query document, we can use Apollo Client's createFragmentRegistry method to pre-register named fragments with our InMemoryCache. This allows Apollo Client to include the definitions for registered fragments in the document sent over the network before the request is sent. For more information, see Registering named fragments using createFragmentRegistry.

We can then use useFragment from within the component to create a live binding for each item by providing the fragment document, fragmentName and object reference via from.

1function Item(props: { id: number }) {2 const { complete, data } = useFragment({3 fragment: ItemFragment,4 fragmentName: "ItemFragment",5 from: {6 __typename: "Item",7 id: props.id,8 },9 });10 11 return {complete ? data.text : "incomplete"};12}

useFragment can be used in combination with the @nonreactive directive in cases where list items should react to individual cache updates without rerendering the entire list. For more information, see the @nonreactive docs.

See the API reference for more details.



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有