容器

声明数据需求的主要途径是通过 Relay.Container —一个更高阶的React组件,让React组件对其数据需求进行编码。

类似于 React 组件的 render 方法不直接修改原生视图,Relay 容器不会直接获取数据。相反,容器声明了渲染所需的数据的 规范。Relay 保证在渲染 之前 该数据是可用的。

一个完整的例子#

首先,我们来构建一个显示用户的个人资料照片和一个滑块来调整照片大小的的一般 React 版本<ProfilePicture>组件。

基本 React 组件 #

这是一个<ProfilePicture>的基本实现,忽略了样式,以突显功能:

class ProfilePicture extends React.Component {
  render() {
    // Expects the `user` prop to have the following shape:
    // {
    //   profilePhoto: {
    //     uri,
    //   }
    // }
    var user = this.props.user;
    return (
      <View>
        <Image uri={user.profilePhoto.uri} width={...} />
        <Slider onChange={value => this.setSize(value)} />
      </View>
    );
  }

  // Update the size of the photo
  setSize(photoSize) {
    // TODO: Fetch the profile photo URI for the given size...
  }
}

使用 GraphQL 描述数据依赖关系#

在 Relay 中, 使用GraphQL来描述数据依赖关系。对于 <ProfilePicture>, 依赖性可以表示如下。 这完全符合这个组件预期的 user prop 情形。

Relay.QL`
  # This fragment only applies to objects of type 'User'.
  fragment on User {
    # Set the 'size' argument to a GraphQL variable named '$size' so that we can
    # later change its value via the slider.
    profilePhoto(size: $size) {
      # Get the appropriate URI for the given size, for example on a CDN.
      uri,
    },
  }
`

Relay 容器 #

给定了一个简单的 React 组件和一个 GraphQL 片段,现在我们可以定义一个 Container 告诉 Relay 关于这个组件的数据需求。让我们先看看代码,然后看看发生了什么:

class ProfilePicture extends React.Component {/* as above */}

// Export a *new* React component that wraps the original `<ProfilePicture>`.
module.exports = Relay.createContainer(ProfilePicture, {
  // Specify the initial value of the `$size` variable.
  initialVariables: {
    size: 32
  },
  // For each of the props that depend on server data, we define a corresponding
  // key in `fragments`. Here, the component expects server data to populate the
  // `user` prop, so we'll specify the fragment from above as `fragments.user`.
  fragments: {
    user: () => Relay.QL`
      fragment on User {
        profilePhoto(size: $size) {
          uri,
        },
      }
    `,
  },
});

容器是高阶组件#

Relay 容器是高阶组件 — Relay.createContainer 是将 React 组件作为输入并返回新组件作为输出的函数。这意味着容器可以管理数据获取和逻辑解析,而不会干扰 state 内部组件。

下面是渲染容器时会发生的事情:

在上图中:

  • 父组件将传入一些 User "记录"的引用。
  • 为调试命名的容器 Relay(ProfilePicture) 将从本地存储检索每个GraphQL片段的响应。
  • 容器将每个片段的结果(以及其他 props) 传递给 <ProfilePicture> 组件。
  • <ProfilePicture> 接收一个 user prop 使用纯 JavaScript 数据对象,数组,字符串呈现。

请求不同的数据 #

上面的示例有一件事没做 — 实现 setSize(), 当滑块值更改时,应该改变图片的大小。除了将每个查询的结果传递给组件之外, Relay 也提供一个relay prop,它有 Relay 专用的方法和 metadata。这包括 variables — 用来获取当下 props 的变量 — 以及 setVariables() — 一个可以被用来请求不同变量值的数据的回调。

class ProfilePicture extends React.Component {
  render() {
    // Access the resolved data for the `user` fragment.
    var user = this.props.user;
    // Access the current `variables` that were used to fetch the `user`.
    var variables = this.props.relay.variables;
    return (
      <View>
        <Image
          uri={user.profilePhoto.uri}
          width={variables.size}
        />
        <Slider onChange={value => this.setSize(value)} />
      </View>
    );
  }

  // Update the size of the photo.
  setSize(photoSize) {
    // `setVariables()` tells Relay that the component's data requirements have
    // changed. The value of `props.relay.variables` and `props.user` will
    // continue to reflect their previous values until the data for the new
    // variables has been fetched from the server. As soon as data for the new
    // variables becomes available, the component will re-render with an updated
    // `user` prop and `variables.size`.
    this.props.relay.setVariables({
      size: photoSize,
    });
  }
}

容器组合 #

React 和 Relay 支持通过 组合 创建任意复杂的应用程序。 可以通过组合较小的组件来创建更大的组件,帮助我们创建模块化,健壮的应用程序。在 Relay中组成组件有两个方面:

  • 构成视图逻辑,和
  • 撰写数据说明。

我们来探讨如何通过从上面的 <Profile> 组成组件 <ProfilePicture>

组合视图 - 这是 Plain React #

视图组合 正是 你以往所做的 — Relay 容器是标准的 React 组件。 这是 <Profile> 组件:

class Profile extends React.Component {
  render() {
    // Expects a `user` with a string `name`, as well as the information
    // for `<ProfilePicture>` (we'll get that next).
    var user = this.props.user;
    return (
      <View>
        {/* It works just like a React component, because it is one! */}
        <ProfilePicture user={user} />
        <Text>{user.name}</Text>
      </View>
    );
  }
}

片段组合 #

片段组合的作用类似 - 父容器的片段为每个子节点组成的片段。在这种情况下, <Profile> 需要获取 <ProfilePicture>需要的 User 信息 。/p>

Relay 提供一个静态 getFragment() 方法,返回对该组件片段的引用:

class Profile extends React.Component {/* as above */}

module.exports = Relay.createContainer(Profile, {
  fragments: {
    // This `user` fragment name corresponds to the prop named `user` that is
    // expected to be populated with server data by the `<Profile>` component.
    user: () => Relay.QL`
      fragment on User {
        # Specify any fields required by '<Profile>' itself.
        name,

        # Include a reference to the fragment from the child component. Here,
        # the 'user' is the name of the fragment specified on the child
        # "<ProfilePicture>'s" 'fragments' definition.
        ${ProfilePicture.getFragment('user')},
      }
    `,
  }
});

最终的数据声明相当于如下普通 GraphQL:

`
  fragment Profile on User {
    name,
    ...ProfilePhoto,
  }

  fragment ProfilePhoto on User {
    profilePhoto(size: $size) {
      uri,
    },
  }
`

注意,当编写片段时,组合片段的类型必须与嵌入其中的父对象的字段相匹配。 例如,嵌入一个 Story 类的片段到父 User 类的字段中没有意义。如果你错了,Relay 和 GraphQL 将会提供有用的错误信息 (如果没有帮助,请让我们知道!)。

渲染容器 #

据我们所知, Relay 将数据要求声明为GraphQL片段。 这意味着,例如<ProfilePicture> 不仅可以嵌入<Profile>, ,而且可以嵌入获取任何User类型字段的容器。

我们几乎可以让 Relay 满足这些组件的数据要求并进行渲染。不过,有一个问题。为了使用 GraphQL 实际地抓取数据,我们需要查询根。举例来说,我们需要把 <Profile> 片段放在一个 User 类型的 GraphQL 查询中。

在 Relay中, 查询的根由 Route 定义。继续了解 Relay 路由。