ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GraphQL 客户端实战指南:Apollo Client 与 Relay 的缓存、校验与数据协同定位

GraphQL 客户端实战指南:Apollo Client 与 Relay 的缓存、校验与数据协同定位 【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载在 GraphQL 全栈架构中客户端承担着将声明式查询转化为网络请求、管理本地缓存、驱动 UI 更新等关键职责。本文围绕 howtographql 仓库中的 GraphQL Clients 概述 展开系统讲解 GraphQL 客户端需要解决的五大基础设施问题——直接发送查询/变更、视图层集成、缓存策略、构建时 Schema 校验、视图与数据依赖的协同定位并结合仓库中 React/React Native、Angular、Vue 等多个前端教程的实际代码对比 Apollo Client、Relay、urql 三大主流实现。读完本文你将理解 GraphQL 客户端的设计动机与核心机制并能在自己的框架中正确配置和使用它们。一、为什么前端需要一个 GraphQL 客户端使用 GraphQL API 的前端开发天然存在一套需要反复实现的基础设施能力。原文档将其归纳为四点直接发送查询与变更无需手工构造 HTTP 请求视图层集成让数据能够自然流入组件缓存避免重复请求提供流畅的用户体验基于 Schema 的校验与查询优化在构建期发现错误、优化查询。诚然使用原生fetch或NSURLSession直接调用 GraphQL 端点也是可行的——把 JSON 请求体拼好、把响应里的字段逐层解包塞进 UI。但 GraphQL 的价值恰恰在于将这一层手工搬运抽象掉你只需要用声明式语言写出数据需求剩下的请求发送、响应处理、缓存与更新都由客户端接管。这正是 0-clients.md 全篇的核心主张。当前生态中两个最主要的 GraphQL 客户端是Apollo Client社区驱动的通用客户端覆盖 Web、iOS、Android 等几乎所有主流平台RelayFacebook 自研客户端深度优化性能主要面向 Web 端。下文将逐项展开这五大能力并在每一节给出仓库内前端教程的可运行示例作为印证。二、直接发送查询与变更从手工 HTTP 到声明式取数使用 REST 时你需要针对每个端点手工构造请求、处理状态码、解析响应体。而 GraphQL 客户端把发送请求并处理响应封装成了统一机制你只需提供一个查询文档客户端负责将其 POST 到 GraphQL 端点并解析结果。以仓库中的 React Apollo 教程 2-queries-loading-links.md 为例定义一个FEED_QUERY查询文档{ feed { id links { id createdAt description url } } }在组件中使用useQueryhook 发送import { useQuery, gql } from apollo/client; const FEED_QUERY gql { feed { id links { id createdAt url description } } } ; const LinkList () { const { data } useQuery(FEED_QUERY); return ( div {data ( {data.feed.links.map((link) ( Link key{link.id} link{link} / ))} / )} /div ); };gql使用 JavaScript 的 tagged template literals 将 GraphQL 字符串解析为可执行的查询文档useQuery接收该文档自动完成网络请求并返回loading、error、data三个状态量——loading在请求进行中为trueerror携带失败信息data为服务端返回的数据。这也是声明式取数最直接的体现组件不关心请求如何发出只声明自己要什么。在 Angular 教程 2-queries-loading-links.md 中同样的能力通过Apollo服务与 RxJS Observable 呈现。查询被集中定义在src/app/graphql.tsimport gql from graphql-tag; export const ALL_LINKS_QUERY gql query AllLinksQuery { allLinks { id createdAt url description } } ;组件中注入服务并订阅结果import { Apollo } from apollo-angular; constructor(private apollo: Apollo) {} ngOnInit() { this.apollo.watchQueryAllLinkQueryResponse({ query: ALL_LINKS_QUERY }).valueChanges.subscribe((response) { this.allLinks response.data.allLinks; this.loading response.data.loading; }); }watchQuery返回一个可订阅的 Observable除了一次性query方法之外它能持续跟踪查询结果的变化——这为后面的缓存与 UI 更新机制埋下了伏笔。三、视图层集成与 UI 更新数据如何流入组件服务端响应抵达客户端之后数据必须进入 UI。不同框架有不同的集成方式但共同点是GraphQL 客户端都提供与框架深度绑定的绑定层。以 React 为例原文档指出客户端历史上使用**高阶组件HOC**在幕后取数并把数据注入组件props。现代 React 则演进出了更直接的hooks方案。在 2-queries-loading-links.md 中useQuery就是这一演进的体现——无需包装组件直接在函数组件内声明数据依赖。值得一提的是GraphQL 的声明式特性与**函数响应式编程FRP**天然契合视图只声明数据依赖FRP 层负责把数据流与 UI 状态接通。React 的 hooks Observable 组合、Angular 的 RxJS Observable上文watchQuery().valueChanges即是一条数据流、Vue 的响应式data都是这种结合的实例。在 Vue 教程 2-queries-loading-links.md 中视图层集成通过组件的apollo对象完成import { ALL_LINKS_QUERY } from ../constants/graphql; export default { name: LinkList, data () { return { allLinks: [], loading: 0 } }, components: { LinkItem }, apollo: { allLinks: { query: ALL_LINKS_QUERY } } }模板中配合v-ifloading显示加载态v-for遍历allLinks渲染列表。客户端取到数据后自动更新响应式dataVue 的响应式系统随即驱动视图刷新——整个过程对组件作者完全透明。四、缓存查询结果为何必须归一化Normalization大多数应用都希望缓存已获取的数据以提供流畅体验并节省流量。直觉做法是把查询结果整体塞进本地 store下次遇到相同查询直接返回。但原文档明确指出这种整体缓存方案对大多数应用极其低效。原因在于 GraphQL 查询往往是嵌套结构且不同查询会以不同形状覆盖同一批对象。比如feed查询返回的Link对象可能在vote相关的另一查询中以不同的字段组合再次出现。若按查询整体缓存同一个Link会被复制多份任何一处更新都难以同步到其他副本缓存一致性无从谈起。更优的做法是归一化normalize将可能嵌套的查询结果拍平store 中只存放可被全局唯一 ID引用的单条记录。这样每条Link只存一份多个查询共享同一份记录更新一处即可全局生效。Apollo Client 的实现即为此设计——1-getting-started.md 中创建客户端实例时传入的InMemoryCache就是归一化缓存的核心import { ApolloProvider, ApolloClient, createHttpLink, InMemoryCache } from apollo/client; const httpLink createHttpLink({ uri: http://localhost:4000 }); const client new ApolloClient({ link: httpLink, cache: new InMemoryCache() });Angular 教程 1-getting-started.md 还提到一个关键配置new InMemoryCache({ dataIdFromObject: o o.id })即指定 Apollo 如何识别并去重服务端返回的对象——这正是归一化缓存中全局唯一 ID的落地方式告诉缓存每个对象的 ID 从哪个字段取缓存即可据此建立记录索引。Relay 同样采用归一化模型。在 1-getting-started.md 中Relay Environment 由两大部分构成负责网络通信的Network和负责缓存的Store基于RecordSource实现const { Environment, Network, RecordSource, Store } require(relay-runtime); const store new Store(new RecordSource()); const network Network.create((operation, variables) { return fetch(__RELAY_API_ENDPOINT__, { method: POST, headers: { Accept: application/json, Content-Type: application/json }, body: JSON.stringify({ query: operation.text, variables, }), }).then(response response.json()); }); const environment new Environment({ network, store });RecordSource存放的就是归一化后的记录集合每条记录以全局唯一 ID 为键。这也解释了为什么 Relay 教程要求 fragment 中必须包含id字段——2-queries-loading-links.md 中明确指出id是 Relay 在缓存中唯一标识、存储与检索 link 项所必需的。五、构建时 Schema 校验与优化把错误挡在发布之前GraphQL Schema 包含了客户端对该 API 能做的一切操作信息因此存在一个巨大机会在构建期build-time校验乃至优化客户端将要发送的查询。原理很简单当构建环境能访问 Schema 时它可以解析项目中所有 GraphQL 代码与 Schema 逐项比对。拼写错误的字段名、不存在的类型、缺失的必填参数都会在应用到达真实用户之前被捕获——否则同样的错误要等到运行时才暴露代价要大得多。这并非纸上谈兵。Relay 正是以编译期严谨著称的客户端。在 1-getting-started.md 中作者明确描述了relay-compiler的职责Relay Compiler 是一个你在构建期用来校验和优化项目中 GraphQL 代码的工具。项目需要三件套react-relayRelay 运行时负责网络与缓存relay-compiler构建期校验与优化 GraphQL 代码babel-plugin-relayBabel 插件把项目中的 GraphQL 代码转换为 Relay Compiler 需要的格式。实际运行方式2-queries-loading-links.mdrelay-compiler --src ./src --schema ./schema.graphql--src指向所有包含graphql代码的文件目录--schema指向完整的 GraphQL Schema 文件。编译器扫描src中的全部 GraphQL 代码、对照 Schema 校验并生成对应的 JavaScript 表示存入./src/__generated__Created: - Link_link.flow.js - Link_link.graphql.js - LinkList_viewer.flow.js - LinkList_viewer.graphql.js - LinkListPageQuery.graphql.js如果跳过编译直接运行应用会立刻看到类似Module not found: Cant resolve ./__generated__/LinkListPageQuery.graphql的编译错误——这正是构建期校验在工程实践中的直观体现未编译的 GraphQL 代码根本无法进入运行时。六、协同定位Colocation让视图与数据依赖并肩而行GraphQL 的一个强大理念是UI 代码与其数据需求可以放在一起。视图与数据依赖的紧密耦合极大改善了开发者体验——你不再需要在头脑中维护某块 UI 的数据来自哪里的映射关系。协同定位的效果取决于平台。原文档指出在 JavaScript 应用中数据依赖与 UI 代码可以写进同一个文件而在 Xcode 中可以用 Assistant Editor 同时编辑 view controller 与 GraphQL 代码。这一理念在 Relay 中被贯彻得最为彻底甚至成为其核心标识。在 2-queries-loading-links.md 中作者如此定义colocation 意味着 React 组件在其定义处同一文件内声明数据依赖形式是 GraphQL Fragment。实现方式是createFragmentContainer高阶组件——接收两个参数一个 React 组件以及用graphql函数包装的数据依赖Fragmentimport { createFragmentContainer, graphql } from react-relay; export default createFragmentContainer(Link, graphql fragment Link_link on Link { id description url } );Fragment 命名有约定文件名_prop名。Link.js文件、注入linkprop因此 Fragment 命名为Link_link。子组件与父组件之间的 Fragment 通过展开运算符复用。LinkList需要一组链接它引用子组件的 Fragment 并声明连接查询export default createFragmentContainer(LinkList, graphql fragment LinkList_viewer on Viewer { allLinks(last: 100, orderBy: createdAt_DESC) connection(key: LinkList_allLinks, filters: []) { edges { node { ...Link_link } } } } );这里的connection指令是 Relay 对列表的抽象Connection服务于后续的游标分页与缓存更新——key参数用于在缓存中标识这条连接。那么组件都只写 Fragment 而不写完整查询真正的查询是谁拼出来的答案是QueryRenderer——Relay 组件树的根。它接收三样东西一个 Relayenvironment、一个根query、以及一个处理 loading/error/success 三种状态的render函数const LinkListPageQuery graphql query LinkListPageQuery { viewer { ...LinkList_viewer } } ; QueryRenderer environment{environment} query{LinkListPageQuery} render{({error, props}) { if (error) { return div{error.message}/div } else if (props) { return LinkList viewer{props.viewer} / } return divLoading/div }} /各组件通过 Fragment 声明局部数据需求QueryRenderer在树根处把这些 Fragment 组合成实际发送给服务器的完整查询——你可以通过浏览器 DevTools 的 Network 面板查看它最终拼出的查询。这与 Apollo 形成鲜明对比Apollo 同样支持协同定位但常规做法是直接写查询而非Fragment。七、客户端生态对照Apollo、Relay 与 urql 的设计取向通过上文可以总结出三大客户端在核心机制上的取向差异维度Apollo ClientRelayurql数据依赖形式完整查询文档支持 FragmentFragment 编译器组合完整查询文档缓存InMemoryCache归一化缓存StoreRecordSource归一化缓存归一化文档缓存校验时机运行时 可选工具链构建期relay-compiler强校验运行时视图绑定HOC / hooksuseQuery/ 服务注入FragmentContainerQueryRendererhooksuseQuery/ render props平台覆盖Web、iOS、Android 等多平台主要面向 Web以 React 为主urql 的查询流程在 2-queries-loading-links.md 中有清晰记录低层 API 是executeQuery、executeMutation、executeSubscription返回基于 Wonka 库的结果流而 React 场景下推荐使用useQueryhook返回[result]数组——第一个元素是包含fetching、errorCombinedError区分networkError与graphQLErrors、data的结果对象第二个是用于重新取数的execute函数const [result] useQuery({ query: FEED_QUERY }) const { data, fetching, error } result if (fetching) return divFetching/div if (error) return divError/div const linksToRender data.feed.links无论选择哪家客户端其背后共享的底层模型是一致的声明式查询 → 客户端代为发送请求 → 归一化缓存 → 响应式驱动 UI 更新。这正是 0-clients.md 所描绘的图景也是后续 更多 GraphQL 概念、工具链与生态 等进阶章节的基础。八、小结GraphQL 客户端存在的根本理由是把数据如何到达 UI这一横切关注点从业务代码中剥离声明式查询取代手工 HTTP、框架绑定层接管视图更新、归一化缓存解决数据一致性与重复请求、构建期校验把错误挡在发布之前、协同定位让视图与其数据需求同处一室。理解这五大机制你就能在选型时做出有依据的判断——追求强类型编译保障与性能优化选 Relay追求灵活性与多平台覆盖选 Apollo Client追求轻量与 React 生态契合选 urql并在自己的项目中正确地配置客户端、编写查询与设计缓存策略。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐regnety_064.ra3_in1k性能深度测评ImageNet-1k数据集83.7%准确率背后的秘密regnety_064.ra3_in1k性能深度测评ImageNet 1k数据集83.7%准确率背后的秘密 regnety_064.ra3_in1k是一个基于人工智能计算机视觉深度学习TW-Elements与GraphQL客户端Apollo Client集成实战TW Elements与GraphQL客户端Apollo Client集成实战 你是否在前端开发中遇到过UI组件与数据获取逻辑难以协同的问题是否想让TailUI组件前端Snabbdom与GraphQL客户端Apollo与Relay集成实践Snabbdom与GraphQL客户端Apollo与Relay集成实践 你还在为前端状态管理与虚拟DOM渲染的性能问题烦恼吗当应用数据复杂度提升时传统的状前端上一篇Firo未来路线图隐私币的下一个里程碑与技术革新下一篇Radium复杂动画多元素协同动画实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表