(Gutenberg)块
以下是获取 Gutenberg 块数据的方法。
GraphQL 模式为所有 CustomPost 类型(例如 Post 和 Page)添加了以下字段:
blocksblockDataItemsblockFlattenedDataItems
如果 Classic Editor 插件处于激活状态,这些字段将不可用。
blocks
字段 CustomPost.blocks: [BlockUnion!] 用于检索自定义文章中包含的所有块的列表。
blocks 返回已映射到 GraphQL 模式的 Block 类型列表。这些 Block 类型都是 BlockUnion 类型的一部分,并实现了 Block 接口。
该插件实现了一种 Block 类型 GenericBlock,仅凭它就足以检索任何块的数据(通过字段 attributes: JSONObject)。
此 query:
{
post(by: { id: 1 }) {
blocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
}
}
}
}
}
}
}
}
}...将生成以下响应:
{
"data": {
"post": {
"blocks": [
{
"name": "core/gallery",
"attributes": {
"linkTo": "none",
"className": "alignnone",
"images": [
{
"url": "https://d.pr/i/zd7Ehu+",
"alt": "",
"id": "1706"
},
{
"url": "https://d.pr/i/jXLtzZ+",
"alt": "",
"id": "1705"
}
],
"ids": [],
"shortCodeTransforms": [],
"imageCrop": true,
"fixedHeight": true,
"sizeSlug": "large",
"allowResize": false
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"content": "List Block",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/list",
"attributes": {
"ordered": false,
"values": "<li>List item 1</li><li>List item 2</li><li>List item 3</li><li>List item 4</li>"
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"className": "has-top-margin",
"content": "Columns Block",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 1701,
"className": "layout-column-1",
"url": "https://d.pr/i/fW6V3V+",
"alt": ""
},
"innerBlocks": null
}
]
},
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/paragraph",
"attributes": {
"className": "layout-column-2",
"content": "Phosfluorescently morph intuitive relationships rather than customer directed human capital.",
"dropCap": false
},
"innerBlocks": null
}
]
}
]
},
{
"name": "core/heading",
"attributes": {
"content": "Columns inside Columns (nested inner blocks)",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 1701,
"className": "layout-column-1",
"url": "https://d.pr/i/fW6V3V+",
"alt": ""
},
"innerBlocks": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {
"width": "33.33%"
},
"innerBlocks": [
{
"name": "core/heading",
"attributes": {
"fontSize": "large",
"content": "Life is so rich",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"level": 3,
"content": "Life is so dynamic"
},
"innerBlocks": null
}
]
},
{
"name": "core/column",
"attributes": {
"width": "66.66%"
},
"innerBlocks": [
{
"name": "core/paragraph",
"attributes": {
"content": "This rhyming poem is the spark that can reignite the fires within you. It challenges you to go out and live your life in the present moment as a \u201chero\u201d and leave your mark on this world.",
"dropCap": false
},
"innerBlocks": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 361,
"sizeSlug": "large",
"linkDestination": "none",
"url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/graphql-voyager-public-1024x622.jpg",
"alt": ""
},
"innerBlocks": null
}
]
},
{
"name": "core/column",
"attributes": {},
"innerBlocks": null
},
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 362,
"sizeSlug": "large",
"linkDestination": "none",
"url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/namespaced-interactive-schema-1024x598.webp",
"alt": ""
},
"innerBlocks": null
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
}
}Block 类型的 GraphQL 模式如下所示:
interface Block {
name: String!
attributes: JSONObject
innerBlocks: [BlockUnion!]
contentSource: HTML!
}
type GenericBlock implements Block {
name: String!
attributes: JSONObject
innerBlocks: [BlockUnion!]
contentSource: HTML!
}
union BlockUnion = GenericBlockBlock 字段
Block 接口(以及 GeneralBlock 类型)包含以下字段:
name检索块的名称:"core/paragraph"、"core/heading"、"core/image"等。attributes检索包含块所有属性的 JSON 对象。innerBlocks检索[BlockUnion!],因此我们可以使用它在包含内嵌块的块层次结构中导航,并获取所有层级的数据,深度不限。contentSource检索块的(Gutenberg)HTML 源代码,包括含有属性的注释分隔符。但是,该字段检索到的数据与数据库中存储的格式并不完全相同(参见 #2346),请谨慎使用此字段。
直接检索 GeneralBlock(而非 BlockUnion)
由于目前只有一种 Block 类型 GeneralBlock 用于映射块,因此让 CustomPost.blocks(以及 Block.innerBlocks)直接返回该类型而非 BlockUnion 类型是合理的。
可以在设置页面的"Blocks"选项卡中,勾选选项 Use single type instead of union type? 来进行配置:

这样,GraphQL query 将得以简化:
{
post(by: { id: 1 }) {
blocks {
name
attributes
innerBlocks {
name
attributes
}
}
}
}请注意,保持响应类型为 BlockUnion 有利于前向兼容性:如果将来决定向模式中添加块特定类型(参见下面的章节),则不会产生破坏性变更。
映射块特定类型
JSONObject 类型(由 Block.attributes 检索)没有严格的类型约束:其属性可以具有任意类型和基数(String、Int、[Boolean!] 等),因此我们需要了解每个块的这些信息,并在客户端分别处理各种情况。
如果需要严格类型,则必须通过 PHP 代码扩展 GraphQL 模式,添加块特定类型,将块的特定属性映射为字段,并使其成为 BlockUnion 的一部分。
例如,可以添加 CoreParagraphBlock 类型来映射 core/paragraph 块,并添加类型为 String 的字段 content。
有关如何扩展 GraphQL 模式的说明,请参阅 GatoGraphQL/GatoGraphQL 的文档(目前正在完善中)。
过滤块
字段 CustomPost.blocks 包含参数 filterBy,该参数具有 include 和 exclude 两个属性。我们可以使用它们按块名称过滤要检索的块:
{
post(by: { id: 1 }) {
id
blocks(
filterBy: {
include: [
"core/heading",
"core/gallery"
]
}
) {
name
attributes
}
}
}这将生成:
{
"data": {
"post": {
"blocks": [
{
"name": "core/gallery",
"attributes": {
"linkTo": "none",
"className": "alignnone",
"images": [
{
"url": "https://d.pr/i/zd7Ehu+",
"alt": "",
"id": "1706"
},
{
"url": "https://d.pr/i/jXLtzZ+",
"alt": "",
"id": "1705"
}
],
"ids": [],
"shortCodeTransforms": [],
"imageCrop": true,
"fixedHeight": true,
"sizeSlug": "large",
"allowResize": false
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"content": "List Block",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"className": "has-top-margin",
"content": "Columns Block",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"content": "Columns inside Columns (nested inner blocks)",
"level": 2
},
"innerBlocks": null
}
]
}
}
}请注意,并非所有 core/heading 类型的块都被包含在内:嵌套在 core/column 下的块已被排除,因为没有办法访问它们(core/columns 和 core/column 块本身已被排除)。
字段 blocks 的不便之处
字段 blocks 存在一个不便之处:为了检索自定义文章中包含的所有块数据(包括内嵌块的数据、内嵌块的内嵌块数据,依此类推),我们必须了解内容中嵌套块的层级数,并在 GraphQL query 中反映这一信息。
如果不知道层级数,则必须编写包含足够层级的 query,以确保所有数据都能被获取。
例如,以下 query 最多检索 7 层内嵌块:
{
post(by: { id: 1 }) {
blocks {
...BlockData
}
}
}
fragment BlockData on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
innerBlocks {
...on Block {
name
attributes
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}blockDataItems
为了避免字段 blocks 检索所有数据(包括其内嵌块、内嵌块的内嵌块,依此类推)时存在的不便,提供了字段 CustomPost.blockDataItems。
该字段不返回 [BlockUnion],而是返回 [JSONObject!]:
type CustomPost {
blockDataItems: [JSONObject!]
}换言之,与典型 GraphQL 方式(实体关联实体并在它们之间导航)不同,顶层的每个 Block 实体已经在单个 JSONObject 结果中生成了自身及其所有子块的完整块数据。
JSON 对象递归地包含块的属性(在 name 和 attributes 条目下)及其内嵌块的属性(在 innerBlocks 条目下)。
例如,以下 query:
{
post(by: { id: 1 }) {
blockDataItems
}
}...将生成:
{
"data": {
"post": {
"blockDataItems": [
{
"name": "core/gallery",
"attributes": {
"linkTo": "none",
"className": "alignnone",
"images": [
{
"url": "https://d.pr/i/zd7Ehu+",
"alt": "",
"id": "1706"
},
{
"url": "https://d.pr/i/jXLtzZ+",
"alt": "",
"id": "1705"
}
],
"ids": [],
"shortCodeTransforms": [],
"imageCrop": true,
"fixedHeight": true,
"sizeSlug": "large",
"allowResize": false
}
},
{
"name": "core/heading",
"attributes": {
"content": "List Block",
"level": 2
}
},
{
"name": "core/list",
"attributes": {
"ordered": false,
"values": "<li>List item 1</li><li>List item 2</li><li>List item 3</li><li>List item 4</li>"
}
},
{
"name": "core/heading",
"attributes": {
"className": "has-top-margin",
"content": "Columns Block",
"level": 2
}
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 1701,
"className": "layout-column-1",
"url": "https://d.pr/i/fW6V3V+",
"alt": ""
}
}
]
},
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/paragraph",
"attributes": {
"className": "layout-column-2",
"content": "Phosfluorescently morph intuitive relationships rather than customer directed human capital.",
"dropCap": false
}
}
]
}
]
},
{
"name": "core/heading",
"attributes": {
"content": "Columns inside Columns (nested inner blocks)",
"level": 2
}
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 1701,
"className": "layout-column-1",
"url": "https://d.pr/i/fW6V3V+",
"alt": ""
}
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {
"width": "33.33%"
},
"innerBlocks": [
{
"name": "core/heading",
"attributes": {
"fontSize": "large",
"content": "Life is so rich",
"level": 2
}
},
{
"name": "core/heading",
"attributes": {
"level": 3,
"content": "Life is so dynamic"
}
}
]
},
{
"name": "core/column",
"attributes": {
"width": "66.66%"
},
"innerBlocks": [
{
"name": "core/paragraph",
"attributes": {
"content": "This rhyming poem is the spark that can reignite the fires within you. It challenges you to go out and live your life in the present moment as a \u201chero\u201d and leave your mark on this world.",
"dropCap": false
}
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlocks": [
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 361,
"sizeSlug": "large",
"linkDestination": "none",
"url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/graphql-voyager-public-1024x622.jpg",
"alt": ""
}
}
]
},
{
"name": "core/column",
"attributes": {}
},
{
"name": "core/column",
"attributes": {},
"innerBlocks": [
{
"name": "core/image",
"attributes": {
"id": 362,
"sizeSlug": "large",
"linkDestination": "none",
"url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/namespaced-interactive-schema-1024x598.webp",
"alt": ""
}
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
}
}过滤块数据项
与 blocks 类似,blockDataItems 也允许通过 filterBy 参数过滤要检索的块。
此 query:
{
post(by: { id: 1 }) {
id
blockDataItems(
filterBy: {
include: [
"core/heading"
]
}
)
}
}...将生成:
{
"data": {
"post": {
"blockDataItems": [
{
"name": "core/heading",
"attributes": {
"content": "List Block",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"className": "has-top-margin",
"content": "Columns Block",
"level": 2
},
"innerBlocks": null
},
{
"name": "core/heading",
"attributes": {
"content": "Columns inside Columns (nested inner blocks)",
"level": 2
},
"innerBlocks": null
}
]
}
}
}请注意,与 blocks 类似,并非所有 core/heading 类型的块都被包含在内:嵌套在 core/column 下的块已被排除,因为没有办法访问它们(core/columns 和 core/column 块本身已被排除)。
blockFlattenedDataItems
字段 blocks 和 blockDataItems 都允许过滤要检索的块(通过 filterBy 参数)。在这两种情况下,如果一个块满足包含条件,但嵌套在不满足条件的块中,则该块将被排除。
然而,有时我们需要从自定义文章中检索某种类型的所有块,而不管这些块在层次结构中的位置。例如,我们可能希望包含所有 core/image 类型的块,以检索博客文章中包含的所有图片。
为了满足这一需求,提供了字段 CustomPost.blockFlattenedDataItems。与字段 blocks 和 blockDataItems 不同,它将块层次结构展平为单一层级。
此 query:
{
post(by: { id: 1 }) {
blockFlattenedDataItems
}
}...将生成:
{
"data": {
"post": {
"blockFlattenedDataItems": [
{
"name": "core/gallery",
"attributes": {
"linkTo": "none",
"className": "alignnone",
"images": [
{
"url": "https://d.pr/i/zd7Ehu+",
"alt": "",
"id": "1706"
},
{
"url": "https://d.pr/i/jXLtzZ+",
"alt": "",
"id": "1705"
}
],
"ids": [],
"shortCodeTransforms": [],
"imageCrop": true,
"fixedHeight": true,
"sizeSlug": "large",
"allowResize": false
},
"innerBlockPositions": null,
"parentBlockPosition": null
},
{
"name": "core/heading",
"attributes": {
"content": "List Block",
"level": 2
},
"innerBlockPositions": null,
"parentBlockPosition": null
},
{
"name": "core/list",
"attributes": {
"ordered": false,
"values": "<li>List item 1</li><li>List item 2</li><li>List item 3</li><li>List item 4</li>"
},
"innerBlockPositions": null,
"parentBlockPosition": null
},
{
"name": "core/heading",
"attributes": {
"className": "has-top-margin",
"content": "Columns Block",
"level": 2
},
"innerBlockPositions": null,
"parentBlockPosition": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlockPositions": [
5,
7
],
"parentBlockPosition": null
},
{
"name": "core/column",
"attributes": {},
"parentBlockPosition": 4,
"innerBlockPositions": [
6
]
},
{
"name": "core/image",
"attributes": {
"id": 1701,
"className": "layout-column-1",
"url": "https://d.pr/i/fW6V3V+",
"alt": ""
},
"parentBlockPosition": 5,
"innerBlockPositions": null
},
{
"name": "core/column",
"attributes": {},
"parentBlockPosition": 4,
"innerBlockPositions": [
8
]
},
{
"name": "core/paragraph",
"attributes": {
"className": "layout-column-2",
"content": "Phosfluorescently morph intuitive relationships rather than customer directed human capital.",
"dropCap": false
},
"parentBlockPosition": 7,
"innerBlockPositions": null
},
{
"name": "core/heading",
"attributes": {
"content": "Columns inside Columns (nested inner blocks)",
"level": 2
},
"innerBlockPositions": null,
"parentBlockPosition": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"innerBlockPositions": [
11
],
"parentBlockPosition": null
},
{
"name": "core/column",
"attributes": {},
"parentBlockPosition": 10,
"innerBlockPositions": [
12,
13
]
},
{
"name": "core/image",
"attributes": {
"id": 1701,
"className": "layout-column-1",
"url": "https://d.pr/i/fW6V3V+",
"alt": ""
},
"parentBlockPosition": 11,
"innerBlockPositions": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"parentBlockPosition": 11,
"innerBlockPositions": [
14,
17
]
},
{
"name": "core/column",
"attributes": {
"width": "33.33%"
},
"parentBlockPosition": 13,
"innerBlockPositions": [
15,
16
]
},
{
"name": "core/heading",
"attributes": {
"fontSize": "large",
"content": "Life is so rich",
"level": 2
},
"parentBlockPosition": 14,
"innerBlockPositions": null
},
{
"name": "core/heading",
"attributes": {
"level": 3,
"content": "Life is so dynamic"
},
"parentBlockPosition": 14,
"innerBlockPositions": null
},
{
"name": "core/column",
"attributes": {
"width": "66.66%"
},
"parentBlockPosition": 13,
"innerBlockPositions": [
18,
19
]
},
{
"name": "core/paragraph",
"attributes": {
"content": "This rhyming poem is the spark that can reignite the fires within you. It challenges you to go out and live your life in the present moment as a \u201chero\u201d and leave your mark on this world.",
"dropCap": false
},
"parentBlockPosition": 17,
"innerBlockPositions": null
},
{
"name": "core/columns",
"attributes": {
"isStackedOnMobile": true
},
"parentBlockPosition": 17,
"innerBlockPositions": [
20,
22,
23
]
},
{
"name": "core/column",
"attributes": {},
"parentBlockPosition": 19,
"innerBlockPositions": [
21
]
},
{
"name": "core/image",
"attributes": {
"id": 361,
"sizeSlug": "large",
"linkDestination": "none",
"url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/graphql-voyager-public-1024x622.jpg",
"alt": ""
},
"parentBlockPosition": 20,
"innerBlockPositions": null
},
{
"name": "core/column",
"attributes": {},
"parentBlockPosition": 19,
"innerBlockPositions": null
},
{
"name": "core/column",
"attributes": {},
"parentBlockPosition": 19,
"innerBlockPositions": [
24
]
},
{
"name": "core/image",
"attributes": {
"id": 362,
"sizeSlug": "large",
"linkDestination": "none",
"url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/namespaced-interactive-schema-1024x598.webp",
"alt": ""
},
"parentBlockPosition": 23,
"innerBlockPositions": null
}
]
}
}
}请注意,属性 innerBlocks 已消失,因为块不再嵌套。取而代之的是,响应中包含另外两个属性(可用于重建块层次结构):
parentBlockPosition:该块的父块在返回数组中的位置,如果是顶层块则为nullinnerBlockPositions:包含该块的内嵌块在返回数组中位置的数组
过滤展平的块数据项
由于块层次结构已被展平,按 core/heading 过滤将生成所有此类块(即使其中某些块原本嵌套在已被排除的块下)。
此 query:
{
post(by: { id: 1 }) {
id
blockFlattenedDataItems(
filterBy: {
include: [
"core/heading"
]
}
)
}
}...将生成:
{
"data": {
"post": {
"blockFlattenedDataItems": [
{
"name": "core/heading",
"attributes": {
"content": "List Block",
"level": 2
}
},
{
"name": "core/heading",
"attributes": {
"className": "has-top-margin",
"content": "Columns Block",
"level": 2
}
},
{
"name": "core/heading",
"attributes": {
"content": "Columns inside Columns (nested inner blocks)",
"level": 2
}
},
{
"name": "core/heading",
"attributes": {
"fontSize": "large",
"content": "Life is so rich",
"level": 2
}
},
{
"name": "core/heading",
"attributes": {
"level": 3,
"content": "Life is so dynamic"
}
}
]
}
}
}请注意,过滤时会删除 parentBlockPosition 和 innerBlockPositions 这两个附加属性,因为它们不再有意义。