当我们需要用 GraphQL 查询多层套嵌的数据,比如像 WordPress 这样套嵌的评论信息时,通常的写法是:

{
  posts(first: 100) {
    nodes {
      id
      title
      comments {
        nodes {
          ...CommentFields
          replies: children {
            nodes {
              ...CommentFields
              replies: children {
                nodes {
                  ...CommentFields
                  replies: children {
                    nodes {
                      ...CommentFields
                      replies: children {
                        nodes {
                          ...CommentFields
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

fragment CommentFields on Comment {
  id
  date
  type
  approved
  content
}

以上的写法只实现了四层套嵌评论的查询,很麻烦对不对?这或许是 GraphQL 的缺陷,但这也或许正体现了 GraphQL 的设计理念——所得即所查。

找了一下,没有现成的轮子,就自己写一个套嵌实现吧(注意 graphql 查询语句要顶头写,多余的缩进会影响递归结果):

import ApolloClient from 'apollo-boost'
import gql from 'graphql-tag'

class getPostCommentByPostId {
  private postId: number
  private MaxChildrenLevel: number
  private data: any

  /**
   * @param {number} postId           wordpress post id
   * @param {number} MaxChildrenLevel post threaded (nested) comments levels deep (/wp-admin/options-discussion.php)
   */
  public constructor(postId: number, MaxChildrenLevel) {
    this.postId = postId
    this.MaxChildrenLevel = MaxChildrenLevel
  }

  // 处理递归部分
  private queryChildren() {
    let queryHeader: string = ``
    let queryFooter: string = ``
    // 迭代之前的内容
    const childrenHeader: string = `
children {
  nodes {
    ...CommentFields`
    // 迭代之后的内容
    const childrenFooter: string = `
  }
}`

    // 处理每行前的空格
    let addTabs = function (str: string, n: number) {
      return str.replace(/^/gm, '  '.repeat(n)) // 注意我用的是两格缩进,四格缩进请自行调整
    }

    for (let i = 0; i < this.MaxChildrenLevel; i++) {
      queryHeader = addTabs(childrenHeader + queryHeader, 2)
      queryFooter = addTabs(queryFooter + childrenFooter, 2)
    }
    return addTabs(queryHeader + queryFooter, 2)
  }

  // 查询部分
  private query() {
    const client: ApolloClient = new ApolloClient()
    client.query({
      query: gql`
query GET_POST($postId: Int) {
  postBy(postId: $postId) {
    id
    postId
    title
    date
    uri
    content
  }
  comments {
    edges {
      node {
        ...CommentFields${this.queryChildren()}
      }
    }
  }
}
fragment CommentFields on Comment {
  date
  agent
  content(format: RENDERED)
  commentId
  author {
    ... on CommentAuthor {
      email
      name
      url
    }
  }
  authorIp
}
`,
      variables: {
        "postId": this.postId
      },
    })
      .then(data => console.log(data))
      .catch(error => console.log(error))
  }
}

Q.E.D.