资讯动态

Relay 查询变量实战指南:从 GraphQL 变量到 @arguments 与 @argumentDefinitions

发布时间:2026/9/23 15:47:53 来源:尧图企业网站定制
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本篇指南以 RelayMeta 开源的 JavaScript 数据驱动 React 应用框架官方文档中关于查询变量Query Variables的讲解为骨架系统梳理 GraphQL 变量在 Relay 中的三种用法查询级全局变量、fragment 对全局变量的引用以及通过arguments/argumentDefinitions声明的 fragment 局部变量。文中结合本仓库编译器与运行时源码深入解释变量类型推导、fragment 参数内联静态柯里化等底层机制帮助读者在组件化开发中正确设计可复用、可定制、可被编译器静态校验的 Relay 数据依赖。从 GraphQL 变量说起查询中的动态输入在前面的示例中你可能已经注意到GraphQL 查询声明里出现了$id这样的符号——这正是 GraphQL VariablesGraphQL 变量的语法。GraphQL 变量是 GraphQL 提供的一种构造允许在查询内部引用动态值。以UserQuery为例query UserQuery($id: ID!) { # $id 的值被用作 user() 调用的输入 user(id: $id) { id name } }这里ID!是$id变量的类型表示它是一个必填non-null的 ID。当向服务器发送网络请求以获取上述查询时需要同时提供两样东西查询本身本次执行该查询要使用的变量集合。例如# 查询 query UserQuery($id: ID!) { # ... } # 变量 {id: 4}从服务器获取上述查询与变量会产生如下响应{ data: { user: { id: 4, name: Mark Zuckerberg } } }可以看到服务器解析$id变量、执行user(id: 4)并在返回结果前将其应用于查询的各个位置。这正是 GraphQL 变量区别于把值硬编码进查询字符串的价值同一份查询模板可以通过不同的变量值反复复用同时请求内容保持可缓存、可校验。Fragment 引用查询级全局变量变量不只可以在查询的顶层参数列表中使用。Fragment 同样可以引用由查询声明的变量fragment UserFragment on User { name profile_picture(scale: $scale) { uri } } query ViewerQuery($scale: Float!) { viewer { actor { ...UserFragment } } }关于这种跨 fragment 的变量引用官方文档给出了三条关键规则尽管UserFragment并没有声明$scale变量它仍然可以直接引用它。任何直接或间接包含该 fragment 的查询都必须声明该变量及其类型否则会产生错误。换句话说查询变量对该查询的所有后代 fragment 全局可见。一个引用了全局变量的 fragment只能被直接或间接定义了该全局变量的查询所包含。这意味着 fragment 一旦使用了全局变量它就与某个声明了该变量的查询形成了强耦合——fragment 本身无法独立决定该变量的存在性只能依赖包含它的查询来完成声明。Relay 组件中的 fragment 变量引用与编译期检查在 Relay 中组件内部声明的 fragment 同样可以引用查询变量function UserComponent(props: Props) { const data useFragment( graphql fragment UserComponent_user on User { name profile_picture(scale: $scale) { uri } } , props.user, ); return (...); }这里有两个要点需要理解上述 fragment 可能被多个查询包含、被不同组件渲染这意味着任何最终渲染/包含该 fragment 的查询都必须声明$scale变量。如果某个恰好包含该 fragment 的查询没有声明$scale变量Relay 编译器会在**构建期build time**直接报错从而保证一个不合法缺少变量声明的查询永远不会被发送到服务器——服务器收到这类查询同样会报错但把检查前移到编译期显然更安全、更早暴露问题。编译器如何推断查询必须声明的变量查询必须声明其包含的 fragment 所引用的所有变量这一规则在编译器中有专门的实现。从源码看root_variables.rs 中的InferVariablesVisitor/VariablesVisitor会遍历整个 program遍历每个 operation收集其**传递引用transitively**的所有根变量即该 fragment 自身及其展开的所有 fragment 用到的根变量的并集每个变量记录的是使用它的最具体类型is_type_strict_subtype_of严格子类型判断以保证查询声明出的类型对所有使用位置都合法当同一变量以不兼容类型被多处使用时会抛出IncompatibleVariableUsage诊断错误。同时fragment 的遍历结果会被缓存visited_fragments一旦某个 fragment 被计算过其他查询直接复用避免重复处理。这正是任何包含该 fragment 的查询都必须声明这些变量这一规则在实现层面的保证。arguments 与 argumentDefinitionsfragment 局部变量全局查询变量的耦合问题引出了 Relay 的解决方案Relay 提供了使用arguments和argumentDefinitions指令来声明作用域限定在 fragment 内部的局部变量。使用局部变量的 fragment 易于定制和复用因为它们不依赖全局查询级变量的值。声明带参数的 fragmentargumentDefinitions/** * 用 argumentDefinitions 声明一个接受参数的 fragment */ function TaskView(props) { const data useFragment( graphql fragment TaskView_task on Task argumentDefinitions(showDetailedResults: {type: Boolean!}) { name is_completed ... include(if: $showDetailedResults) { description } } , props.task, ); }argumentDefinitions为 fragment 声明了局部变量showDetailedResults其类型为Boolean!。fragment 内部可以像使用普通变量一样在字段参数、指令条件中使用它——上例中include(if: $showDetailedResults)会根据该局部变量的值决定description字段是否被包含。传入参数arguments/** * 用 arguments 包含 fragment */ function TaskList(props) { const data usePreloadedQuery( graphql query TaskListQuery { todays_tasks { ...TaskView_task arguments(showDetailedResults: true) } tomorrows_tasks { ...TaskView_task arguments(showDetailedResults: false) } } , props.queryRef, ); }同一个TaskView_taskfragment 被展开两次分别传入true与false实现了同一模板、不同渲染的定制化复用。局部变量带来的收益是结构性的查询定义必须列出所有被嵌套 fragment包括递归嵌套的 fragment使用的变量。这是全局变量方案的固有成本。由于一个 fragment 可能被很多查询访问修改一个使用全局变量的 fragment往往需要同步修改大量查询定义。这还可能催生尴尬的同义变量混乱例如同时存在$showDetailedResults和$showDetails两种写法。而只使用局部变量的 fragment 不涉及全局变量天然绕开上述所有问题。arguments 可以传什么向 fragment 传递arguments时可以传入字面量例如42.0另一个变量它可以是查询级全局变量由argumentDefinitions声明的局部变量或直接的字面量值。当TaskView_task真正作为查询的一部分被获取时showDetailedResults的值将取决于其父级为TaskView_task提供的参数。局部变量同样遵循传递可见规则需要特别注意的是arguments传入的值可以是外层 fragment 的局部变量而 fragment 内部依然遵守变量对后代可见的规则——一个 fragment spread 只要直接或间接引用了某个局部变量包含它的父级就必须通过arguments提供该值。这与全局变量的必须声明约束在精神上完全一致只是作用域从查询级收窄到了 fragment 级。默认值让参数变为可选期望接受参数的 fragment 还可以声明默认值使参数变为可选/** * 声明一个带默认值参数的 fragment */ function TaskView(props) { const data useFragment( graphql fragment TaskView_task on Task argumentDefinitions(showDetailedResults: {type: Boolean!, defaultValue: true}) { name is_completed ... include(if: $showDetailedResults) { description } } , props.task, ); }function TaskList(props) { const data usePreloadedQuery( graphql query TaskListQuery { todays_tasks { ...TaskView_task } tomorrows_tasks { ...TaskView_task arguments(showDetailedResults: false) } } , props.queryRef, ); }这里showDetailedResults声明了defaultValue: truetodays_tasks的展开没有传arguments则$showDetailedResults使用默认值truedescription会被包含tomorrows_tasks显式传入了falsedescription会被排除。不传参数即使用 fragment 为局部声明的$showDetailedResults的默认值。这为大多数场景一个默认行为少数场景定制覆盖的组件设计提供了非常自然的表达方式。从编译器测试看参数内联的完整流程Relay 编译器的ApplyFragmentArgumentsTransform变换见 apply_fragment_arguments.rs是这套机制的核心它将一组包含带参数的 fragment 及 fragment spread的文档转换为所有参数都已内联的等价文档文档头部注释将其精辟概括为对函数进行静态柯里化static currying。其主要行为包括带参数的 fragment spread 被替换为引用一份已内联版本的 fragment带argumentDefinitions的 fragment 会针对每组唯一参数克隆一次名称变为原名 hash所有嵌套的变量引用被替换为参数对应的值字段与指令参数中的变量被替换为其上下文中的值字面量include/skip条件会被静态求值条件恒真时消除条件节点并把选择集内联到父级恒假时直接删除节点。仓库中的测试 fixture inlines-fragment-arguments.graphql 与其 expected 输出 直观展示了这一过程输入中同一份Profilefragment 以不同arguments被展开输出中被克隆为Profile_4FmGHP与Profile_4CNNX6两个不同片段query TestQuery( $id: ID! $pictureSize: [Int] [128] $includeFriends: Boolean true ) { node(id: $id) { id ...Profile arguments(pictureSize: $pictureSize, includeFriends: $includeFriends) } } fragment Profile on User argumentDefinitions( pictureSize: {type: [Int]} includeFriends: {type: Boolean!, defaultValue: false} ) { # ... }变换后的输出节选query TestQuery(...) { node(id: $id) { id ...Profile_4FmGHP } } fragment Profile_4CNNX6 on User { id name profilePicture(size: $pictureSize) { uri } } fragment Profile_4FmGHP on User { id name profilePicture(size: $pictureSize) { uri } friends(first: 10) include(if: $includeFriends) { edges { node { ...Profile_4CNNX6 } } } }注意includeFriends的默认值false是逐克隆实例生效的Profile_4FmGHP保持include(if: $includeFriends)的运行时条件而递归引用的Profile_4CNNX6则对应另一组参数。这也解释了每个唯一参数组合生成唯一 fragment的命名策略——同名 fragment 因参数不同而拥有不同语义必须用 hash 后缀区分。在运行时访问查询变量如果你希望在运行时访问**查询根query root**处设置的变量官方推荐的做法是在组件树中通过 props或你应用自有的 context把变量逐层向下传递。需要明确的是Relay 目前不会向某个特定 fragment 暴露其解析后即应用了 argument definitions 之后的变量值而且你极少会真的需要这样做。从运行时源码看RelayConcreteVariables.js 提供了getOperationVariables将查询级变量与variables合并得到实际请求变量与getLocalVariables为 fragment 生成局部变量对象等工具函数它们是 Relay 内部在读取数据时完成变量解析的入口而非面向业务组件暴露的 API。业务侧若需要当前查询用了哪些变量最稳妥、最符合 Relay 数据流的方式仍然是把它们作为 props 显式传入相关组件。小结三种变量用法的取舍用法声明位置作用域典型场景代价查询变量查询参数列表查询全局所有后代 fragment 可见查询根级输入如$id包含 fragment 的查询都必须声明其用到的所有变量fragment 引用全局变量fragment 内部直接使用$var依赖外层查询声明fragment 共享查询级输入修改 fragment 可能牵连多个查询定义argumentDefinitionsargumentsfragment 内声明、spread 处传参fragment 局部可带默认值组件级可定制、可复用片段需在 spread 处显式传参除非有默认值设计建议fragment 应优先考虑把真正属于该数据视图自身的可变输入声明为局部参数并给出合理默认值只有当某个值确实来源于查询根、且被整棵子树共享时才使用全局查询变量。这样既能最大化 fragment 的复用性也能让 Relay 编译器在构建期替你兜住变量声明缺失这类低级错误。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 查询变量完全指南从全局变量到 arguments 与 argumentDefinitionsRelay 查询变量完全指南从全局变量到 arguments 与 argumentDefinitions 本指南以 Relay 官方文档 version前端开发工具Relay 查询变量完全指南GraphQL Variables、argumentDefinitions 与 arguments 的实战解析Relay 查询变量完全指南GraphQL Variables、argumentDefinitions 与 arguments 的实战解析 本篇技术指南围前端开发工具从0到1部署deberta-v3-base-injection完整代码示例与环境配置教程从0到1部署deberta v3 base injection完整代码示例与环境配置教程 deberta v3 base injection是一个基于micr前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价