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

GraphQL 客户端开发有哪些关键要点

2月21日 17:00

GraphQL 客户端开发指南

GraphQL 客户端开发是构建现代前端应用的关键部分。以下是使用 Apollo Client 和其他 GraphQL 客户端的全面指南。

1. Apollo Client 配置

基本配置

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' } } });

带认证的配置

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. 查询数据

使用 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> ); }

使用 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. 修改数据

使用 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> ); }

乐观更新

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. 缓存管理

配置缓存策略

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'] } } });

手动更新缓存

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>; }

清除缓存

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

5. 分页

基于偏移的分页

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> ); }

基于游标的分页

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. 错误处理

处理 GraphQL 错误

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>; }

全局错误处理

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. 订阅

使用 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. 性能优化

使用 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() });

批量查询

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. 客户端开发最佳实践

实践说明
使用缓存减少网络请求,提高性能
实现乐观更新提升用户体验
处理错误提供友好的错误提示
使用分页处理大量数据
实现加载状态改善用户体验
使用订阅实现实时更新
优化查询只请求需要的字段
使用 persisted queries减少网络传输
实现重试机制提高可靠性
监控性能及时发现性能问题

10. 常见问题及解决方案

问题原因解决方案
缓存未更新缓存策略配置不当调整缓存策略,手动更新缓存
查询慢请求过多数据优化查询,使用字段选择
内存占用高缓存数据过多定期清理缓存,限制缓存大小
订阅断开网络不稳定实现自动重连机制
错误处理不当未正确处理错误实现全局错误处理
标签:GraphQL