
Gutenberg QueryControls 组件详解构建块编辑器中的文章查询控制面板【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg本文以 Gutenberg 仓库中wordpress/components包的 QueryControls 组件文档为核心完整讲解该组件的两种分类选择模式单选与多选、全部 Props 参数及其默认值并结合组件源码解析其条件渲染逻辑、orderBy/order复合值拆分机制以及buildTermsTree如何将扁平的分类/作者列表构建成树形结构供 TreeSelect 使用。读完后你能够在自定义块中复用这套“排序 过滤 数量”查询控制模式并理解其底层实现细节。组件定位与导出方式QueryControls 是 Gutenberg 的组件库wordpress/components中提供的“文章查询控制”组件聚合了排序Order/OrderBy、分类筛选、作者筛选和条目数量范围控制四类控件通常出现在需要让用户配置“查询哪些文章、如何排序、取多少条”的块编辑场景中。该组件从组件包入口导出入口文件export { default as QueryControls } from ./query-controls;组件本体实现在 index.tsx其核心结构是用VStack垂直布局容器按固定顺序挂载四个可选控件每个控件是否渲染都由对应的回调 prop 是否存在决定Order by 下拉框仅当onOrderChange与onOrderByChange同时传入时渲染分类选择传入categoriesList渲染单选模式传入categorySuggestions渲染多选模式作者选择传入onAuthorChange时渲染条目数量 RangeControl传入onNumberOfItemsChange时渲染。这种“回调即开关”的设计意味着不传某个回调对应的 UI 就不出现父组件完全控制哪些控件可见。单分类选择模式基础用法组件文档README给出的第一个使用场景是通过categoriesListselectedCategoryId实现“一次只选一个分类”的模式。完整示例如下import { useState } from react; import { QueryControls } from wordpress/components; const QUERY_DEFAULTS { category: 1, categories: [ { id: 1, name: Category 1, parent: 0, }, { id: 2, name: Category 1b, parent: 1, }, { id: 3, name: Category 2, parent: 0, }, ], maxItems: 20, minItems: 1, numberOfItems: 10, order: asc, orderBy: title, }; const MyQueryControls () { const [ query, setQuery ] useState( QUERY_DEFAULTS ); const { category, categories, maxItems, minItems, numberOfItems, order, orderBy } query; const updateQuery ( newQuery ) { setQuery( { ...query, ...newQuery } ); }; return ( QueryControls { ...{ maxItems, minItems, numberOfItems, order, orderBy } } onOrderByChange{ ( newOrderBy ) updateQuery( { orderBy: newOrderBy } ) } onOrderChange{ ( newOrder ) updateQuery( { order: newOrder } ) } categoriesList{ categories } selectedCategoryId{ category } onCategoryChange{ ( newCategory ) updateQuery( { category: newCategory } ) } onNumberOfItemsChange{ ( newNumberOfItems ) updateQuery( { numberOfItems: newNumberOfItems } ) } / ); };几个关键点categories是扁平数组每项为{ id, name, parent }其中parent: 0表示顶级分类parent: 1表示 id 为 1 的分类的子分类maxItems/minItems限定“条目数量”滑块的取值范围不传时默认上限 100、下限 1orderasc | desc与orderBydate | title是分开的两个状态分别通过onOrderChange和onOrderByChange回调更新。单选模式下组件内部会用categoriesList构建一棵分类树再交给TreeSelect渲染因此父级分类会自动展示层级结构。相关实现在 category-select.tsxconst termsTree useMemo( () { return buildTermsTree( categoriesList ); }, [ categoriesList ] ); return ( TreeSelect { ...{ label, noOptionLabel, onChange: onChangeProp } } tree{ termsTree } selectedId{ selectedCategoryId ! undefined ? String( selectedCategoryId ) : undefined } { ...props } / );值得注意的是selectedCategoryId是 number而 TreeSelect 的selectedId是 string组件内部做了String( selectedCategoryId )的类型转换同理onChange回调收到的是字符串形式的分类 ID父组件若需要数字 ID 需自行转换。多分类选择模式categorySuggestions 与 selectedCategories文档的第二个场景说明了组件支持的多分类选择能力用categorySuggestions替代categoriesList、用selectedCategories数组替代selectedCategoryId。完整示例如下const QUERY_DEFAULTS { orderBy: title, order: asc, selectedCategories: [ { id: 1, value: Category 1, parent: 0, }, { id: 2, value: Category 1b, parent: 1, }, ], categories: { Category 1: { id: 1, name: Category 1, parent: 0, }, Category 1b: { id: 2, name: Category 1b, parent: 1, }, Category 2: { id: 3, name: Category 2, parent: 0, }, }, numberOfItems: 10, }; const MyQueryControls () { const [ query, setQuery ] useState( QUERY_DEFAULTS ); const { orderBy, order, selectedCategories, categories, numberOfItems } query; const updateQuery ( newQuery ) { setQuery( { ...query, ...newQuery } ); }; return ( QueryControls { ...{ orderBy, order, numberOfItems } } onOrderByChange{ ( newOrderBy ) updateQuery( { orderBy: newOrderBy } ) } onOrderChange{ ( newOrder ) updateQuery( { order: newOrder } ) } categorySuggestions{ categories } selectedCategories{ selectedCategories } onCategoryChange{ ( category ) updateQuery( { selectedCategories: category } ) } onNumberOfItemsChange{ ( newNumberOfItems ) updateQuery( { numberOfItems: newNumberOfItems } ) } / ); };与单选模式的关键差异在于分类列表的数据形态多选模式下categorySuggestions是一个以分类名为键的对象Record Category[ name ], Category 而不是数组。文档中特别强调“The format of the categories list also needs to be updated to match the expected type for the category suggestions.” 也就是说切换到多选模式时分类列表格式必须从数组改写为“名称映射”对象。从源码看index.tsx多选分支的渲染逻辑是isMultipleCategorySelection( props ) props.categorySuggestions props.onCategoryChange ( FormTokenField keyquery-controls-categories-select label{ __( Categories ) } value{ props.selectedCategories props.selectedCategories.map( ( item ) ( { id: item.id, // Keeping the fallback to item.value for legacy reasons value: item.name || item.value, } ) ) } suggestions{ Object.keys( props.categorySuggestions ) } onChange{ props.onCategoryChange } maxSuggestions{ MAX_CATEGORIES_SUGGESTIONS } / ),这里有三个值得注意的实现细节UI 载体是 FormTokenField多选分类渲染为标签输入框token fieldsuggestions取categorySuggestions对象的所有 keymaxSuggestions被硬编码为 20源码常量MAX_CATEGORIES_SUGGESTIONS 20即输入提示最多展示 20 条候选legacy 兼容token 的显示文案取item.name || item.value为旧数据中selectedCategories项上使用value字段的写法保留了回退路径两种模式通过类型守卫区分源码用两个类型守卫函数isSingleCategorySelection检测categoriesList in props和isMultipleCategorySelection检测categorySuggestions in props判断走哪个分支onCategoryChange的函数签名也随之不同——单选模式接收字符串形式的分类 ID多选模式接收 token 数组即FormTokenFieldProps[ onChange ]。Props 完整参考以下参数表完整继承自组件文档的 Props 章节并结合 types.ts 中的类型定义补充了说明。所有 prop 均为可选Required: No平台均为 Web。数据列表类Prop类型说明authorListAuthor[]可供选择的作者数组每项为{ id: number, name: string }categoriesListCategory[]分类数组。与onCategoryChange一起传入时渲染单选分类 UI。每项为{ id: number, name: string, parent: number }categorySuggestionsRecord Category[ name ], Category 以分类名为键的分类对象。与onCategoryChange一起传入时渲染多选分类 UI数量控制类Prop类型默认值说明maxItemsnumber100最大条目数作为“条目数量”滑块的上限minItemsnumber1最小条目数作为“条目数量”滑块的下限numberOfItemsnumber—当前选中要取回的条目数量默认值在源码中定义为常量index.tsxconst DEFAULT_MIN_ITEMS 1; const DEFAULT_MAX_ITEMS 100;回调类Prop签名说明onAuthorChange( newAuthor: string ) void接收新的作者值不指定时不渲染作者控件onCategoryChange( newCategory: string ) void或FormTokenFieldProps[ onChange ]接收新的分类值不指定时不渲染分类控件。函数签名随单选/多选模式不同而不同onNumberOfItemsChange( newNumber?: number ) void接收新的条目数量不指定时不渲染数量范围控件onOrderChange( newOrder: asc \| desc ) void接收新的排序方向与onOrderByChange二者缺任一即不渲染排序控件onOrderByChange( newOrderBy: date \| title ) void接收新的排序依据与onOrderChange二者缺任一即不渲染排序控件选中状态类Prop类型说明orderasc \| desc取回文章的排序方向orderBydate \| title \| menu_order排序依据的 meta 键orderByOptionsOrderByOption[]自定义排序选项列表覆盖默认选项selectedAuthorIdnumber当前选中的作者 IDselectedCategoriesCategory[]多选模式下当前选中的分类配合categorySuggestions使用selectedCategoryIdnumber单选模式下当前选中的分类配合categoriesList使用其中OrderByOption的类型定义types.ts如下export type OrderByOption { /** * The label to be shown to the user. */ label: string; /** * Option value passed to onChange when the option is selected. */ value: ${ OrderBy }/${ Order }; };value采用模板字面量类型约束必须是date/asc、date/desc、title/asc、title/desc、menu_order/asc、menu_order/desc这样的“orderBy/order”组合格式。排序控件的复合值机制排序控件的 UI 是一个下拉框但它同时驱动orderBy和order两个独立状态。从源码看index.tsx这个“一对二”的映射是这样完成的1. 默认选项列表不传orderByOptions时使用内置的四个选项const defaultOrderByOptions: OrderByOption[] [ { label: __( Newest to oldest ), value: date/desc }, { label: __( Oldest to newest ), value: date/asc }, { label: __( A → Z ), value: title/asc }, { label: __( Z → A ), value: title/desc }, ];2. 值的拼装下拉框的value由orderBy和order拼接而成若两者任一为undefined则整体显示为未选中状态value{ orderBy undefined || order undefined ? undefined : ${ orderBy }/${ order } }3. 变更时的拆分与差量回调用户选择新选项后onChange把date/desc这样的字符串按/拆回newOrderBy和newOrder然后只在各自发生变化时才调用对应的回调const [ newOrderBy, newOrder ] value.split( / ); if ( newOrder ! order ) { onOrderChange( newOrder as ... ); } if ( newOrderBy ! orderBy ) { onOrderByChange( newOrderBy as ... ); }这个差量设计对基于块 attributes 的编辑器场景很友好只有真正变化的字段会触发setAttributes避免不必要的 undo 历史记录。4. 渲染条件排序控件要求onOrderChange与onOrderByChange同时存在onOrderChange onOrderByChange (...)缺任何一个都不渲染与文档描述一致。分类树构建buildTermsTree 原理单选模式的分类下拉以及作者下拉都不是简单的列表而是树形选择器TreeSelect。扁平的{ id, name, parent }数组如何变成树答案在 terms.ts 的buildTermsTree函数中其算法分为三步第一步补全结构。把扁平数组映射为带children: []和parent的节点同时把数字id统一转为字符串因为 TreeSelect 使用字符串 IDconst flatTermsWithParentAndChildren: TermWithParentAndChildren[] flatTerms.map( ( term ) ( { children: [], parent: null, ...term, id: String( term.id ), } ) );第二步按 parent 分组。用reduce把节点按parent值聚合到termsByParent对象中前提是全部节点都有明确的parent由类型守卫ensureParentsAreDefined检查若不满足则直接返回未组装的扁平结果const termsByParent flatTermsWithParentAndChildren.reduce( ( acc: TermsByParent, term ) { const { parent } term; if ( ! acc[ parent ] ) { acc[ parent ] []; } acc[ parent ].push( term ); return acc; }, {} );第三步递归挂载子节点。从parent: 0的顶级节点出发递归地把每个节点对应的termsByParent[ term.id ]子列表填入childrenconst fillWithChildren ( terms ) { return terms.map( ( term ) { const children termsByParent[ term.id ]; return { ...term, children: children children.length ? fillWithChildren( children ) : [], }; } ); }; return fillWithChildren( termsByParent[ 0 ] || [] );这套算法同时服务于分类和作者两种场景作者列表只是没有层级关系的“树”。该行为由单元测试 test/terms.ts 覆盖包括四种情形无parent字段的输入原样返回并补上parent: null和空children全部为顶级节点parent: 0返回带空children的平铺数组存在子节点正确嵌套父子关系多分支混合多子节点与无子节点并存时结构正确。实战案例latest-posts 块的“排序与过滤”面板Gutenberg 仓库中 QueryControls 的一个真实使用方是 latest-posts 块的编辑组件edit.jsx。它把 QueryControls 放在“Sort and filter”工具面板项中且走的是多选分类模式QueryControls { ...{ order, orderBy } } numberOfItems{ postsToShow } onOrderChange{ ( value ) setAttributes( { order: value } ) } onOrderByChange{ ( value ) setAttributes( { orderBy: value } ) } onNumberOfItemsChange{ ( value ) setAttributes( { postsToShow: value } ) } categorySuggestions{ categorySuggestions } onCategoryChange{ selectCategories } selectedCategories{ categories } onAuthorChange{ ( value ) setAttributes( { selectedAuthor: ! value ? Number( value ) : undefined, } ) } authorList{ authorList ?? [] } selectedAuthorId{ selectedAuthor } /这个案例展示了几条有复用价值的实践经验回调直接映射到setAttributes块属性即查询状态天然获得 undo/redo 支持作者回调中处理了 TreeSelect “全部”选项的字符串空值 ! value ? Number( value ) : undefined即选择 “All authors” 时把作者筛选清除为undefined配套的ToolsPanelItem用hasValue判断当前查询是否偏离默认值order ! desc || orderBy ! date || postsToShow ! 5 || ...从而在面板上显示“已激活”的徽标onDeselect则负责一键重置回默认查询。组件的 Storybook 故事stories/index.story.tsx也同时演示了两种模式Default故事使用categorySuggestions多选模式并内置了作者列表数据SelectSingleCategory故事则展示categoriesList单选模式可作为交互行为的参考实现。选型建议与小结单选 vs 多选需要“某个分类下”的简单筛选时用categoriesListselectedCategoryId渲染为带层级的 TreeSelect需要“多个分类联合筛选”时用categorySuggestions名称键控对象selectedCategories渲染为 FormTokenField提示候选上限 20 条。两者通过传入的 prop 名自动切换不可混用最小可用组合只传排序相关 prop 时组件仅显示一个 Order by 下拉框数量控件需要onNumberOfItemsChange才会出现其上下界由minItems/maxItems约束默认 1~100自定义排序项通过orderByOptions可注入任意orderBy/order组合如基于menu_order的排序value必须符合${orderBy}/${order}格式类型注意selectedCategoryId是 number 而内部 TreeSelect 用 string IDonCategoryChange单选与onAuthorChange收到的都是字符串需要数字时要自行转换。综合来看QueryControls 的价值在于把“查询文章”这一高频交互模式封装为声明式组件父组件只需管理查询状态对象并挂接回调控件的显隐、排序值的拆分与重组、分类树的构建全部由组件内部完成。其源码位于 packages/components/src/query-controls/配合组件文档 README 可进一步深入每个子组件TreeSelect、FormTokenField、RangeControl的实现细节。【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考