与 Apollo 客户端的 React Context
通过使用 React 钩子使数据可访问来避免 prop 钻孔
React Context 通过无需在应用程序的每一级手动传递 props 即可传递数据的方式,在组件树中通过上下文进行数据传递,这种方式也被称为 prop 钻孔。
当您需要重命名属性或更新数据类型时,prop 钻孔可能会导致问题,并且会随着树中每个组件都需要知道它们可能不使用的属性而使应用程序变得臃肿。相反,我们的数据将通过一个 React 钩子来访问,这个钩子可以在嵌套组件树的任何地方调用。
为了帮助展示如何避免 prop 钻孔,我们将首先配置两个组件使用 React ContextQueryResultProvider
和 useQueryResult
。
组件 QueryResultProvider
用于封装一个组件树,为特定 GraphQL 查询 提供 查询
,这是一个 GraphQL 查询,并且可选地 变量
传递给查询。该组件通过从 Apollo 客户端 获取 useQuery
钩子执行 查询
并获取查询数据、加载和错误值。然后这些值将被保存在上下文中。
import React, { createContext, useContext } from "react";import { ApolloQueryResult, OperationVariables, useQuery } from "@apollo/client";interface QueryResult<TData> {data?: TData;error?: any;loading: boolean;}interface QueryContextValue<TData> {queryData: QueryResult<TData>;refetch: () => Promise<ApolloQueryResult<TData>>;}const QueryResultContext = createContext<QueryContextValue<any>>({queryData: { loading: true },refetch: () => Promise.resolve({} as ApolloQueryResult<any>),});interface QueryResultProviderProps<TVariables extends OperationVariables | undefined = object> {query: any;variables?: TVariables;children: React.ReactNode;}export const QueryResultProvider = ({ query, variables, children }: QueryResultProviderProps) => {const { data, error, loading, refetch } = useQuery(query, { variables });const value = { queryData: { data, error, loading }, refetch };return <QueryResultContext.Provider value={value}>{children}</QueryResultContext.Provider>;};export const useQueryResult = <TData = any,>(): QueryResult<TData> => {const context = useContext(QueryResultContext);if (!context) {throw new Error("useQueryResult must be used within a QueryResultProvider");}return context.queryData;};
在此示例中,我们使用QueryResultProvider
组件包装Users
组件,向其提供GET_USERS
查询。然后Users
组件使用useQueryResult
钩子从查询上下文访问数据、加载状态和错误。
import { QueryResultProvider, useQueryResult } from "./query-context";import { gql } from "@apollo/client";const GET_USERS = gql`query GetUsers {users {idname}}`;interface IUser {id: string;name: string;}const Users = () => {const { data, loading, error } = useQueryResult<IUser>();if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error.message}</div>;return (<div><h1>Users</h1><ul>{data.users.map((user) => (<li key={user.id}>{user.name}</li>))}</ul></div>);};const App = () => (<QueryResultProvider query={GET_USERS}><Users /></QueryResultProvider>);