跳到主要内容
版本: v18.0.0

片段

Relay 中声明 React 组件数据依赖关系的主要构建块是 GraphQL 片段。片段是 GraphQL 中可复用的单元,表示从 模式 中公开的 GraphQL 类型查询的一组数据。

实际上,它们是 GraphQL 类型上的字段选择。

fragment UserFragment on User {
name
age
profile_picture(scale: 2) {
uri
}
}

为了在 JavaScript 代码中声明片段,必须使用 graphql 标签

const {graphql} = require('react-relay');

const userFragment = graphql`
fragment UserFragment_user on User {
name
age
profile_picture(scale: 2) {
uri
}
}
`;

渲染片段

为了渲染片段的数据,可以使用 useFragment Hook

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');

const {graphql, useFragment} = require('react-relay');

type Props = {
user: UserComponent_user$key,
};

function UserComponent(props: Props) {
const data = useFragment(
graphql`
fragment UserComponent_user on User {
name
profile_picture(scale: 2) {
uri
}
}
`,
props.user,
);

return (
<>
<h1>{data.name}</h1>
<div>
<img src={data.profile_picture?.uri} />
</div>
</>
);
}

module.exports = UserComponent;

让我们提炼一下这里发生了什么

  • useFragment 接收一个片段定义和一个片段引用,并返回该片段和引用对应的 data
    • 这类似于 usePreloadedQuery,它接收一个查询定义和一个查询引用。
  • 片段引用 是 Relay 用于读取片段定义中声明的数据的对象;如您所见,UserComponent_user 片段本身只声明了 User 类型上的字段,但我们需要知道要从哪个特定用户读取这些字段;这就是片段引用所对应的。换句话说,片段引用就像指向要从中读取数据的特定类型实例的指针
  • 请注意,组件会自动订阅到片段数据的更新:如果此特定 User 的数据在应用程序中的任何地方更新(例如,通过获取新数据或更改现有数据),则组件将自动使用最新更新的数据重新渲染。
  • Relay 将在编译器运行时自动为任何声明的片段生成 Flow 类型,因此您可以使用这些类型来声明组件的 props 类型。
    • 生成的 Flow 类型包括片段引用的类型,该类型带有 $key 后缀:<fragment_name>$key,以及数据形状的类型,该类型带有 $data 后缀:<fragment_name>$data;这些类型可以从以下名称生成的文件中导入:<fragment_name>.graphql.js
    • 我们使用我们的 lint 规则 来强制执行使用 useFragment 时片段引用 prop 的类型是否正确声明。通过使用正确类型的片段引用作为输入,返回的 data 的类型将自动成为 Flow 类型,而无需显式注释。
    • 在我们的示例中,我们正在将 user prop 键入为 useFragment 所需的片段引用,它对应于从 UserComponent_user.graphql 导入的 UserComponent_user$key,这意味着上面的 data 的类型将是:{ name: ?string, profile_picture: ?{ uri: ?string } }
  • 片段名称需要全局唯一。为了轻松实现这一点,我们使用以下基于模块名称后跟标识符的约定来命名片段:<module_name>_<property_name>。这使得轻松识别哪些片段定义在哪些模块中,并在同一模块中定义多个片段时避免命名冲突。

如果您需要在同一个组件中渲染来自多个片段的数据,则可以多次使用 useFragment

import type {UserComponent_user$key} from 'UserComponent_user.graphql';
import type {UserComponent_viewer$key} from 'UserComponent_viewer.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay');

type Props = {
user: UserComponent_user$key,
viewer: UserComponent_viewer$key,
};

function UserComponent(props: Props) {
const userData = useFragment(
graphql`
fragment UserComponent_user on User {
name
profile_picture(scale: 2) {
uri
}
}
`,
props.user,
);

const viewerData = useFragment(
graphql`
fragment UserComponent_viewer on Viewer {
actor {
name
}
}
`,
props.viewer,
);

return (
<>
<h1>{userData.name}</h1>
<div>
<img src={userData.profile_picture?.uri} />
Acting as: {viewerData.actor?.name ?? 'Unknown'}
</div>
</>
);
}

module.exports = UserComponent;

组合片段

在 GraphQL 中,片段是可复用单元,这意味着它们可以包含其他片段,因此片段可以包含在其他片段或 查询 中。

fragment UserFragment on User {
name
age
profile_picture(scale: 2) {
uri
}
...AnotherUserFragment
}

fragment AnotherUserFragment on User {
username
...FooUserFragment
}

使用 Relay,您可以通过组件组合和片段组合来类似地组合片段组件。每个 React 组件负责获取其直接子级的数据依赖关系——就像它必须知道其子级的 props 才能正确渲染它们一样。这种模式意味着开发人员能够就组件进行本地推理——他们需要什么数据、他们渲染了什么组件——但 Relay 能够推断出整个 UI 树的数据依赖关系的全局视图。

/**
* UsernameSection.react.js
*
* Child Fragment Component
*/

import type {UsernameSection_user$key} from 'UsernameSection_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay');

type Props = {
user: UsernameSection_user$key,
};

function UsernameSection(props: Props) {
const data = useFragment(
graphql`
fragment UsernameSection_user on User {
username
}
`,
props.user,
);

return <div>{data.username ?? 'Unknown'}</div>;
}

module.exports = UsernameSection;
/**
* UserComponent.react.js
*
* Parent Fragment Component
*/

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay');

const UsernameSection = require('./UsernameSection.react');

type Props = {
user: UserComponent_user$key,
};

function UserComponent(props: Props) {
const user = useFragment(
graphql`
fragment UserComponent_user on User {
name
age
profile_picture(scale: 2) {
uri
}

# Include child fragment:
...UsernameSection_user
}
`,
props.user,
);

return (
<>
<h1>{user.name}</h1>
<div>
<img src={user.profile_picture?.uri} />
{user.age}

{/* Render child component, passing the _fragment reference_: */}
<UsernameSection user={user} />
</div>
</>
);
}

module.exports = UserComponent;

这里需要注意一些事项

  • UserComponent 同时渲染 UsernameSection并且在其自己的 graphql 片段声明中包含 UsernameSection 声明的片段。
  • UsernameSection 期望片段引用作为 user prop。正如我们之前提到的,片段引用是 Relay 用于读取片段定义中声明的数据的对象;如您所见,子 UsernameSection_user 片段本身只声明了 User 类型上的字段,但我们需要知道要从哪个特定用户读取这些字段;这就是片段引用所对应的。换句话说,片段引用就像指向要从中读取数据的特定类型实例的指针
  • 请注意,在本例中,传递给 UsernameSectionuser,即片段引用,实际上不包含子 UsernameSection 组件声明的任何数据;相反,UsernameSection 将使用片段引用来读取在内部声明的数据,使用 useFragment
  • 这意味着父组件不会收到子组件选择的数据(除非父组件明确选择了相同的字段)。同样,子组件也不会收到其父组件选择的数据(同样,除非子组件选择了相同的字段)。
  • 这可以防止不同的组件即使意外地彼此之间具有隐式依赖关系。如果不是这样,修改一个组件可能会破坏其他组件!
  • 这使我们能够就组件进行本地推理,并在不担心影响其他组件的情况下修改它们。
  • 这被称为 数据屏蔽
  • 子级(即 UsernameSection)期望的片段引用是读取包含子级片段的父片段的结果。在我们具体的示例中,这意味着读取包含 ...UsernameSection_user 的片段的结果将是 UsernameSection 期望的片段引用。换句话说,通过 useFragment 读取片段获得的结果数据也用作该片段中包含的任何子片段的片段引用。

将片段组合成查询

Relay 中的片段允许声明组件的数据依赖关系,但它们不能独立获取。相反,它们需要包含在查询中,无论是直接还是间接。这意味着所有片段在渲染时都必须属于一个查询,或者换句话说,它们必须“根植”在某个查询之下。请注意,单个片段仍然可以被多个查询包含,但是当渲染片段组件的特定实例时,它必须作为特定查询请求的一部分包含在内。

要获取和渲染包含片段的查询,您可以像组合片段一样组合它们,如 组合片段 部分所示

/**
* UserComponent.react.js
*
* Fragment Component
*/

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay');

type Props = {
user: UserComponent_user$key,
};

function UserComponent(props: Props) {
const data = useFragment(
graphql`...`,
props.user,
);

return (...);
}

module.exports = UserComponent;
/**
* App.react.js
*
* Query Component
*/

import type {AppQuery} from 'AppQuery.graphql';
import type {PreloadedQuery} from 'react-relay';

const React = require('React');
const {graphql, usePreloadedQuery} = require('react-relay');

const UserComponent = require('./UserComponent.react');

type Props = {
appQueryRef: PreloadedQuery<AppQuery>,
}

function App({appQueryRef}) {
const data = usePreloadedQuery(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name

# Include child fragment:
...UserComponent_user
}
}
`,
appQueryRef,
);

return (
<>
<h1>{data.user?.name}</h1>
{/* Render child component, passing the fragment reference: */}
<UserComponent user={data.user} />
</>
);
}

请注意

  • UserComponent 期望的片段引用是读取包含其片段的父查询的结果,在我们这种情况下意味着一个包含 ...UsernameSection_user 的查询。换句话说,usePreloadedQuery 获得的结果 data 也用作该查询中包含的任何子片段的片段引用。
  • 如前所述,所有片段在渲染时都必须属于一个查询,这意味着所有片段组件必须是查询的后代。这保证您始终能够通过从使用 usePreloadedQuery 读取的根查询的结果开始为 useFragment 提供片段引用。

此页面是否有用?

通过以下方式帮助我们改进网站: 回答一些快速问题.