โ๏ธ Apollo Client
GraphQL๋ก ๋ก์ปฌ ๋ฐ์ดํฐ์ ์๊ฒฉ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ด๋ฆฌํ ์ ์๋ JavaScript์ฉ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โ๏ธ Fetching
1. useQuery
์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ ์ฌ์ฉํ๋ค. (๋ณ๊ฒฝ X)
์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ ์๋์ผ๋ก ์บ์ํจ.
โ๏ธ Updating cached query results
1. Polling : ์ง์ ํ ์๊ฐ์ผ๋ก ์ฃผ๊ธฐ์ ์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ์คํํด์, ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๊ฐ์ ธ์ด.
2. Refetching : user action์ ํตํด์ ๋ค์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ. (ex. button)
polling, refetching ์ ํ ๋๋ ๋ฐ์ดํฐ๋ฅผ fetching ํ๊ณ ์๋ค๋ ๊ฒ์ ์ ์ ์๋๋ฐ, notifyOnNetworkStatusChange : true๋ก ์ค์ ํ๋ฉด refetch ๋ ๋๋ง๋ค ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
function Dogs({ onDogSelected }) {
const { loading, error, data } = useQuery(GET_DOGS,{
pollInterval: 500
});
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<select name='dog' onChange={onDogSelected}>
{data.dogs.map((dog) => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
<button onClick={() => refetch({ breed: 'new_dog_breed' })}>
Refetch new breed!
</button>
</select>
);
}
โ๏ธ fetchPolicy
const { loading, error, data } = useQuery(GET_DOGS, {
// ์ฒ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฒญํ ๋
fetchPolicy: "network-only",
// ๊ทธ ๋ค์๋ถํฐ ์ฟผ๋ฆฌ๋ฅผ ์์ฒญํ ๋ (=์ฟผ๋ฆฌ ์
๋ฐ์ดํธ)
nextFetchPolicy : 'cache-first' ,
});
+ useLazyQuery
useQuery ๋ ๋ฆฌ์กํธ๊ฐ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํ๋ฉด์ ์๋์ ์ผ๋ก ํด๋น ์ฟผ๋ฆฌ๋ฅผ ์คํ,
useLazyQuery๋ user action์ ํตํด์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ค๋ ๊ฒ์ ์ฐจ์ด์ ์ด ์๋ค! (์๋์ )
import React from 'react';
import { useLazyQuery } from '@apollo/client';
function DelayedQuery() {
// ์์ฒญ์ ์ํ ํจ์๋ฅผ ๋ฐํํจ.
const [getDog, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
if (error) return `Error! ${error}`;
return (
<div>
{data?.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>
Click me!
</button>
</div>
);
}
2. useMutation
์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝ / ์บ์์ ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ ๋ฐ์.
const [addTodo, { data, loading, error, reset }] = useMutation(ADD_TODO);
if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
{
error &&
<LoginFailedMessageWindow
message={error.message}
onDismiss={() => reset()}
/>
}
</form>
</div>
);
}
โ๏ธ Updating local data
1. Refetching queries : ์ํ๋ ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ๋ฆฌ๋ ๋๋งํ๋ ๋ฐฉ๋ฒ.
// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
{query: GET_POST}, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});
2. Updating the cache directly
function AddTodo() {
let input;
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
// cache object : readQuery, writeQuery, readFragment, writeFragment, modify
// data
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
3. Refetching after update
refetchQueries: ["ReallyImportantQuery"],
onQueryUpdated(observableQuery) {
// Define any custom logic for determining whether to refetch
if (shouldRefetchQuery(observableQuery)) {
return observableQuery.refetch();
}
},
3. Refetching
์ ํ์ ์ผ๋ก ์ํ๋ ์ฟผ๋ฆฌ๋ง ๋ค์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ.
client.refetchQueries
interface RefetchQueriesOptions<
TCache extends ApolloCache<any>,
TResult = Promise<ApolloQueryResult<any>>,
> {
updateCache?: (cache: TCache) => void;
// ๋ค์ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ๋ฅผ ์ง์ .
include?: Array<string | DocumentNode> | "all" | "active";
onQueryUpdated?: (
observableQuery: ObservableQuery<any>,
diff: Cache.DiffResult<any>,
lastDiff: Cache.DiffResult<any> | undefined,
) => boolean | TResult;
optimistic?: boolean;
}
4. Subscriptions
์ค์๊ฐ์ผ๋ก ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ๋ฐฉ๋ฒ.
Web Socket ๋ฅผ ์ฌ์ฉํด์ ์๋ฒ์ ์ฐ๊ฒฐ์ ์ ์งํ๋ฉด์, ์ฟผ๋ฆฌ์ ๊ด๋ จํ ์ด๋ฒคํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ์กํ๋ ๋ฐฉ๋ฒ.
const COMMENTS_SUBSCRIPTION = gql`
subscription OnCommentAdded($postID: ID!) {
commentAdded(postID: $postID) {
id
content
}
}
`;
function CommentsPageWithData({ params }) {
const { subscribeToMore, ...result } = useQuery(
COMMENTS_QUERY,
{ variables: { postID: params.postID } }
);
return (
<CommentsPage
{...result}
subscribeToNewComments={() =>
subscribeToMore({
document: COMMENTS_SUBSCRIPTION,
variables: { postID: params.postID },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
post: {
comments: [newFeedItem, ...prev.post.comments]
}
});
}
})
}
/>
);
}
5. Fragments
์ฌํ์ฉํ๊ธฐ ์ฌ์ด ์ฟผ๋ฆฌ๋ค์ ๋ฐ๋ก ๋ถ๋ฆฌํด์ ๋๋ ๋์ ๊ฒ. (์ฌ์ฌ์ฉ์ฑ, ์ผ๊ด์ฑ)
import { gql } from '@apollo/client';
export const CORE_COMMENT_FIELDS = gql`
fragment CoreCommentFields on Comment {
id
postedBy {
username
displayName
}
createdAt
content
}
`;
import { gql } from '@apollo/client';
import { CORE_COMMENT_FIELDS } from './fragments';
export const GET_POST_DETAILS = gql`
${CORE_COMMENT_FIELDS}
query CommentsForPost($postId: ID!) {
post(postId: $postId) {
title
body
author
comments {
...CoreCommentFields
}
}
}
`;
// ...PostDetails component definition...
6. @defer
@defer ์ ์ฌ์ฉํ์ฌ, ๋จผ์ ๊ฐ์ ธ์ค๋ ํ๋๋ฅผ ์ ํ ์ ์๋ค.
query PersonQuery($personId: ID!) {
person(id: $personId) {
# Basic fields (fast) // ๋จผ์ ๊ฐ์ ธ์ด.
id
firstName
lastName
# highlight-start
# Friend fields (slower) // ๋๋ฒ์งธ
... @defer {
friends {
id
}
}
# highlight-end
}
}
7. Error handling
- GraphQl errors
- Syntax errors (e.g., a query was malformed)
- Validation errors (e.g., a query included a schema field that doesn't exist)
- Resolver errors (e.g., an error occurred while attempting to populate a query field
- Network errors
const MY_QUERY = gql`
query WillFail {
badField # This field's resolver produces an error
goodField # This field is populated successfully
}
`;
function ShowingSomeErrors() {
const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: "all" });
// none, ignore, all
if (loading) return <span>loading...</span>;
return (
<div>
<h2>Good: {data.goodField}</h2>
<pre>
Bad:{" "}
{error.graphQLErrors.map(({ message }, i) => (
<span key={i}>{message}</span>
))}
</pre>
</div>
);
}