用 Relay 思考
Relay 的数据获取方法深受我们使用 React 的经验启发。特别是,React 将复杂的界面分解成可重用的**组件**,使开发人员能够独立地分析应用程序的离散单元,并减少应用程序不同部分之间的耦合。更重要的是,这些组件是**声明式**的:它们允许开发人员指定给定状态下 UI 应该是什么样子,而不必担心如何显示该 UI。与以前使用命令式命令操作原生视图(例如 DOM)的方法不同,React 使用 UI 描述来自动确定必要的命令。
让我们看看一些产品用例,以了解我们如何将这些想法融入 Relay。我们假设您对 React 有基本的了解。
为视图获取数据
根据我们的经验,绝大多数产品都想要一种特定行为:获取视图层次结构的所有数据,同时显示加载指示器,然后在数据可用后渲染整个视图。
一种解决方案是让根组件声明并获取它和所有子组件所需的数据。但是,这将引入耦合:对子组件的任何更改都需要更改可能渲染它的任何根组件!这种耦合可能意味着更大的错误可能性,并减慢开发速度。
另一种合乎逻辑的方法是让每个组件声明并获取它所需的数据。听起来很棒。但是,问题是组件可能会根据收到的数据渲染不同的子组件。因此,嵌套组件将无法渲染并开始获取它们的数据,直到父组件的查询完成。换句话说,这迫使数据获取分阶段进行:首先渲染根并获取它需要的数据,然后渲染它的子组件并获取它们的数据,依此类推,直到到达叶子组件。渲染将需要多次缓慢的串行往返。
Relay 通过允许组件指定它们需要什么数据,但将这些需求合并成一个查询来获取整个组件子树的数据,从而结合了这两种方法的优点。换句话说,它在静态(即您的应用程序运行之前;在您编写代码时)确定整个视图的需求!
这是在 GraphQL 的帮助下实现的。函数式组件使用一个或多个 GraphQL 片段 来描述它们的数据需求。这些片段然后嵌套在其他片段中,最终嵌套在查询中。当这样的查询被获取时,Relay 将为此查询及其所有嵌套片段发出一个网络请求。换句话说,Relay 运行时随后能够为视图所需的所有数据发出一个网络请求!
让我们深入了解 Relay 如何实现这一壮举。
指定组件的数据需求
在 Relay 中,组件的数据需求是使用 片段 指定的。片段是 GraphQL 的命名代码段,指定要从特定类型的对象中选择哪些字段。片段写在 GraphQL 字面量中。例如,以下代码声明一个 GraphQL 字面量,其中包含一个片段,该片段选择作者的姓名和照片 URL
// AuthorDetails.react.js
const authorDetailsFragment = graphql`
fragment AuthorDetails_author on Author {
name
photo {
url
}
}
`;
然后,通过在函数式 React 组件中调用 useFragment(...)
钩子,从存储中读取此数据。要从中读取此数据的实际作者由传递给 useFragment
的第二个参数确定。例如
// AuthorDetails.react.js
export default function AuthorDetails(props) {
const data = useFragment(authorDetailsFragment, props.author);
// ...
}
第二个参数(props.author
)是片段引用。片段引用是通过将片段传播到另一个片段或查询中获得的。片段不能直接获取。相反,所有片段最终都必须被传播(直接或间接)到一个查询中,才能获取数据。
让我们看一下这样的一个查询。
查询
为了获取该数据,我们可以声明一个查询,该查询将 AuthorDetails_author
传播如下
// Story.react.js
const storyQuery = graphql`
query StoryQuery($storyID: ID!) {
story(id: $storyID) {
title
author {
...AuthorDetails_author
}
}
}
`;
现在,我们可以通过调用 const data = useLazyLoadQuery(storyQuery, {storyID})
来获取查询。此时,data.story.author
(如果存在;默认情况下所有字段都是可空字段)将是一个片段引用,我们可以将其传递给 AuthorDetails
。例如
// Story.react.js
function Story(props) {
const data = useLazyLoadQuery(storyQuery, props.storyId);
return (<>
<Heading>{data?.story.title}</Heading>
{data?.story?.author && <AuthorDetails author={data.story.author} />}
</>);
}
请注意这里发生了什么。我们发出一个包含Story 组件和AuthorDetails
组件所需数据的单个网络请求!当该数据可用时,整个视图可以在一次传递中渲染。
数据屏蔽
使用典型的获取数据方法,我们发现两个组件通常具有隐式依赖关系。例如,<Story />
可能会使用一些数据,而无需直接确保数据被获取。这些数据通常由系统中的其他部分获取,例如 <AuthorDetails />
。然后,当我们更改 <AuthorDetails />
并删除该数据获取逻辑时,<Story />
就会突然莫名其妙地崩溃。这些类型的错误并不总是立即显而易见,特别是在由大型团队开发的大型应用程序中。手动和自动测试只能帮助我们做到这一点:这正是框架可以更好地解决的系统性问题类型。
我们已经看到,Relay 确保为视图获取所有数据。但是 Relay 还提供了另一个好处,这个好处并不那么明显:数据屏蔽。Relay 只允许组件访问他们在 GraphQL 片段中明确请求的数据,而不会访问更多数据。因此,如果一个组件查询一个 Story 的 title
,另一个查询其 text
,则每个组件只能看到他们请求的字段。事实上,组件甚至无法看到它们子组件请求的数据:这也会破坏封装性。
Relay 还更进一步:它在 props
上使用不透明标识符来验证我们在渲染组件之前是否显式获取了该组件的数据。如果 <Story />
渲染 <AuthorDetails />
但忘记传播其片段,Relay 将警告说 <AuthorDetails />
的数据丢失了。事实上,即使其他组件恰好获取了 <AuthorDetails />
所需的相同数据,Relay 也会发出警告。此警告告诉我们,虽然现在可能一切正常,但很可能在以后会崩溃。
结论
GraphQL 为构建高效、解耦的客户端应用程序提供了一个强大的工具。Relay 在此功能基础上提供了一个框架来实现声明式数据获取。通过将要获取的数据与如何获取数据分离,Relay 帮助开发人员构建默认情况下健壮、透明和高效的应用程序。它是 React 所倡导的以组件为中心的思维方式的绝佳补充。虽然这些技术中的每一种——React、Relay 和 GraphQL——本身都很强大,但它们的组合是一个UI 平台,使我们能够快速行动并大规模地发布高质量的应用程序。
此页面是否有用?
通过以下方式帮助我们改进网站: 回答几个快速问题.