前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载导读本文基于 Relay 官方教程v17 版的第一篇查询Query章节完整讲解如何把一个渲染硬编码占位数据的 React 组件改造成通过 GraphQL 查询从服务器获取数据的组件。你将学会使用graphql标签字面量定义查询、使用useLazyLoadQueryHook 触发数据获取以及借助 Relay 编译器自动生成的 TypeScript 类型获得端到端类型安全。文中所有示例均可在仓库配套的教程示例项目relay-examples/newsfeed中动手验证并会结合当前仓库源码packages/react-relay/relay-hooks/useLazyLoadQuery.js、packages/relay-runtime/query/GraphQLTag.js等深入解释底层原理。在 Relay 中数据获取通过 GraphQL Query查询完成。一个 Query 指定了应用要从 GraphQL 图中获取的某一部分它从某个根节点出发沿节点到节点的边edge遍历最终以树形结构取回一组特定数据。从硬编码占位数据开始教程示例应用目前不获取任何数据只是把占位数据硬编码在 React 组件里。打开src/components/Newsfeed.tsx会看到这样的Newsfeed组件export default function Newsfeed() { const story { title: Placeholder Story, summary: Placeholder data, to be replaced with data fetched via GraphQL, poster: { name: Placeholder Person, profilePicture: { url: /assets/cat_avatar.png, }, }, thumbnail: { url: /assets/placeholder.jpeg, }, }; return ( div classNamenewsfeed Story story{story} / /div ); }接下来要做的就是把这份占位数据替换为来自服务器的真实数据。用graphql标签定义查询首先在Newsfeed组件上方添加如下查询声明import { graphql } from relay-runtime; const NewsfeedQuery graphql query NewsfeedQuery { topStory { title summary poster { name profilePicture { url } } thumbnail { url } } } ;逐部分拆解这段代码**graphql 标签**要把 GraphQL 嵌入 JavaScript需把字符串字面量放进graphql 标签中。这个标签让 Relay 编译器能够在 JavaScript 代码库中找到并编译其中的 GraphQL。注意graphql与后面的反引号之间不能有空格否则 Babel 插件无法识别该调用点。查询声明GraphQL 字符串由关键字query加查询名构成。查询名必须以模块名为前缀此处即Newsfeed因为 Relay 编译器会基于模块名生成对应的产物文件如NewsfeedQuery.graphql.ts这也保证了全仓库范围内查询名的唯一性。字段fields查询声明内部是字段用来指定要查询的信息标量字段scalar fields获取字符串、数字等单一信息单元例如title、summary。边edges允许我们从图的一个节点遍历到另一个节点。当字段是边时后面会跟一个{ }块其中包含边另一端节点的字段。这里的poster字段就是从 Story 到发布者 Person 的边遍历到 Person 之后就可以查询name等 Person 的字段。下图直观展示了这个查询请求的是图的哪一部分关于边edge概念的说明这里文档中所说的 edge边泛指 GraphQL 图中节点之间的连接关系对应 tutorial 的 graphql 基础章节中节点/边的概念与分页中 Connection 规范里边对象edges { node { } }是两回事。在 GraphQL 基础章节graphql.md中可以看到Story的thumbnail: Image、poster: Actor这类字段以及interface Actor { id: ID! name: String profilePicture: Image }等类型定义都说明了节点如何通过字段彼此连接。静态查询为什么 GraphQL 必须是字符串字面量Relay 应用中的一切 GraphQL 字符串都会在编译期被 Relay 编译器预处理并从最终打包代码中移除。这意味着你不能在运行时动态拼接 GraphQL 查询——查询必须是编译器在编译期就能确认的静态字符串字面量。这样做带来两大好处Relay 能为查询结果生成类型定义让代码更类型安全。Relay 会把 GraphQL 字符串字面量替换成一个描述该做什么的对象运行时代价比直接用 GraphQL 字符串快得多。从源码可以印证这一点GraphQLTag.js 中的运行时graphql函数实现直接抛出不变量错误function graphql(strings: ReadonlyArraystring): any { invariant( false, graphql: Unexpected invocation at runtime. Either the Babel transform was not set up, or it failed to identify this call site. ..., ); }也就是说graphql在运行时永远不会被真正调用——所有调用点在构建期都已被 Babel 插件如仓库中的 BabelPluginRelay.js转换成了高效的内部数据结构。例如下面这段查询const MyQuery graphql query MyQuery { viewer { name } } ;在转换之后JavaScript 变量MyQuery实际上被赋值给一个类似这样的对象const MyQuery { kind: query, selections: [ { name: viewer, kind: LinkedField, selections: [ name: name, kind: ScalarField, ], } ] };实际产物还包含许多其他属性和信息。这些数据结构经过精心设计能让 JIT 编译器极快地执行 Relay 的响应载荷处理代码。如果想亲眼观察编译器输出可以使用仓库内的 Relay Compiler Explorer 或 compiler-playground 亲自体验。作为真实产物参考仓库测试项目中的 AppQuery.graphql.js 展示了编译器生成的Request节点结构它包含fragment用于从 Store 读取数据、operation用于向服务器发送网络请求与params请求元信息含持久化查询所需的text/id/cacheID等三大部分。这正是GraphQL 字符串在运行时被数据对象取代的直接证据。此外Relay 编译器还可配置为在构建应用时将查询保存到服务器即持久化查询 / persisted queries运行时客户端只需发送查询 ID 而非查询全文从而节省打包体积和网络带宽并防止攻击者提交任意查询——因为只有应用构建时包含的查询才会被服务器接受。编译查询运行 Relay 编译器定义好查询之后需要做两件事运行 Relay 编译器让它感知新的 GraphQL 查询执行npm run relay。修改 React 组件让它真正发起查询并使用服务器返回的数据。打开package.json可以看到relay脚本已与 relay-compiler 挂钩npm run relay执行的正是它。编译器成功生成/更新编译后的查询产物后你会在src/components/__generated__/目录下找到NewsfeedQuery.graphql.ts。由于本项目预计算了 fragmentprecomputed fragments如果不做这一步将得不到预期的结果。在开发模式下npm run devRelay 编译器会处于 watch 状态每当你保存某个 JavaScript 源文件它都会自动重新生成这些文件无需手动刷新。相关实现可参考仓库中的compiler/crates/relay-compiler与compiler/crates/graphql-watchman。用useLazyLoadQuery获取并消费数据回到Newsfeed组件先删除占位数据再替换为如下代码import { useLazyLoadQuery } from react-relay; export default function Newsfeed({}) { const data useLazyLoadQuery( NewsfeedQuery, // 之前定义的 GraphQL 查询 {}, // 传递给服务器的变量 ); const story data.topStory; // 其余渲染逻辑与之前一致 return ( div classNamenewsfeed Story story{story} / /div ); }useLazyLoadQueryHook 负责获取并返回数据它接受两个参数GraphQL 查询即前面定义的NewsfeedQuery。变量Variables随查询一起发送给服务器。本查询没有声明任何变量因此传入空对象{}。useLazyLoadQuery返回的对象与查询具有相同的形状。例如以 JSON 打印出来可能长这样{ topStory: { title: Local Yak Named Yak of the Year, summary: The annual Yak of the Year awards ceremony ..., poster: { name: Baller Bovine Board, profilePicture: { url: /images/baller_bovine_board.jpg, }, }, thumbnail: { url: /images/max_the_yak.jpg, } } }注意查询中选择的每个字段都对应 JSON 响应中的一个属性。此时运行应用你应该能看到一条从服务器获取的故事:::note 服务器的响应被人为地放慢了速度目的是让加载状态可见——这在我们后续为应用增加更多交互时会派上用场。如果想去掉延迟可以打开server/index.js删除对sleep()的调用。 :::useLazyLoadQuery会在组件首次渲染时获取数据。Relay 还提供了在应用加载完成之前就预取数据的 API即预加载查询preloaded queries这部分内容会在后续章节讲解。无论哪种方式Relay 都借助 React Suspense 在数据就绪前展示加载指示器。从源码看useLazyLoadQuery的实现细节查看仓库中的 useLazyLoadQuery.js可以看到这个 Hook 的实际执行流程hook useLazyLoadQueryTVariables extends Variables, TData( gqlQuery: QueryTVariables, TData, variables: NoInferTVariables, options?: Options, ): TData { const environment useRelayEnvironment(); const query useMemoOperationDescriptor( gqlQuery, variables, options options.networkCacheConfig ? options.networkCacheConfig : {force: true}, ); const data useLazyLoadQueryNode $FlowFixMe, LazyLoadQueryNodeParamsWithQuery, ({ componentDisplayName: useLazyLoadQuery(), fetchKey: options?.fetchKey, fetchObservable: fetchQuery(environment, query), fetchPolicy: options?.fetchPolicy, query, renderPolicy: options?.UNSTABLE_renderPolicy, }); return data; }其中useMemoOperationDescriptor根据查询和变量构造操作描述符OperationDescriptor并默认使用{force: true}作为网络层缓存配置即默认绕过网络层的查询响应缓存。fetchQuery(environment, query)生成一个可观察对象Observable交由useLazyLoadQueryNode消费。在 useLazyLoadQueryNode.js 内部数据通过QueryResource.prepareWithIdentifier准备组件挂载期间通过QueryResource.retain保留查询结果防止被垃圾回收最终用useFragmentInternal从 Relay Store 读出与查询形状一致的数据。useLazyLoadQuery还接受一个可选的options对象其中最有价值的是fetchPolicy它决定如何使用缓存中的数据、何时发起网络请求默认值为store-or-networkfetchPolicy行为store-or-network默认复用本地缓存数据仅当查询有数据缺失时才发起网络请求若查询被完整缓存则不发请求store-and-network复用本地缓存数据且无论缓存是否缺失总是发起网络请求network-only不复用本地缓存总是发起网络请求拉取查询store-only只读本地缓存永不发网络请求适用于读取纯本地数据或由调用方负责取数的场景其他可用选项还包括fetchKey强制在重渲染时重新求值当前查询、networkCacheConfig控制网络层缓存行为默认{force: true}以及实验性的UNSTABLE_renderPolicy。深潜Suspense 数据加载Suspense是 React 的一项新 API它让 React 可以在组件需要的数据加载完成之前先等待之后再渲染这些组件。当一个组件渲染前需要加载数据时React 会显示一个加载指示器你可以通过一个名为Suspense的特殊组件来控制加载指示器的位置和样式。目前App.tsx中就有一个Suspense组件useLazyLoadQuery加载数据期间显示的 spinner旋转指示器正是由它负责的。后续章节增加更多交互时会深入讲解 Suspense。Relay 与类型系统让查询类型安全到这里你可能会发现TypeScript 对现有代码报出了如下错误const story data.topStory; ^^^^^^^^ Property topStory does not exist on type unknown要修复它需要为useLazyLoadQuery的调用标注上 Relay 生成的类型这样 TypeScript 就能根据查询中选择的字段推断出data应有的类型。添加如下代码import type {NewsfeedQuery as NewsfeedQueryType} from ./__generated__/NewsfeedQuery.graphql; function Newsfeed({}) { const data useLazyLoadQuery NewsfeedQueryType (NewsfeedQuery, {}); ... }查看__generated__/NewsfeedQuery.graphql文件可以看到如下类型定义——加上刚才的注解后TypeScript 便知道data具有该类型export type NewsfeedQuery$data { readonly topStory: { readonly poster: { readonly name: string | null; readonly profilePicture: { readonly url: string; } | null; }; readonly summary: string | null; readonly thumbnail: { readonly url: string; } | null; readonly title: string; } | null; };Relay 编译器会为应用中graphql 字面量里的每一段 GraphQL 生成对应的 TypeScript 类型。只要npm run dev 在运行编译器就会在你保存 JavaScript 源文件时自动重新生成这些文件无需手动刷新。使用 Relay 生成类型能让应用更安全、更易维护。除了 TypeScriptRelay 也支持 Flow 类型系统使用 Flow 时useLazyLoadQuery上不需要额外标注类型因为 Flow 能直接理解 graphql 标签字面量的内容。:::tip 类型文件中的| null值得特别留意GraphQL 中字段默认可空非空才需要!声明Relay 生成的类型会如实反映可空性因此访问data.topStory.poster?.name这类嵌套可空字段时需要借助可选链或空值判断。 :::小结Query查询是获取 GraphQL 数据的基础。本章我们学会了如何在应用内用 graphql 标签字面量定义 GraphQL 查询如何用useLazyLoadQueryHook 在组件渲染时获取查询结果如何导入 Relay 生成的类型以获得类型安全。下一节将介绍 Fragments片段——这是 Relay 最核心、最具特色的概念之一。Fragment 让每个组件各自声明自己的数据需求同时保留向服务器只发一条查询的性能优势。延伸阅读想先回顾 GraphQL 与 Relay 的整体关系可阅读教程前序章节 GraphQL 与 Relay 导览。本节示例应用的启动与目录结构说明见 教程导论。掌握基础查询后下一篇 用于交互的查询Queries for Interactions 将讲解查询变量query variables与预加载查询preloaded queries——其中明确提到虽然为了讲解简单先引入了useLazyLoadQuery但在真实项目中预加载查询始终是更推荐的使用方式因为它能显著改善实际性能。在仓库源码层面可进一步阅读 useLazyLoadQuery.js、useLazyLoadQueryNode.js 与 GraphQLTag.js深入理解 Hook 与graphql标签的运行时契约编译器生成的查询产物结构可参考 AppQuery.graphql.js。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 查询基础Query Basics用 graphql 标签与 useLazyLoadQuery 为 React 组件接入 GraphQL 数据Relay 查询基础Query Basics用 graphql 标签与 useLazyLoadQuery 为 React 组件接入 GraphQL 数据前端开发工具Relay 查询基础使用 graphql 标签与 useLazyLoadQuery 实现类型安全的 GraphQL 数据获取Relay 查询基础使用 graphql 标签与 useLazyLoadQuery 实现类型安全的 GraphQL 数据获取 本教程围绕 Relay 的查询前端开发工具如何从源码构建 witr 并用 Go 交叉编译验证 Linux 与 macOS 的 amd64、arm64 二进制如何从源码构建 witr 并用 Go 交叉编译验证 Linux 与 macOS 的 amd64、arm64 二进制 如果你克隆了 witr 的源码想在本地确前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考