10月8日至10日加入我们在纽约市的会议,了解关于 GraphQL 联邦和 API 平台工程的最新技巧、趋势和新闻。加入2024年纽约市 GraphQL 大会
文档
免费开始

Apollo Client 中的本地字段

使用单个 GraphQL 查询检索本地和远程数据


您的查询可以包含只限本地使用的字段,这些字段没有定义在您的's schema:

query ProductDetails($productId: ID!) {
product(id: $productId) {
name
price
isInCart @client # This is a local-only field
}
}

这些的值通过你想要的任何逻辑在本地上进行计算,例如从localStorage

如所示,一个可以包含仅本地字段 字段,这些字段是从你的GraphQL服务器获得的。

定义

假设我们正在构建一个电子商务应用程序。大多数产品详细信息存储在我们的后端服务器上,但我们要定义一个Product.isInCart布尔,它是客户端本地的。首先,我们为isInCart创建一个字段策略

字段策略指定了如何从你的Apollo客户端缓存中读取和写入单个字段的自定义逻辑。你可以为本地只读字段和远程检索字段定义字段策略。

策略主要在自定义缓存字段的行为中进行文档说明。本文特别说明了如何与本地只读字段一起使用它们。

你将在一个映射中定义你应用程序的字段策略,并将其提供给Apollo客户端的InMemoryCache构造函数。每个字段策略都是特定类型策略的子策略(就像相应的字段是特定类型的子字段一样)。

以下是一个示例InMemoryCache构造函数,它定义了Product.isInCart的字段策略:

const cache = new InMemoryCache({
typePolicies: { // Type policy map
Product: {
fields: { // Field policy map for the Product type
isInCart: { // Field policy for the isInCart field
read(_, { variables }) { // The read function for the isInCart field
return localStorage.getItem('CART').includes(
variables.productId
);
}
}
}
}
}
});

查询

const GET_PRODUCT_DETAILS = gql`
query ProductDetails($productId: ID!) {
product(id: $productId) {
name
price
isInCart @client
}
}
`;

注意:如果你将@client指令应用于具有子字段的字段,该指令将自动应用于所有子字段。

存储和突变

您可以使用Apollo Client查询和修改本地状态,无论您如何存储该状态。Apollo Client为表示本地状态提供了一些可选但非常有用的机制:

考虑仅本地,这些突变在其他本地字段中表达得类似。在使用Apollo Client的旧版本时,开发人员会使用@client定义仅本地的突变。然后,他们可以使用局部解析器来处理client.mutate /useMutation调用。但是,现在不再是这种情况。对于Apollo Client版本 > = 3的用户,我们建议使用writeQuerywriteFragment响应式变量来管理本地状态。

使用响应式变量存储和更新本地状态

Apollo Client的响应式响应式变量非常适合表示本地状态:

  • 您可以从应用中的任何地方读取和修改响应式变量,而无需使用GraphQL操作。
  • 与Apollo Client缓存不同,响应式变量不强制执行数据规范化,这意味着您可以以任何格式存储数据。
  • 如果一个字段's的值依赖于一个响应式变量's的值,并且那个变量的值,那么包含该字段的每个活跃查询都会自动刷新。

示例

回到我们的电子商务应用程序,假设我们想获取一个用户的购物车中的项目ID列表,并且这个列表是本地存储的。执行该查询的方法如下:

Cart.js
export const GET_CART_ITEMS = gql`
query GetCartItems {
cartItems @client
}
`;

让我们使用makeVar函数来初始化一个存储我们本地购物项目列表的响应式变量:

cache.js
import { makeVar } from '@apollo/client';
export const cartItemsVar = makeVar([]);

这初始化了一个包含空数组的响应式变量(你可以传递任何初始值到makeVar)。请注意,makeVar的返回值不是变量本身,而是一个函数。我们通过调用cartItemsVar()来获取变量的当前值,并通过调用cartItemsVar(newValue)来设置一个新的新值

接下来,让我们定义字段策略,用于cartItems。和往常一样,我们将它传递给InMemoryCache的构造函数:

cache.js
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
cartItems: {
read() {
return cartItemsVar();
}
}
}
}
}
});

以下read函数在查询cartItems时返回我们的响应式变量的值。

现在,让我们创建一个按钮组件,使用户能够将产品添加到购物车

AddToCartButton.js
import { cartItemsVar } from './cache';
// ... other imports
export function AddToCartButton({ productId }) {
return (
<div class="add-to-cart-button">
<Button onClick={() => cartItemsVar([...cartItemsVar(), productId])}>
Add to Cart
</Button>
</div>
);
}

单击时,此按钮更新cartItemsVar的值,以便将按钮关联的productId附加到。当这发生时,Apollo Client将通知每个包含cartItems字段

这是一个Cart组件,它使用GET_CART_ITEMS查询

Cart.js
export const GET_CART_ITEMS = gql`
query GetCartItems {
cartItems @client
}
`;
export function Cart() {
const { data, loading, error } = useQuery(GET_CART_ITEMS);
if (loading) return <Loading />;
if (error) return <p>ERROR: {error.message}</p>;
return (
<div class="cart">
<Header>My Cart</Header>
{data && data.cartItems.length === 0 ? (
<p>No items in your cart</p>
) : (
<Fragment>
{data && data.cartItems.map(productId => (
<CartItem key={productId} />
))}
</Fragment>
)}
</div>
);
}

组件会在cartItemsVar发生变化时自动刷新:

Cart.js
import { useReactiveVar } from '@apollo/client';
export function Cart() {
const cartItems = useReactiveVar(cartItemsVar);
return (
<div class="cart">
<Header>My Cart</Header>
{cartItems.length === 0 ? (
<p>No items in your cart</p>
) : (
<Fragment>
{cartItems.map(productId => (
<CartItem key={productId} />
))}
</Fragment>
)}
</div>
);
}

与前面的useQuery示例一样,每当cartItemsVar变量

重要:如果您在上面的示例中使用cartItemsVar而不是useReactiveVar(cartItemsVar),未来的变量更新将不会导致Cart组件重新渲染

在缓存中存储和修改本地状态

直接在Apollo Client缓存中存储本地状态提供了一些优势,但通常需要的代码比使用响应式变量多:

  • 您无需定义字段策略的本地字段,因为这些字段仅存在于缓存中。如果您查询一个未定义函数的字段,则默认情况下,Apollo Client会尝试直接从缓存中获取该字段的值。
  • 当您修改缓存的字段与以下操作之一相关时writeQuerywriteFragment,则包含该字段的每个活动查询都会自动刷新。

示例

例如,我们的应用程序定义以下查询:

const IS_LOGGED_IN = gql`
query IsUserLoggedIn {
isLoggedIn @client
}
`;

isLoggedIn字段是本地字段。我们可以使用writeQuery方法来将一个值直接写入此字段到Apollo Client缓存中,如下所示:

cache.writeQuery({
query: IS_LOGGED_IN,
data: {
isLoggedIn: !!localStorage.getItem("token"),
},
});

这会根据localStorage是否存在一个token来写入一个布尔值,指示一个活动的会话。

现在,我们的应用程序的组件可以根据isLoggedIn字段的值进行了渲染,无需为它定义read函数:

function App() {
const { data } = useQuery(IS_LOGGED_IN);
return data.isLoggedIn ? <Pages /> : <Login />;
}

这里有一个包含上述代码块的完整示例

请注意,即使在您将本地数据作为字段存储在Apollo Client缓存中,您仍然可以(并可能应该!)定义read函数来处理这些字段。一个read函数可以执行有用的自定义逻辑,例如在字段不在缓存中时返回默认值。

跨会话持久本地状态

默认情况下,响应式变量和InMemoryCache在会话之间不会持久化其状态(例如,如果用户刷新浏览器)。要持久化此状态,您需要添加相应的逻辑。

apollo3-cache-persist库帮助您在会话之间持久化和重新活化Apollo Client缓存。详情请参阅持久化缓存

目前还没有用于持久化响应式变量的内置API,但您可以在它们被修改时将变量值写入localStorage(或其他存储),在应用加载时用存储的值(如果有的话)初始化这些变量。

修改

修改本地仅字段值的方法取决于您如何存储该字段:

  • 如果您正在使用响应式变量,您只需要设置响应式变量的新值。存储位置。Apollo Client会自动检测该更改并触发包含受影响字段的每个活动操作刷新。

  • 如果您直接使用缓存(请见直接使用缓存部分),请调用 writeQuerywriteFragment,或者 cache.modify(此处有完整文档))来修改缓存字段。这些方法类似于响应式变量,都会触发每个受影响的活动操作刷新。

  • 如果您使用其他存储方法,例如 localStorage,请使用您应用的方法设置字段的新的值。然后,您可以通过调用 cache.evict强制刷新每个受影响的操作。在您的调用中,提供包含该字段的实体的 id 以及只存在于本地的字段的名称。

将本地专用字段作为 GraphQL 变量使用

如果您的 GraphQL 查询使用变量,则该查询的本地专用字段可以提供这些变量的

为了这样做,您可以在字段上应用 @export(as: "variableName") 指令,如下所示:

const GET_CURRENT_AUTHOR_POST_COUNT = gql`
query CurrentAuthorPostCount($authorId: Int!) {
currentAuthorId @client @export(as: "authorId")
postCount(authorId: $authorId)
}
`;

在上面的查询中,本地专用字段 currentAuthorId 的结果用作传递给 postCount$authorId 变量的值。

即使 postCount 也是一个本地专用字段(即,如果它也标记为 @client),您也可以这样做。

@export 指令的使用注意事项

  • 要使用 @export 指令,一个字段 还必须使用 @client 指令。换句话说,只有本地专用字段可以用于变量值。

  • 必须放在任何使用该变量的字段之前的字段才可以将变量值 @export

  • 如果操作中的多个字段都使用 @export 指令将它们的值分配给同一个变量,则列出的最后一个字段具有优先权。在开发模式下发生这种情况时,Apollo Client 会记录一条警告信息。

  • 初看之下,@export 指令似乎违反了 GraphQL 规范的要求 ,即操作执行的顺序不应影响其结果:

    …非顶层 字段的解析必须始终是无副作用的和幂等的,执行的顺序不应影响结果,因此服务器有自由按其认为最优化顺序执行字段条目。

    然而,所有 @export 输出的 变量 值都应在将操作发送到远程服务器之前填充。只有本地字段可以使用 @export 指令,这些字段在传输之前会被从 中移除。

上一页
概述
下一页
响应式变量
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,商业名称为Apollo GraphQL。

隐私策略

公司