当我们需要用 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))
}
}