乐闻世界logo
搜索文章和话题

What are key points for GraphQL client development

2月21日 17:00

GraphQL Client Development Guide

GraphQL client development is a key part of building modern frontend applications. Here is a comprehensive guide to using Apollo Client and other GraphQL clients.

1. Apollo Client Configuration

Basic Configuration

javascript
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'; const httpLink = new HttpLink({ uri: 'https://api.example.com/graphql', credentials: 'include' }); const client = new ApolloClient({ link: httpLink, cache: new InMemoryCache(), defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network', errorPolicy: 'all' }, query: { fetchPolicy: 'network-only', errorPolicy: 'all' }, mutate: { errorPolicy: 'all' } } });

Configuration with Authentication

javascript
import { ApolloClient, InMemoryCache, createHttpLink, ApolloLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; const httpLink = createHttpLink({ uri: 'https://api.example.com/graphql' }); const authLink = setContext((_, { headers }) => { const token = localStorage.getItem('token'); return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '' } }; }); const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache() });

2. Querying Data

Using useQuery Hook

javascript
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers($limit: Int, $offset: Int) { users(limit: $limit, offset: $offset) { id name email createdAt } } `; function UserList() { const { loading, error, data, fetchMore } = useQuery(GET_USERS, { variables: { limit: 10, offset: 0 }, notifyOnNetworkStatusChange: true }); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <div> {data.users.map(user => ( <div key={user.id}> <h3>{user.name}</h3> <p>{user.email}</p> </div> ))} <button onClick={() => fetchMore({ variables: { offset: data.users.length }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) return prev; return { users: [...prev.users, ...fetchMoreResult.users] }; } })} > Load More </button> </div> ); }

Using Lazy Query

javascript
import { useLazyQuery, gql } from '@apollo/client'; const GET_USER = gql` query GetUser($id: ID!) { user(id: $id) { id name email } } `; function UserSearch() { const [getUser, { loading, error, data }] = useLazyQuery(GET_USER); const [userId, setUserId] = useState(''); return ( <div> <input value={userId} onChange={(e) => setUserId(e.target.value)} placeholder="Enter user ID" /> <button onClick={() => getUser({ variables: { id: userId } })}> Search </button> {loading && <div>Loading...</div>} {error && <div>Error: {error.message}</div>} {data && ( <div> <h3>{data.user.name}</h3> <p>{data.user.email}</p> </div> )} </div> ); }

3. Mutating Data

Using useMutation Hook

javascript
import { useMutation, gql } from '@apollo/client'; const CREATE_USER = gql` mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name email } } `; function CreateUserForm() { const [createUser, { loading, error }] = useMutation(CREATE_USER, { update(cache, { data: { createUser } }) { cache.modify({ fields: { users(existingUsers = []) { const newUserRef = cache.writeFragment({ data: createUser, fragment: gql` fragment NewUser on User { id name email } ` }); return [...existingUsers, newUserRef]; } } }); } }); const [name, setName] = useState(''); const [email, setEmail] = useState(''); const handleSubmit = (e) => { e.preventDefault(); createUser({ variables: { input: { name, email } } }); }; return ( <form onSubmit={handleSubmit}> <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" /> <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <button type="submit" disabled={loading}> {loading ? 'Creating...' : 'Create User'} </button> {error && <div>Error: {error.message}</div>} </form> ); }

Optimistic Updates

javascript
const [updateUser, { loading }] = useMutation(UPDATE_USER, { optimisticResponse: (variables) => ({ updateUser: { __typename: 'User', id: variables.id, name: variables.input.name, email: variables.input.email } }), update(cache, { data: { updateUser } }) { cache.writeFragment({ id: `User:${updateUser.id}`, fragment: gql` fragment UpdateUser on User { name email } `, data: updateUser }); } });

4. Cache Management

Configure Cache Policies

javascript
const cache = new InMemoryCache({ typePolicies: { Query: { fields: { users: { keyArgs: ['filter', 'sort'], merge(existing = [], incoming) { return [...existing, ...incoming]; } }, read(existing, { args: { offset, limit } }) { return existing && existing.slice(offset, offset + limit); } }, user: { read(_, { args, toReference }) { return toReference({ __typename: 'User', id: args.id }); } } } }, User: { keyFields: ['id', 'email'] } } });

Manual Cache Updates

javascript
import { useApolloClient } from '@apollo/client'; function UpdateUserButton({ userId, newData }) { const client = useApolloClient(); const handleClick = () => { client.writeFragment({ id: `User:${userId}`, fragment: gql` fragment UserFragment on User { name email } `, data: newData }); }; return <button onClick={handleClick}>Update User</button>; }

Clear Cache

javascript
function ClearCacheButton() { const client = useApolloClient(); const handleClick = () => { client.clearStore(); }; return <button onClick={handleClick}>Clear Cache</button>; }

5. Pagination

Offset-based Pagination

javascript
const GET_POSTS = gql` query GetPosts($offset: Int, $limit: Int) { posts(offset: $offset, limit: $limit) { id title content author { name } } } `; function PostList() { const { loading, data, fetchMore } = useQuery(GET_POSTS, { variables: { offset: 0, limit: 10 } }); return ( <div> {data?.posts.map(post => ( <div key={post.id}> <h3>{post.title}</h3> <p>{post.content}</p> </div> ))} <button onClick={() => fetchMore({ variables: { offset: data.posts.length }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) return prev; return { posts: [...prev.posts, ...fetchMoreResult.posts] }; } })} > Load More </button> </div> ); }

Cursor-based Pagination

javascript
const GET_POSTS = gql` query GetPosts($after: String, $first: Int) { posts(after: $after, first: $first) { edges { node { id title content } cursor } pageInfo { hasNextPage endCursor } } } `; function PostList() { const { loading, data, fetchMore } = useQuery(GET_POSTS, { variables: { first: 10 } }); return ( <div> {data?.posts.edges.map(({ node }) => ( <div key={node.id}> <h3>{node.title}</h3> <p>{node.content}</p> </div> ))} {data?.posts.pageInfo.hasNextPage && ( <button onClick={() => fetchMore({ variables: { after: data.posts.pageInfo.endCursor }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) return prev; return { posts: { ...fetchMoreResult.posts, edges: [ ...prev.posts.edges, ...fetchMoreResult.posts.edges ] } }; } })} > Load More </button> )} </div> ); }

6. Error Handling

Handling GraphQL Errors

javascript
function UserList() { const { loading, error, data } = useQuery(GET_USERS); if (loading) return <div>Loading...</div>; if (error) { if (error.graphQLErrors) { return ( <div> GraphQL Errors: {error.graphQLErrors.map((err, i) => ( <div key={i}>{err.message}</div> ))} </div> ); } if (error.networkError) { return <div>Network Error: {error.networkError.message}</div>; } return <div>Error: {error.message}</div>; } return <div>{/* render data */}</div>; }

Global Error Handling

javascript
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink } from '@apollo/client'; import { onError } from '@apollo/client/link/error'; const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => { if (graphQLErrors) { graphQLErrors.forEach(({ message, locations, path }) => { console.error( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` ); }); } if (networkError) { console.error(`[Network error]: ${networkError}`); } }); const httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' }); const client = new ApolloClient({ link: errorLink.concat(httpLink), cache: new InMemoryCache() });

7. Subscriptions

Using useSubscription Hook

javascript
import { useSubscription, gql } from '@apollo/client'; const MESSAGE_ADDED = gql` subscription OnMessageAdded($roomId: ID!) { messageAdded(roomId: $roomId) { id text author { name } createdAt } } `; function ChatRoom({ roomId }) { const { data, loading, error } = useSubscription(MESSAGE_ADDED, { variables: { roomId } }); if (loading) return <div>Connecting...</div>; if (error) return <div>Error: {error.message}</div>; return ( <div> <h3>New Message:</h3> <p>{data.messageAdded.text}</p> <small>By {data.messageAdded.author.name}</small> </div> ); }

8. Performance Optimization

Using Persisted Queries

javascript
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries'; import { sha256 } from 'crypto-hash'; const persistedQueryLink = createPersistedQueryLink({ sha256, useGETForHashedQueries: true }); const client = new ApolloClient({ link: persistedQueryLink.concat(httpLink), cache: new InMemoryCache() });

Batch Queries

javascript
import { useQuery, gql } from '@apollo/client'; const GET_MULTIPLE_USERS = gql` query GetMultipleUsers($ids: [ID!]!) { users(ids: $ids) { id name email } } `; function UserList({ userIds }) { const { loading, data } = useQuery(GET_MULTIPLE_USERS, { variables: { ids: userIds } }); if (loading) return <div>Loading...</div>; return ( <div> {data.users.map(user => ( <div key={user.id}>{user.name}</div> ))} </div> ); }

9. Client Development Best Practices

PracticeDescription
Use cachingReduce network requests, improve performance
Implement optimistic updatesImprove user experience
Handle errorsProvide friendly error messages
Use paginationHandle large datasets
Implement loading statesImprove user experience
Use subscriptionsEnable real-time updates
Optimize queriesRequest only needed fields
Use persisted queriesReduce network transmission
Implement retry mechanismsImprove reliability
Monitor performanceDetect performance issues in time

10. Common Issues and Solutions

IssueCauseSolution
Cache not updatingCache policy misconfiguredAdjust cache policy, manually update cache
Slow queriesRequesting too much dataOptimize queries, use field selection
High memory usageToo much cached dataRegularly clean cache, limit cache size
Subscription disconnectedUnstable networkImplement auto-reconnect mechanism
Poor error handlingErrors not handled properlyImplement global error handling
标签:GraphQL