5月27日 22:18
What are key points for GraphQL client development
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
javascriptimport { 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
javascriptimport { 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
javascriptimport { 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
javascriptimport { 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
javascriptimport { 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
javascriptconst [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
javascriptconst 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
javascriptimport { 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
javascriptfunction ClearCacheButton() { const client = useApolloClient(); const handleClick = () => { client.clearStore(); }; return <button onClick={handleClick}>Clear Cache</button>; }
5. Pagination
Offset-based Pagination
javascriptconst 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
javascriptconst 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
javascriptfunction 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
javascriptimport { 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
javascriptimport { 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
javascriptimport { 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
javascriptimport { 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
| Practice | Description |
|---|---|
| Use caching | Reduce network requests, improve performance |
| Implement optimistic updates | Improve user experience |
| Handle errors | Provide friendly error messages |
| Use pagination | Handle large datasets |
| Implement loading states | Improve user experience |
| Use subscriptions | Enable real-time updates |
| Optimize queries | Request only needed fields |
| Use persisted queries | Reduce network transmission |
| Implement retry mechanisms | Improve reliability |
| Monitor performance | Detect performance issues in time |
10. Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| Cache not updating | Cache policy misconfigured | Adjust cache policy, manually update cache |
| Slow queries | Requesting too much data | Optimize queries, use field selection |
| High memory usage | Too much cached data | Regularly clean cache, limit cache size |
| Subscription disconnected | Unstable network | Implement auto-reconnect mechanism |
| Poor error handling | Errors not handled properly | Implement global error handling |