Relay에서의 GraphQL
Table of Contents:
graphql
The graphql
template tag provided by Relay serves as the mechanism to write queries, fragments, mutations or subscriptions in the GraphQL language. For example:
import {graphql} from 'react-relay';
graphql`
query MyQuery {
viewer {
id
}
}
`;
The result of using the graphql
template tag is a GraphQLTaggedNode
; a runtime representation of the GraphQL document which can be used to define Query Renderers, Fragment Containers, Refetch Containers, Pagination Containers, etc.
Note that graphql
template tags are never executed at runtime. Instead, they are compiled ahead of time by the Relay Compiler into generated artifacts that live alongside your source code, and which Relay requires to operate at runtime. The Relay Babel plugin will then convert the graphql
literals in your code into require()
calls for the generated files.
Directives
Relay uses directives to add additional information to GraphQL documents, which are used by the Relay Compiler to generate the appropriate runtime artifacts. These directives only appear in your application code and are removed from requests sent to your GraphQL server.
Note: The relay-compiler will maintain any directives supported by your server (such as @include
or @skip
) so they remain part of the request to the GraphQL server and won't alter generated runtime artifacts.
@arguments
@arguments
is a directive used to pass arguments to a fragment that was defined using @argumentDefinitions
. For example:
query TodoListQuery($userID: ID) {
...TodoList_list @arguments(count: $count, userID: $userID) # Pass arguments here
}
See the Fragment Container docs for more details.
@argumentDefinitions
@argumentDefinitions
is a directive used to specify arguments taken by a fragment. For example:
fragment TodoList_list on TodoList @argumentDefinitions(
count: {type: "Int", defaultValue: 10}, # Optional argument
userID: {type: "ID"}, # Required argument
) {
title
todoItems(userID: $userID, first: $count) { # Use fragment arguments here as variables
...TodoItem_item
}
}
See the Fragment Container docs for more details.
@connection(key: String!, filters: [String])
When using the Pagination Container, Relay expects connection fields to be annotated with a @connection
directive. For more detailed information and an example, check out the docs on using @connection
inside a Pagination Container.
@relay(plural: Boolean)
When defining a fragment for use with a Fragment container, you can use the @relay(plural: true)
directive to indicate that container expects the prop for that fragment to be a list of items instead of a single item. A query or parent that spreads a @relay(plural: true)
fragment should do so within a plural field (ie a field backed by a GraphQL list. For example:
// Plural fragment definition
graphql`
fragment TodoItems_items on TodoItem @relay(plural: true) {
id
text
}`;
// Plural fragment usage: note the parent type is a list of items (`[TodoItem}]`)
fragment TodoApp_app on App {
items {
// parent type is a list here
...TodoItem_items
}
}
@inline
By default, Relay will only expose the data for fields explicitly requested by a component's fragment, which is known as data masking. Fragment data is unmasked for use in React components by createFragmentContainer
. However, you may want to use fragment data in non-React functions that are called from React.
Non-React functions can also take advantage of data masking. A fragment can be defined with the @inline
directive and stored in a local variable. The non-React function can then "unmask" the data using the readInlineData
function.
In the example below, the function processItemData
is called from a React component. It requires an item object with a specific set of fields. All React components that use this function should spread the processItemData_item
fragment to ensure all of the correct item data is loaded for this function.
import {graphql, readInlineData} from 'react-relay';
// non-React function called from React
function processItemData(itemRef) {
const item = readInlineData(graphql`
fragment processItemData_item on Item @inline {
title
price
creator {
name
}
}
`, itemRef);
sendToThirdPartyApi({
title: item.title,
price: item.price,
creatorName: item.creator.name
});
}
// React Component
function MyComponent({item}) {
function handleClick() {
processItemData(item);
}
return (
<button onClick={handleClick}>Process {item.title}</button>
);
}
export default createFragmentContainer(MyComponent, {
item: graphql`
fragment MyComponent_item on Item {
...processItemData_item
title
}
`
});
@relay(mask: Boolean)
It is not recommended to use @relay(mask: false)
. Please instead consider using the @inline
fragment.
@relay(mask: false)
can be used to prevent data masking; when including a fragment and annotating it with @relay(mask: false)
, its data will be available directly to the parent instead of being masked for a different container.
Applied to a fragment definition, @relay(mask: false)
changes the generated Flow types to be better usable when the fragment is included with the same directive. The Flow types will no longer be exact objects and no longer contain internal marker fields.
This may be helpful to reduce redundant fragments when dealing with nested or recursive data within a single Component.
Keep in mind that it is typically considered an anti-pattern to create a single fragment shared across many containers. Abusing this directive could result in over-fetching in your application.
In the example below, the user
prop will include the data for id
and name
fields wherever ...Component_internUser
is included, instead of Relay's normal behavior to mask those fields:
graphql`
fragment Component_internUser on InternUser @relay(mask: false) {
id
name
}
`;
export default createFragmentContainer(
({ user }) => /* ... */,
graphql`
fragment Component_user on User {
internUser {
manager {
...Component_internUser @relay(mask: false)
}
... on Employee {
admins {
...Component_internUser @relay(mask: false)
}
reports {
...Component_internUser @relay(mask: false)
}
}
}
}
`,
);
Relay Compiler
Relay uses the Relay Compiler to convert graphql
literals into generated files that live alongside your source files.
A query like the following:
graphql`
fragment MyComponent on Type {
field
}
`
Will cause a generated file to appear in ./__generated__/MyComponent.graphql
,
with both runtime artifacts (which help to read and write from the Relay Store)
and Flow types to help you write type-safe code.
The Relay Compiler is responsible for generating code as part of a build step which can then be referenced at runtime. By building the query ahead of time, the Relay's runtime is not responsible for generating a query string, and various optimizations can be performed on the query that could be too expensive at runtime (for example, fields that are duplicated in the query can be merged during the build step, to improve efficiency of processing the GraphQL response).
Persisting queries
Relay Compiler supports the use of persisted queries, in which each version of a query is associated to a unique ID on the server and the runtime uploads only the persisted ID instead of the full query text. This has several benefits: it can significantly reduce the time to send a query (and the upload bytes) and enables whitelisting of queries. For example, you may choose to disallow queries in text form and only allow queries that have been persisted (and that presumably have passed your internal code review process).
Persisted queries can be enabled by instructing Relay Compiler to emit metadata about each query, mutation, and subscription into a JSON file. The generated file will contain a mapping of query identifiers to query text, which you can then save to your server. To enable persisted queries, use the --persist-output
flag to the compiler:
"scripts": {
"relay": "relay-compiler --src ./src --schema ./schema.graphql --persist-output ./path/to/persisted-queries.json"
}
Relay Compiler will then create the id => query text mapping in the path you specify. You can then use this complete json file in your server side to map query ids to operation text.
For more details, refer to the Persisted Queries section.
Set up relay-compiler
See our relay-compiler section in our Installation and Setup guide.
GraphQL Schema
To use the Relay Compiler, you need either a .graphql or .json GraphQL schema file, describing your GraphQL server's API. Typically these files are local representations of a server source of truth and are not edited directly. For example, we might have a schema.graphql
like:
schema {
query: Root
}
type Root {
dictionary: [Word]
}
type Word {
id: String!
definition: WordDefinition
}
type WordDefinition {
text: String
image: String
}
Source files
Additionally, you need a directory containing .js
files that use the graphql
tag to describe GraphQL queries and fragments. Let's call this ./src
.
Then run yarn run relay
as set up before.
This will create a series of __generated__
directories that are co-located with the corresponding files containing graphql
tags.
For example, given the two files:
src/Components/DictionaryComponent.js
const DictionaryWordFragment = graphql` fragment DictionaryComponent_word on Word { id definition { ...DictionaryComponent_definition } } ` const DictionaryDefinitionFragment = graphql` fragment DictionaryComponent_definition on WordDefinition { text image } `
src/Queries/DictionaryQuery.js
const DictionaryQuery = graphql` query DictionaryQuery { dictionary { ...DictionaryComponent_word } } `
This would produce three generated files, and two __generated__
directories:
src/Components/__generated__/DictionaryComponent_word.graphql.js
src/Components/__generated__/DictionaryComponent_definition.graphql.js
src/Queries/__generated__/DictionaryQuery.graphql.js
Importing generated definitions
Typically you will not need to import your generated definitions. The Relay Babel plugin will then convert the graphql
literals in your code into require()
calls for the generated files.
However the Relay Compiler also automatically generates Flow types as type comments. For example, you can import the generated Flow types like so:
import type {DictionaryComponent_word} from './__generated__/DictionaryComponent_word.graphql';
Client schema extensions
The Relay Compiler fully supports client-side schema extensions, which allows you to extend the server schema by defining additional GraphQL types and fields on the client. Relay expects the client schema to be located in your --src
directory.
For example, assuming the server schema ./schema.graphql
:
schema {
query: Root
}
type Root {
title: String!
}
We can create a ./src/clientSchema.graphql
and define a new type called Setting
:
type Setting {
name: String!
active: Boolean!
}
We can then extend existing server types in the client schema ./src/clientSchema.graphql
with our new Setting
type, like so:
extend type Root {
settings: [Setting]
}
Any fields specified in the client schema, can be fetched from the Relay Store, by selecting it in a query or fragment.
For more details, refer to the Local state management section.
Advanced usage
In addition to the bin script, the relay-compiler
package also exports library code which you may use to create more complex configurations for the compiler, or to extend the compiler with your own custom output.
If you find you need to do something unique (like generate types that conform to an older version of Flow, or to parse non-javascript source files), you can build your own version of the Compiler by swapping in your own FileWriter
and ASTCache
, or by adding on an additional IRTransform
. Note, the internal APIs of the RelayCompiler
are under constant iteration, so rolling your own version may lead to incompatibilities with future releases.