Skip to content

Latest commit

 

History

History
210 lines (152 loc) · 10.8 KB

ch06_05.md

File metadata and controls

210 lines (152 loc) · 10.8 KB

缓存

作为开发人员,我们从事网络请求最小化业务。我们不希望我们的用户发出无必要的请求。为了尽量减少我们的应用程序发送的网络请求数量,我们可以更深入地研究如何自定义 Apollo 缓存。

获取策略

默认情况下,Apollo 客户端将数据存储在本地 JavaScript 变量中。每次我们创建客户端时,都会为我们创建一个缓存。每次我们发送操作时,响应都会缓存在本地。fetchPolicy 告诉 Apollo Client 到哪里寻找数据来解决操作:本地缓存或网络请求。默认的 fetchPolicy 是缓存优先的。这意味着客户端将在本地缓存中查找数据以解决操作。如果客户端可以在不发送网络请求的情况下解决操作,它将这样做。但是,如果解析查询的数据不在缓存中,则客户端将向 GraphQL 服务发送网络请求。

另一种类型的 fetchPolicy 是仅缓存的。该策略告诉客户端只查看缓存,永远不要发送网络请求。如果缓存中不存在用于完成查询的数据,则会抛出错误。

查看 src/Users.js,并在 Users 组件中找到 Query。我们可以简单地通过添加 fetchPolicy 属性来更改单个查询的获取策略:

<Query query={{ query: ROOT_QUERY }} fetchPolicy="cache-only">

目前,如果我们将此查询的策略设置为仅缓存并刷新浏览器,我们应该会看到一个错误,因为 Apollo Client 仅在缓存中查找数据来解析我们的查询,而当应用程序运行时该数据不存在 开始。要清除错误,请将获取策略更改为 cache-and-network :

<Query query={{ query: ROOT_QUERY }} fetchPolicy="cache-and-network">

该应用程序再次运行。缓存和网络策略始终立即从缓存中解析查询,并另外发送网络请求以获取最新数据。如果本地缓存不存在,就像应用程序启动时的情况一样,该策略将简单地从网络中检索数据。其他策略包括:

  • network-only 始终发送网络请求来解析查询
  • no-cache 始终发送网络请求来解析数据,并且不缓存结果响应。

持久化缓存

可以在客户端本地保存缓存。这释放了缓存优先策略的力量,因为当用户返回到应用程序时缓存已经存在。在这种情况下,缓存优先策略会立即从现有的本地缓存中解析数据,根本不会向网络发送请求。

要在本地保存缓存数据,我们需要安装一个 npm 包:

npm install apollo-cache-persist

apollo-cache-persist 包包含一个功能,可以通过在缓存发生变化时将其保存到本地存储来增强缓存。要实现缓存持久性,我们需要创建自己的缓存对象并在配置应用程序时将其添加到客户端。

将以下代码添加到 src/index.js 文件中:

import ApolloClient, { InMemoryCache } from "apollo-boost";
import { persistCache } from "apollo-cache-persist";

const cache = new InMemoryCache();
persistCache({
  cache,
  storage: localStorage,
});

const client = new ApolloClient({
  cache,
  //...
});

首先,我们使用 apollo-boost 提供的 InMemoryCache 构造函数创建了自己的缓存实例。接下来,我们从 apollo-cache-persist 导入了 persistCache 方法。使用 InMemoryCache,我们创建一个新的缓存实例并将其与存储位置一起发送到 persistCache 方法。我们选择将缓存保存在浏览器窗口的 localStorage 存储中。这意味着一旦我们启动我们的应用程序,我们应该看到我们的缓存值保存到我们的商店。您可以通过添加以下语法来查看它:

console.log(localStorage['apollo-cache-persist'])

下一步是在启动时检查 localStorage 以查看我们是否已经保存了缓存。如果我们这样做,那么我们将要在创建客户端之前用该数据初始化我们的本地缓存:

const cache = new InMemoryCache();
persistCache({
  cache,
  storage: localStorage,
});

if (localStorage["apollo-cache-persist"]) {
  let cacheData = JSON.parse(localStorage["apollo-cache-persist"]);
  cache.restore(cacheData);
}

现在我们的应用程序将在启动前加载任何缓存数据。如果我们确实有数据保存在键 apollo-cache-persist 下,那么我们将使用 cache.restore(cacheData) 方法将它添加到缓存实例。

通过有效地使用 Apollo Client 的缓存,我们已经成功地减少了对我们服务的网络请求数量。在下一节中,我们将了解如何将数据直接写入本地缓存。

更新缓存

查询组件能够直接从缓存中读取。这就是使像仅缓存这样的获取策略成为可能的原因。我们还可以直接与 Apollo Cache 交互。我们可以从缓存中读取当前数据,也可以直接将数据写入缓存。每次我们更改存储在缓存中的数据时,reactapollo 都会检测到该更改并重新呈现所有受影响的组件。我们所要做的就是更改缓存,UI 将自动更新以匹配更改。

使用 GraphQL 从 Apollo Cache 读取数据。您阅读查询。使用 GraphQL 将数据写入 Apollo 缓存,您将数据写入查询。考虑位于 src/App.js 中的 ROOT_QUERY:

export const ROOT_QUERY = gql`
  query allUsers {
    totalUsers
    allUsers {
      ...userInfo
    }
    me {
      ...userInfo
    }
  }
  fragment userInfo on User {
    githubLogin
    name
    avatar
  }
`;

此查询在其选择集中包含三个字段:totalUsers、allUsers 和 me。我们可以使用 cache.readQuery 方法读取当前存储在缓存中的任何数据:

let { totalUsers, allUsers, me } = cache.readQuery({ query: ROOT_QUERY })

在这行代码中,我们获得了存储在缓存中的 totalUsers、allUsers 和 me 的值。

我们也可以使用 cache.writeQuery 方法直接向 ROOT_QUERY 的 totalUsers、allUsers 和 me 字段写入数据:

cache.writeQuery({
  query: ROOT_QUERY,
  data: {
    me: null,
    allUsers: [],
    totalUsers: 0,
  },
});

在此示例中,我们将从缓存中清除所有数据并重置 ROOT_QUERY 中所有字段的默认值。因为我们使用的是 react-apollo,所以这个更改会触发 UI 更新并从当前 DOM 中清除整个用户列表。

将数据直接写入缓存的好地方是 AuthorizedUser 组件中的注销函数内部。目前此功能正在删除用户的令牌,但在单击“重新获取”按钮或刷新浏览器之前,UI 不会更新。为了改进这个特性,我们会在用户注销时直接从缓存中清除当前用户。

首先,我们需要确保该组件可以在其 props 中访问客户端。传递此属性的最快方法之一是使用 withApollo 高阶组件。这会将客户端添加到 AuthorizedUser 组件的属性中。由于这个组件已经使用了 withRouter 高阶组件,我们将使用 compose 函数来确保 AuthorizedUser 组件被两个高阶组件包裹:

import { Query, Mutation, withApollo, compose } from 'react-apollo'

class AuthorizedUser extends Component {
  ...
}

export default compose(withApollo, withRouter)(AuthorizedUser)

使用 compose,我们将 withApollo 和 withRouter 函数组装成一个函数。withRouter 将 Router 的历史记录添加到属性中,而 withApollo 将 Apollo Client 添加到属性中。这意味着我们可以在注销方法中访问 Apollo Client 并使用它从缓存中删除有关当前用户的详细信息:

logout = () => {
  localStorage.removeItem('token')
  let data = this.props.client.readQuery({ query: ROOT_QUERY })
  data.me = null
  this.props.client.writeQuery({ query: ROOT_QUERY, data })
}

上面的代码不仅从 localStorage 中删除了当前用户的令牌,还清除了缓存中保存的当前用户的 me 字段。现在,当用户注销时,他们将立即看到“使用 GitHub 登录”按钮,而无需刷新浏览器。只有当 ROOT_QUERY 对我来说没有任何值时才会呈现此按钮。

我们可以通过直接使用缓存来彻底改进应用程序的另一个地方是 src/Users.js 文件。目前,当我们点击“添加假用户”按钮时,一个突变被发送到 GraphQL 服务。呈现“添加假用户”按钮的 Mutation 组件包含以下属性:

refetchQueries={[{ query: ROOT_QUERY }]}

这个属性告诉客户端在变更完成后向我们的服务发送一个额外的查询。但是,我们已经在突变本身的响应中收到了新的假用户列表:

mutation addFakeUsers($count:Int!) {
  addFakeUsers(count:$count) {
    githubLogin
    name
    avatar
  }
}

由于我们已经有了新的虚假用户列表,因此无需返回服务器获取相同的信息。我们需要做的是在突变的响应中获取这个新的用户列表,并将其直接添加到缓存中。一旦缓存发生变化,UI 将随之改变。

在处理 addFakeUsers 突变的 Users.js 文件中找到 Mutation 组件,并将 refetchQueries 替换为更新属性:

<Mutation
  mutation={ADD_FAKE_USERS_MUTATION}
  variables={{ count: 1 }}
  update={updateUserCache}
>
  {(addFakeUsers) => <button onClick={addFakeUsers}>Add Fake User</button>}
</Mutation>;

现在,当突变完成后,响应数据将被发送到一个名为 updateUserCache 的函数:

const updateUserCache = (cache, { data: { addFakeUsers } }) => {
  let data = cache.readQuery({ query: ROOT_QUERY });
  data.totalUsers += addFakeUsers.length;
  data.allUsers = [...data.allUsers, ...addFakeUsers];
  cache.writeQuery({ query: ROOT_QUERY, data });
};

当 Mutation 组件调用 updateUserCache 函数时,它会发送缓存和已在突变响应中返回的数据。

我们想将假用户添加到当前缓存中,因此我们将使用 cache.readQuery({ query: ROOT_QUERY }) 读取缓存中已有的数据并将其添加到其中。首先,我们将增加总用户数,data.totalUsers = addFakeUsers.length。然后,我们会将当前用户列表与我们从突变中收到的虚假用户连接起来。现在当前数据已经改变,可以使用 cache.writeQuery({ query: ROOT_QUERY, data }) 将其写回缓存。替换缓存中的数据将导致 UI 更新并显示新的假用户。

至此,我们已经完成了应用程序用户部分的第一个版本。我们可以列出所有用户,添加假用户,并使用 GitHub 登录。我们已经使用 Apollo Server 和 Apollo Client 构建了一个全栈 GraphQL 应用程序。Query 和 Mutation 组件是我们可以用来快速开始使用 Apollo Client 和 React 开发客户端的工具。

第 7 章中,我们将了解如何将订阅和文件上传合并到 PhotoShare 应用程序中。我们还讨论了 GraphQL 生态系统中的新兴工具,您可以将它们整合到您的项目中。

👈 上一节 下一节 👉