大型 react-apollo 应用程序中片段组合的组织结构

Organization structure for fragment composition in large react-apollo apps

我正在使用 Apollo Client 和 React,我正在寻找一种策略来保持我的组件和组件数据需求的共存,以便 parent/sibling/child 可能需要它的组件可以访问它用于查询和突变。我希望能够轻松更新数据需求,这反过来又会更新由某些父组件查询或由 parent/sibling/child 中的突变返回的字段,以便准确更新我的 Apollo 缓存。

我已经尝试创建一个全局高级 graphql 目录,我的所有 queries/mutations.graphql 文件都位于该目录中,导入位于我的应用程序中的所有相关片段文件,然后直接导入这些文件,但是这可能会变得乏味并且不遵循父查询包含子片段的 parent/child 主题。同样在大型项目中,您最终会在导入时遍历长文件路径。

我也尝试过只创建片段文件并放置在与组件文件相对应的全局 graphql 目录中,但这并没有给我正在寻找的“component/data 要求”托管.

这个有效:

class CommentListItem extends Component {
  static fragments = {
    comment: gql`
      #...
    `,
  }
}
class CommentList extends Component {
  static fragments = {
    comment: gql`
      #...
      ${CommentListItem.fragments.comment}
    `,
  }
}
class CommentsPage extends Component {
  static fragments = {
    comment: gql`
      #...
      ${CommentList.fragments.comment}
    `,
  }
}
graphql(gql`
  query Comments {
    comments {
      ...CommentsListItemComment
    }
  }
  ${CommentsPage.fragments.comment}
`)

但是,如果我想在 CommentsPage 的后代中进行突变,我无法引用 CommentsPage.fragments.comment 的片段组成。

是否有针对此类事情的首选方法或最佳实践?

构造查询

如何构建代码始终是个人品味的问题,但我认为查询和组件的搭配是 GraphQL 的一大优势。

对于查询,我从 Relay Modern and the solution looks very close to what you described in the code. Right now as the project becomes bigger and we want to generate Flow type definitions for our queries, putting them into separate files next to the component files is also an option. This will be very similar to CSS-modules 中获得了很多灵感。

构建突变

当涉及到突变时,通常很难为它们找到合适的位置。需要对组件树中最底层的事件调用突变,并且通常会在应用程序的多个状态中更改应用程序的状态。在这种情况下,您希望调用者不知道数据消费者。使用片段似乎是一个简单的答案。突变将只包括为特定类型定义的所有片段。虽然突变现在不需要知道哪些字段是必需的,但它需要知道 who 需要类型上的字段。我想指出两种略有不同的方法,您可以使用它们来作为设计的基础。

全局突变:中继方法

接力现代 Mutations are basically global operations, that can be triggered by any component. This approach is not to bad since most mutations are only written once and thanks to variables are very reusable. They operate on one global state and don't care about which UI part consumes the update. When defining a mutation result you should usually query the properties that might have changed by the mutation instead of all the properties that are required by other components (through fragments). E.g. the mutation likeComment(id: ID!) should probably query for the likeCount and likes field on comment and not care much if any component uses the field at all or what other fields components require on Comment. This approach gets a bit more difficult when you have to update other queries or fields. the mutation createComment(comment: CreateCommentInput) might want to write to the root query object's comments field. This is where Relays structure of nodes and edges comes in handy. You can learn more about Relay updates here.

# A reusable likeComment mutation
mutation likeComment($id: ID!) {
  likeComment(id: $id) {
    comment {
      id
      likeCount
      likes {
        id
        liker {
          id
          name
        }
      }
    }
  }
}

很遗憾,我们无法回答一个问题:我们应该走多远?我需要点赞评论的人的名字,还是该组件只显示点赞数?

查询容器中的突变

并非所有 GraphQL API 都是以中继方式构建的。此外,Apollo 将 mutations 绑定到类似于 Redux action creators 的商店。我目前的方法是在与查询相同的级别上进行突变,然后将它们传递下去。这样您就可以访问 children 的片段,并在需要时在突变中使用它们。在您的示例中, CommentListItem 组件可能会显示一个赞按钮。它将为数据依赖项定义一个片段,根据片段定义道具类型和函数道具类型 likeComment: (id: string) => Promise<any>。此道具类型将传递给查询容器,该容器将 CommentsPage 包装在查询和变异中。

总结

您可以将这两种方法与 Apollo 一起使用。全局 mutations 文件夹可以包含可以在任何地方使用的突变。然后,您可以直接将突变绑定到需要它们的组件。一个好处是,例如在 likeComment 示例中,变量 id 可以直接从组件 props 派生,不需要绑定到组件本身。或者,您可以通过查询组件传递突变。这使您可以更广泛地了解数据消费者。在 CommentsPage 中,可以更容易地决定在突变完成时需要更新的内容。

请在评论中告诉我您的想法!