深入理解 Apollo Client 的缓存策略

news2024/11/11 14:50:24

前言

上一篇文章:Nextjs 使用 graphql,并且接入多个节点 介绍了如何接入 graphql,并且使用 Apollo client 来请求和操作数据。后面深入了解了一下其缓存策略,想着有必要整理出来,作为后续学习。有任何问题还请批评指正。

Apollo Client 的缓存策略

Apollo Client 实现了一个缓存,将其查询结果存储在浏览器中,从而避免了不必要的网络调用。而具体是使用缓存中的数据还是直接网络请求后端数据,这取决于具体的产品需求。而我们将了解其支持的缓存策略,从而根据实际需求场景进行选择。

缓存策略

cache-first

Apollo 的默认获取策略是 cache-first。如果您没有自行设置获取策略,则将使用此策略。它更倾向于快速响应查询,而不是获取最新的数据。如果您不希望数据发生变化,或者您希望在发生更改时明确更新缓存,那么这是一个不错的选择。使用缓存优先获取策略:

  1. 您查询一些数据。Apollo 检查缓存中的数据。如果所有数据都存在于缓存中,则直接跳至步骤 4。
  2. 如果缓存缺少您要求的某些数据,Apollo 将向您的 API 发出网络请求。
  3. API 用数据进行响应,Apollo 使用它来更新缓存。
  4. 返回了请求的数据。
    缓存优先策略
cache-and-network

cache-and-network是显示频繁更新的数据的一个好选择。

  1. 您查询一些数据。Apollo 检查缓存中的数据。
  2. 如果数据在缓存中,则返回该缓存数据。
  3. 无论在第二步是否找到任何数据,将查询传递给 API 以获取最新的数据。
  4. 使用来自 API 的任何新数据更新缓存。
  5. 返回更新后的 API 数据。
    缓存和网络策略
network-only

如果您不想缓存任何数据,那么使用network-only策略来获取数据。此策略倾向于显示最新信息而不是快速响应。与cache-and-network不同,此策略永远不会先从缓存中获取可能过时的数据,但是它将更新缓存,保证始终返回最新的数据状态。

  1. Apollo 不会检查缓存而向您的数据发出网络请求。
  2. 服务器响应您的数据并且缓存被更新。
  3. 数据已返回。
    仅限网络策略
no-cache

no-cache策略与network-only类似,但它跳过了更新缓存的步骤。如果您根本不想在缓存中存储某些信息,这可能是合适的。
无缓存策略

cache-only

no-cache相反 ,此获取策略避免发出任何网络请求。如果您查询的数据在缓存中不可用,它将抛出错误。如果您想要向用户显示一致的信息,而忽略任何服务器端更改,这会很有用。如果您想要让应用程序的某些部分能够离线工作,这也很有用。

  1. Apollo 检查缓存中是否存在查询的数据。
  2. 如果所有数据都存在于缓存中,则返回该数据(否则,抛出错误)。
    仅缓存策略

设置策略

您可以为整个应用程序设置一个获取策略,也可以为给定查询设置单独的获取策略。

统一策略

在初始化 Apollo client 时,传递配置参数即可:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const cache = new InMemoryCache();

const client = new ApolloClient({
  // Provide required constructor fields
  cache: cache,
  // ...other configuration
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
    },
  },
});

您还可以指定查询的 nextFetchPolicy。如果这样做,fetchPolicy 将用于查询的第一次执行,然后 nextFetchPolicy 用于确定查询如何响应未来的缓存更新:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const cache = new InMemoryCache();

const client = new ApolloClient({
  // Provide required constructor fields
  cache: cache,
  // ...other configuration
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
  	  nextFetchPolicy: 'cache-first',
    },
  },
});

例如,如果您希望查询始终发出初始网络请求,但之后您可以轻松地从缓存中读取数据。

独立策略

默认情况下,useQuery 会采用默认的策略 缓存优先 检查 Apollo 客户端缓存,以查看您请求的所有数据是否已在本地可用。如果所有数据都在本地可用,则 useQuery 返回该数据并且不会查询您的 GraphQL 服务器。
您可以为给定查询指定不同的获取策略。为此,请在对 useQuery 的调用中包含 fetchPolicy 选项:

const { loading, error, data } = useQuery(GET_TODO, {
  fetchPolicy: 'network-only', // Doesn't check cache before making a network request
});

同样的,单个请求也可以配置 nextFetchPolicy

const { loading, error, data } = useQuery(GET_TODO, {
  fetchPolicy: 'network-only', // Used for first execution
  nextFetchPolicy: 'cache-first', // Used for subsequent executions
});

Apollo 缓存策略原理

上面的策略提过,Apollo 客户端将 GraphQL 查询的结果存储在本地标准化内存缓存中。这使得 Apollo Client 几乎可以立即响应对已缓存数据的查询,甚至无需发送网络请求。
例如,当您第一次对 id 为 5 的 Book 对象执行 GetBook 查询时,流程如下所示:
首次请求
以后每次您为同一对象执行 GetBook 时,流程将如下所示:
后续请求
那么数据是如何缓存的呢?
Apollo 客户端的 InMemoryCache 将数据存储为可以相互引用的对象的扁平查找表。这些对象对应于 GraphQL 查询返回的对象。如果多个查询获取同一对象的不同字段,则单个缓存对象可能包含多个查询返回的字段。
缓存是扁平化的,但 GraphQL 查询返回的对象通常不是平坦的!事实上,它们的嵌套可以是任意深度。看一下这个示例查询响应:

{
  "data": {
    "person": {
      "__typename": "Person",
      "id": "cGVvcGxlOjE=",
      "name": "Luke Skywalker",
      "homeworld": {
        "__typename": "Planet",
        "id": "cGxhbmV0czox",
        "name": "Tatooine"
      }
    }
  }
}

此响应包含一个 Person 对象,该对象又在其 homeworld 字段中包含一个 Planet 对象。那么 InMemoryCache 如何在平面查找表中存储嵌套数据呢?在存储这些数据之前,缓存需要对其进行规范化。

数据标准化

每当 Apollo 客户端缓存收到查询响应数据时,它都会执行以下操作:

1. 识别对象

首先,缓存识别查询响应中包含的所有不同对象。在上面的例子中,有两个对象:

  • A Person with id cGVvcGxlOjE=
  • A Planet with id cGxhbmV0czox
2. 生成缓存 ID

识别出所有对象后,缓存会为每个对象生成一个缓存 ID。缓存 ID 唯一标识 InMemoryCache 中的特定对象。
默认情况下,对象的缓存 ID 是对象的 __typenameid(或 _id)字段的串联,并用冒号 (:) 分隔。在这里插入代码片
因此,上例中对象的默认缓存 ID 为:

  • Person:cGVvcGxlOjE=
  • Planet:cGxhbmV0czox
3. 用引用替换对象字段

接下来,缓存获取包含对象的每个字段,并将其值替换为对适当对象的引用。
例如,下面是上面示例中引用替换之前的 Person 对象:

{
  "__typename": "Person",
  "id": "cGVvcGxlOjE=",
  "name": "Luke Skywalker",
  "homeworld": {
    "__typename": "Planet",
    "id": "cGxhbmV0czox",
    "name": "Tatooine"
  }
}

这是替换后的同一个对象:

{
  "__typename": "Person",
  "id": "cGVvcGxlOjE=",
  "name": "Luke Skywalker",
  "homeworld": {
    "__ref": "Planet:cGxhbmV0czox"
  }
}

homeworld 字段现在包含对适当标准化 Planet 对象的引用。
规范化可以显着减少数据重复,还可以帮助您的本地数据与服务器保持同步。

4. 存储标准化对象

最后,得到的对象全部存储在缓存的扁平查找表中。
每当传入对象与现有缓存对象具有相同的缓存 ID 时,这些对象的字段就会合并:

  • 如果传入对象和现有对象共享任何字段,则传入对象将覆盖这些字段的缓存值。
  • 仅保留现有对象或仅传入对象中出现的字段。

自定义 fields

那么如果数据对象不存在 id 或者 _id 时,Apollo 的标准化又该如何实现呢?
您可以自定义 InMemoryCache 如何为架构中的各个类型生成缓存 ID。这非常有用,尤其是当类型使用 id_id 之外的字段(或多个字段!)作为其唯一标识符时。
为了实现这一点,您需要为每个要自定义的类型定义一个 TypePolicy。您可以在提供给 InMemoryCache 构造函数的选项对象中指定所有缓存的 typePolicies
在相关 TypePolicy 对象中包含 keyFields 字段,如下所示:

const cache = new InMemoryCache({
  typePolicies: {
    Product: {
      // In an inventory management system, products might be identified
      // by their UPC.
      keyFields: ["upc"],
    },
    Person: {
      // In a user account system, the combination of a person's name AND email
      // address might uniquely identify them.
      keyFields: ["name", "email"],
    },
    Book: {
      // If one of the keyFields is an object with fields of its own, you can
      // include those nested keyFields by using a nested array of strings:
      keyFields: ["title", "author", ["name"]],
    },
    AllProducts: {
      // Singleton types that have no identifying field can use an empty
      // array for their keyFields.
      keyFields: [],
    },
  },
});

此示例显示了具有不同 keyField 的各种 typePolicies

  • Product 类型使用其 upc 字段作为其标识字段
  • Person 类型使用其nameemail字段的组合
  • Book 类型包含一个子字段作为其缓存 ID 的一部分
    • [name] 项表示数组中前一个字段(author)的 name 字段是缓存 ID 的一部分。Bookauthor字段必须是一个包含name字段的对象,此字段才有效。
    • Book 类型的有效缓存 ID 具有以下结构:Book:{"title":"Fahrenheit 451","author":{"name":"Ray Bradbury"}}
  • AllProducts 类型说明了单例类型的特殊策略。如果缓存仅包含一个 AllProducts 对象,并且该对象没有标识字段,则可以为其 keyFields 提供一个空数组。

更新 cache

那么我们如何更新 cache 呢?

Apollo 自动更新

Apollo 的每次 useQuery 请求,都会自动更新相同 __ref 的对象。而当我们 useMutation 更新数据后,如果返回的为修改后的实体对象,则会自动更新缓存。

const EDIT_TODO = gql`
	mutation EditTodo ($id: Int!, $text: String!) {
	  editTodo (id: $id, text: $text) {
	    success
	    todo {          
	      id
	      text 
	      completed
	    }
	  }
	}
`

无论是什么操作,只要我们返回一个包含 id 和更改字段的新对象,Apollo Client 就可以自动更新缓存中的项并触发 UI 的重新渲染。

手动修改缓存

Apollo client 支持不同的方式来手动更新缓存,我这边仅介绍我在开发中最常用的一种方式,其他的可以通过查看 InMemoryCache对象类型来学习更详细的方法。

cache.modify

InMemoryCachemodify方法使您可以直接修改单个缓存字段的值,甚至完全删除字段。
需要注意的是:

  • modify 会绕过您定义的任何 merge 函数,这意味着字段始终会准确地用您指定的值覆盖。
  • modify 无法写入缓存中尚不存在的字段。

modify 方法采用以下参数:

  • 要修改的缓存对象的ID(建议使用cache.identify获取)
  • 要执行的修饰符函数的映射(每个要修改的字段都有一个)
  • 可选的broadcastoptimistic布尔值来自定义行为
    修饰符函数适用于单个字段。它将关联字段的当前缓存值作为参数,并返回应替换它的任何值。这也就是为何上述提到,无法写入缓存中不存在的字段,它仅针对已存在的字段进行修改。

以下是修改name字段以将其值转换为大写的示例:

cache.modify({
  id: cache.identify(myObject),
  fields: {
    name(cachedName) {
      return cachedName.toUpperCase();
    },
  },
});
fields

这个字段为一个映射,指定要为缓存对象的每个修改字段调用的修饰符函数。

1. 修改字段

fields: {
  [key]: (value, secondParams) => {
    // modify the value
    return modifiedValue;
  }
}

如上述伪代码所示,key 为我们要修改的字段名称,其值为针对每个字段需要做的操作的函数。

2. 修饰符函数
其修饰符函数的第一个参数是正在修改的字段的当前缓存值。

第二个参数是一个辅助对象,包含以下实用程序:

字段类型描述
fieldNamestring正在修改的字段的名称。
storeFieldNamestring内部使用的字段的完整键,包括序列化的键参数。。
readFieldfunction用于读取作为第一个参数传递给修饰符函数的对象上的其他字段的辅助函数。
canReadfunction对于非标准化 StoreObject 和非悬空References返回 true 的辅助函数。这表明 readField(name, objOrRef) 有机会工作。对于从列表中过滤悬空引用很有用。
isReferencefunction如果特定对象是对缓存实体的引用,则返回 true 的辅助函数。
DELETEobject您可以从修饰符函数返回一个哨兵对象,以指示应完全删除该字段。
示例

1. 从列表中删除项
假设我们有一个Post应用程序,其中每个帖子都有一组Comments。以下是我们如何从分页的 Post.comments 数组中删除特定Comment

const idToRemove = 'abc123';

cache.modify({
  id: cache.identify(myPost),
  fields: {
    comments(existingCommentRefs, { readField }) {
      return existingCommentRefs.filter(
        commentRef => idToRemove !== readField('id', commentRef)
      );
    },
  },
});
  • id 字段中,我们使用 cache.identify 来获取要从中删除评论的缓存 Post 对象的缓存 ID。
  • fields 字段中,我们提供了一个列出修饰符函数的对象。在本例中,我们定义一个修饰符函数(用于 comments 字段)。
  • comments修饰符函数将我们现有的缓存评论数组作为参数(existingCommentRefs)。它还使用 readField 实用函数,它可以帮助您读取任何缓存字段的值。
  • 修饰符函数返回一个数组,该数组过滤掉 ID 与 idToRemove 匹配的所有注释。返回的数组将替换缓存中现有的数组。

2. 添加项到列表
现在让我们看看向帖子(Post)添加评论(Comment):

const newComment = {
  __typename: 'Comment',
  id: 'abc123',
  text: 'Great blog post!',
};

cache.modify({
  id: cache.identify(myPost),
  fields: {
    comments(existingCommentRefs = [], { readField }) {
      const newCommentRef = cache.writeFragment({
        data: newComment,
        fragment: gql`
          fragment NewComment on Comment {
            id
            text
          }
        `
      });

      // Quick safety check - if the new comment is already
      // present in the cache, we don't need to add it again.
      if (existingCommentRefs.some(
        ref => readField('id', ref) === newComment.id
      )) {
        return existingCommentRefs;
      }

      return [...existingCommentRefs, newCommentRef];
    }
  }
});

当调用 comments 字段修饰符函数时,它首先调用 writeFragment 将我们的 newComment 数据存储在缓存中。 writeFragment 函数返回一个指向新缓存评论的引用 (newCommentRef)。
作为安全检查,我们随后扫描现有评论引用数组 (existingCommentRefs),以确保我们的新评论不在列表中。如果不在列表中,我们将新的评论引用添加到引用列表中,返回要存储在缓存中的完整列表。

3. 从缓存对象中删除字段
修饰符函数的可选第二个参数是一个对象,其中包含几个有用的实用程序,例如 canReadisReference 函数。它还包括一个名为 DELETE 的哨兵对象。
要从特定缓存对象中删除字段,请从字段的修饰符函数返回 DELETE 哨兵对象,如下所示:

cache.modify({
  id: cache.identify(myPost),
  fields: {
    comments(existingCommentRefs, { DELETE }) {
      return DELETE;
    },
  },
});

4. 使缓存对象中的字段无效
通常,更改或删除字段的值也会使该字段无效,从而导致监视的查询(如果它们之前使用了该字段)被重新读取。
使用cache.modify,还可以通过返回INVALIDATE标记来使字段无效而不更改或删除其值:

cache.modify({
  id: cache.identify(myPost),
  fields: {
    comments(existingCommentRefs, { INVALIDATE }) {
      return INVALIDATE;
    },
  },
});

如果需要使给定对象中的所有字段无效,可以传递修饰符函数作为 fields 选项的值:

cache.modify({
  id: cache.identify(myPost),
  fields(fieldValue, details) {
    return details.INVALIDATE;
  },
});

总结

上述是我在使用 Apollo Client 过程中对其缓存策略的深入学习。可以总结为 Apollo 帮忙处理了每个对象,生成唯一标识,并且在请求或修改时,处理缓存数据。
而其提供针对缓存的各种操作方法,可以根据具体场景使用。可以在用户页面操作后,直接修改缓存,更快地响应到页面上。

Apollo 缓存策略文档
Understanding Apollo Fetch Policies
Demystifying Cache Normalization

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1990372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

八股之Java基础

注:本文部分内容源自Java 面试指南 | JavaGuide 一、基础概念与常识 1.JVM、JDK、JRE JVM:Java Virtual Machine,虚拟机,运行Java字节码,实现Java的平台无关性 JDK:Java Development Kit,Ja…

PwnLab: init-文件包含、shell反弹、提权--靶机渗透思路讲解

Vulnhub靶机链接回【PwnLab】 首页有一个登录框 image-20240807124822770 他没有验证码,我们试试暴力破解 image-20240807122743025 开始爆破了,全部失败,哈哈哈 image-20240807122851001 nmap全端口扫描试试 image-20240807131408315 有…

无法访问jakarta.servlet.http.HttpServletRequest

1、问题 无法访问jakarta.servlet.http.HttpServletRequest2、原因 JDK1.8升级到高版本后&#xff0c;需要手动引入jakarta.servlet-api 3、解决 增加依赖 <dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</ar…

6.2.面向对象技术-UML图

UML UML事物关系依赖关联聚合组合 泛化实现 类图对象图用例图序列图通信图状态图活动图构件图部署图练习题 UML 上午选择题3-4分&#xff0c;下午案例分析必考 UML是统一建模语言&#xff0c;和程序设计语言并无关系。 UML三个要素&#xff1a;UML的基本构造块、支配这些构造…

代码随想录算法训练营第二十一天 | 77. 组合, 216.组合总和III , 17.电话号码的字母组合

目录 77. 组合 思路 回溯法三部曲 方法一&#xff1a; 回溯未剪枝 方法二&#xff1a;回溯剪枝 心得收获 216.组合总和III 思路 方法一&#xff1a;回溯-没有使用sum来统计path里元素的总和 方法二&#xff1a;回溯&#xff0c;使用sum来保存当前路径上的总和 心得…

如何应用OceanBase 的实时SQL诊断,解决AP场景下的痛点

随着数据量的快速增长与用户需求的变化&#xff0c;数据库的管理与优化工作日益凸显其重要性。作为DBA及开发者&#xff0c;您是否曾面临以下挑战&#xff1a; ○ 分析场景下&#xff0c;在处理大规模数据的且耗时较长的查询是&#xff0c;常涉及海量数据的处理及复杂的计算&…

Python 设计模式之工厂函数模式

文章目录 案例基本案例逐渐复杂的案例 问题回顾什么是工厂模式&#xff1f;为什么会用到工厂函数模式&#xff1f;工厂函数模式和抽象工厂模式有什么关系&#xff1f; 工厂函数模式是一种创建型设计模式&#xff0c;抛出问题&#xff1a; 什么是工厂函数模式&#xff1f;为什么…

Vue3学习笔记第一天

MVVM Vue是一种用于构建用户界面的JavaScript框架。MVVM 是Vue采用的一种软件架构模式&#xff0c;用于构建交互式的用户界面。它的全称是 Model-View-ViewModel&#xff0c;这三个部分分别代表了应用程序的不同层次和角色&#xff1a; Model&#xff08;模型&#xff09;&…

【ARM】v8架构programmer guide(3)_ARMv8的寄存器

目录 4.ARMv8 registers 4.1 AArch64 特殊寄存器 4.1.1 Zero register 4.1.2 Stack pointer &#xff08;SP) 4.1.3 Program Counter &#xff08;PC) 4.1.4 Exception Link Register(ELR) 4.1.5 Saved Process Status Register &#xff08;SPSR&#xff09; 4.2 Proc…

性能测试基础概念

前言&#x1f440;~ 上一章我们介绍了单元测试Junit的使用&#xff0c;今天我们来讲解一下性能测试的一些基础概念为后面我们进行性能测试做铺垫 什么是性能测试&#xff1f; 性能测试和功能测试有什么区别&#xff1f; 影响一个软件性能因素有哪些&#xff1f; 为什么要进…

循环神经网络和自然语言处理一

目录 一.分词 1.分词工具 2.分词的方法 3.N-gram表示方法 二.向量化 1.one-hot编码 2.word embedding 3.word embedding API 4.数据形状改变 既然是自然语言&#xff0c;那么就有字&#xff0c;词&#xff0c;句了 一.分词 1.分词工具 tokenization&#xff0c;jie…

Outlook Pst文件大小最大多大?如何分开缩减?

簡介 預設情況下&#xff0c;personal Folders (.pst) 和離線 Outlook Data File (.ost) 檔案在 Microsoft Outlook 2010 和 Outlook 2013 中為 Unicode 格式。 .pst 和 .ost 檔案的整體大小有 50 GB 的預先設定限制。 此限制大於 2007 和 Outlook 2003 Outlook Unicode .pst …

零基础5分钟上手亚马逊云科技AWS核心云开发/云架构知识 - 成本分析篇

简介&#xff1a; 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;让大家零基础5分钟通过这篇文章就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我将每天介绍一个基于亚马逊云科…

数据结构 - 相邻节点迭代器

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、相邻节…

nuxt3实战:完整的 nuxt3 + vue3 项目创建与useFetch请求封装

一. 安装 pnpm dlx nuxilatest init <project-name>// ornpx nuxilatest init <project-name>如遇到报错 手动安装&#xff1a; 浏览器访问报错https请求地址&#xff1a; 点击tar(项目初始文件的下载地址)对应地址,下载starter-3.tar.gz 包到本地 本地创建项…

【Android】使用网络技术——WebView的用法、http协议、OKHttp、解析XML、JSON格式数据笔记整理

WebView的用法 新建一个WebView项目 修改activity_main中的代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:id"id/main"and…

STM32F1之SysTick系统定时器详细解析

目录 1. 简介 2. SysTick功能框图 3. SysTick寄存器 3.1 SysTick控制及状态寄存器 3.2 SysTick重装载数值寄存器 3.3 SysTick当前数值寄存器 3.4 SysTick校准数值寄存器 4. SysTick定时时间计算 5. SysTick寄存器结构体 6. 写一个us级延时函数 7. 写一个…

240806-RHEL 无法通过 ssh username@ip 远程连接,报错:Connection closed by ip port 22

A. 原因排查 遇到这个错误通常意味着 SSH 服务可能在目标主机上没有正常运行&#xff0c;或有防火墙/网络配置问题。以下是一些排查步骤&#xff1a; 检查 SSH 服务状态&#xff1a; 确认 SSH 服务是否正在目标主机上运行。 sudo systemctl status sshd重启 SSH 服务&#xff…

探索 Python 异步通信的奥秘:WebSockets 库的神奇之旅

文章目录 探索 Python 异步通信的奥秘&#xff1a;WebSockets 库的神奇之旅背景&#xff1a;为何选择 WebSockets&#xff1f;什么是 websockets 库&#xff1f;安装 websockets 库5个简单的库函数使用方法场景应用示例常见问题与解决方案总结 探索 Python 异步通信的奥秘&…

用Manim实现三维坐标系的绘制

1.ThreeDAxes 函数 ThreeDAxes是 Manim 中用于创建三维坐标系的类。在manim中常用的三位坐标绘制函数是&#xff1a; class ThreeDAxes(x_range(-6, 6, 1), y_range(-5, 5, 1), z_range(-4, 4, 1), x_length10.5, y_length10.5, z_length6.5, z_axis_configNone, z_normala…