RelayJS:查询任意数据的子组件

RelayJS: Children components querying arbitrary data

我正在构建一个包含许多不同小部件的仪表板。用户可以添加和删除任何小部件,并以他们喜欢的任何顺序放置它们。每个小部件都有自己的数据要求。构建容器层次结构的正确 Relay 方式是什么?

为了提供一些上下文,这是目前的架构:

Widget 是一个接受配置对象并相应地呈现相应组件的组件。

class Widget extends React.Component {
  render() {
    const {widget} = this.props;
    // widgetMap is a map that maps string to React component
    const ActualWidget = widgetMap.get(widget.component);

    return (
      <ActualWidget data={widget.data} />
    );
  }
}

export default Relay.createContainer(Widget, {
  fragments: {
    data: () => Relay.QL`
      fragment on ??? {
        # What should be here since we don't know what will be rendered?
      }
    `
  }
});

Dashboard 组件包含用户添加的一些小部件。

class Dashboard extends React.Component {
  renderWidgets = () => {
    return this.props.widgets.map(widget => {
      return <Widget widget={widget}/>;
    });
  };

  render() {
    return (
      <div>
        <span>Hello, {this.props.user.name}</span>
        {this.renderWidgets()}
      </div>
    );
  }
}

export default Relay.createContainer(Dashboard, {
  fragments: {
    // `user` fragment is used by Dashboard
    user: () => Relay.QL`
      fragment on User {
        name
      }
    `,
    // Each widget have different fragment,
    // So what should be here?
  }
});

更新

我试图让每一个ActualWidget成为一个观众的领域。所以架构有点像这样:

type Viewer {
  widget1: Widget1
  widget2: Widget2
}

type Widget1 {
  name,
  fieldOnlyForWidget1
}

type Widget2 {
  name,
  fieldOnlyForWidget2
}

然后对于我的 Widget 容器,我尝试动态插入片段。

export default Relay.createContainer(Widget, {
  initialVariables: {
    component: 'Widget1' // Trying to set the string here
  }

  fragments: {
    data: (variables) => Relay.QL`
      fragment on Viewer { # We now know it is Viewer type
        # This didn't work because `variables.component` is not string! :(
        ${widgetMap.get(variables.component).getFragment('viewer')}
      }
    `
  }
});

那是行不通的。我相信 Relay 静态解析了 QL,因此它无法组成动态片段。不过这只是我的猜测。

我正在测试每个小部件使用 RootContainer 的可行性,很快就会更新。

我认为这里的根本问题是您试图让客户端在运行时根据服务器提供的某些数据(小部件名称)来决定要请求的数据类型。这始终是一个两步过程,因此您不能将子片段嵌套在父片段中以进行一次提取;因此,您需要设置一个新的 RootContainer 或其他东西来启动一组新的查询,一旦您知道自己需要什么。

但是,我认为您可以通过在图形本身中对小部件信息进行编码,使用常规嵌套将其作为单个提取来完成。这里的技巧是使用 GraphQL 联合类型:docs

联合类型应该允许您将 "Dashboard" 类型描述为具有 "Widgets" 的列表。如果您将 "Widget" 定义为联合类型,您可以拥有许多不同类型的 Widget,它们具有自己独特的字段和数据。

一旦您的服务器提供了联合类型,您就可以编写一个如下所示的仪表板中继容器:

var Dashboard = Relay.createContainer(DashboardComponent, {
  fragments: {
    dashboard: () => { console.log("dashboard query"); return Relay.QL`
      fragment on Dashboard {
        widgets {
          _typename
          ${FooWidget.getFragment("widget")}
          ${BarWidget.getFragment("widget")}
        }
      }
    `},
  }
});

请注意在联合类型上定义的特殊“__typename”字段(有关详细信息,请参阅 )。然后,您的仪表板组件可以使用 this.props.dashboard.widgets 遍历所有小部件并基于 __typename 创建适当的小部件,如下所示:

var widgets = this.props.dashboard.widgets.map((widget) => {
  if (widget.__typename == "FooWidget") {
    return <FooWidget widget={widget} />
  } else if (widget.__typename == "BarWidget") {
    return <CountdownWidget widget={widget} />
  }
})

我很确定这会奏效,但我自己还没有尝试过。根据我对 Relay 的了解(不是那么多),这可能是对问题建模的最 "Relay-ish" 方式。

这有意义吗?