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

Posted

技术标签:

【中文标题】RelayJS:查询任意数据的子组件【英文标题】:RelayJS: Children components querying arbitrary data 【发布时间】:2016-03-28 10:46:37 【问题描述】:

我正在构建一个包含许多不同小部件的仪表板。用户可以添加和删除任何小部件,并以他们喜欢的任何顺序放置它们。每个小部件都有自己的数据需求。构建容器层次结构的正确 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 的可行性,并将很快更新。

【问题讨论】:

嗯,这很有趣。通常,您只需将子组件的数据需求嵌套到父容器中,但在您的情况下,您不知道需要什么。 @NevilleS 完全正确。即使对于 React,我也不确定这是否是一种反模式,但我想不出其他方式。我尝试动态插入片段,但这似乎不起作用。我仍在尝试的唯一可行方法(我相信)是让每个小部件都有自己的RootContainer 您是否使用任何类型的根“查看器”节点(例如,绕过对根的复数列表限制)?如果是这样,您应该能够将 Viewer 对象上的 Fragment 传递给 Widget,然后每个 Widget 将仅在 Viewer 下列出它需要的数据项。 @Nik 是的 viewer 正在使用中。这里的问题是没有好的方法来组成Widget 类的片段。我将更新我的问题以更好地说明问题 【参考方案1】:

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

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

联合类型应该允许您将“仪表板”类型描述为具有“小部件”列表。如果将“Widget”定义为联合类型,则可以拥有许多不同类型的 Widget,它们具有自己独特的字段和数据。

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

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 question)。然后,您的仪表板组件可以使用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”方式。

这有意义吗?

【讨论】:

太棒了!没有意识到 Union 类型存在。让我试试看:D 怎么样?我打算很快尝试一些Union 类型的连接,所以我很好奇...... 效果非常好!我正在使用flux来保持仪表板的预览状态,所以我花了一段时间才让事情正常进行。但是对于这个问题的范围,你的答案是救命稻草。谢谢!

以上是关于RelayJS:查询任意数据的子组件的主要内容,如果未能解决你的问题,请参考以下文章

RelayJS:如何通过道具设置初始变量

什么是适合 relayjs 的后端?

在mysql中连接任意数量的字符串行(分层查询)

带有整数查询参数的 RelayJS 不变违规

如何使用 XPath 选择任意深度的子元素?

关于高级查询的知识