EP156. “用 useSelect 在编辑器里查询文章列表”
🔒 登录后可标记已读把下拉框里硬编码的 1/2/3 假选项,换成真正从数据库查出来的全部 Professor 文章列表。这门课之前学过用 JS 发送异步请求打自定义 REST API 端点来取数据,这一讲介绍一个专门为「编辑器里加载数据」量身定做的新工具:@wordpress/data 提供的 useSelect Hook——本质上是对 wp.data.select("core").getEntityRecords(...) 这套 WordPress 内建数据仓库查询能力的 React 封装,帮你自动处理「请求还没返回时先返回 undefined、请求完成后自动重新渲染」这个异步等待的过程,不需要自己手写 loading 状态或者发 fetch 请求。
涉及文件
wp-content/plugins/featured-professor/src/index.js(修改)
代码实现
先在浏览器控制台手动试验(不是写进代码,只是验证工具用法):
wp.data.select("core").getEntityRecords("postType", "professor", {per_page: -1})
src/index.js:改用 useSelect 查询全部 Professor 文章,动态生成下拉选项:
import "./index.scss"
import {useSelect} from "@wordpress/data"
wp.blocks.registerBlockType("ourplugin/featured-professor", {
title: "Professor Callout",
description: "Include a short description and link to a professor of your choice",
icon: "welcome-learn-more",
category: "common",
attributes: {
profId: {type: "string"}
},
edit: EditComponent,
save: function () {
return null
}
})
function EditComponent(props) {
const allProfs = useSelect(select => {
return select("core").getEntityRecords("postType", "professor", {per_page: -1})
})
if (allProfs == undefined) return <p>Loading...</p>
return (
<div className="featured-professor-wrapper">
<div className="professor-select-container">
<select onChange={e => props.setAttributes({profId: e.target.value})}>
<option value="">Select a professor</option>
{allProfs.map(prof => {
return (
<option value={prof.id} selected={props.attributes.profId == prof.id}>
{prof.title.rendered}
</option>
)
})}
</select>
</div>
<div>
The HTML preview of the selected professor will appear here.
</div>
</div>
)
}
关键改动点:
- 在浏览器控制台先手动跑一遍
wp.data.select("core").getEntityRecords(...):这是 WordPress 编辑器全局就带着的数据仓库工具,不需要额外安装或引入——select("core")拿到「核心数据」这个仓库,.getEntityRecords(实体类型, 具体类型名, 查询参数)负责发请求查文章列表。第一个参数固定是"postType"(查的是某种文章类型),第二个参数是具体的文章类型 slug(这里是"professor"),第三个参数是查询选项对象——{per_page: -1}表示一次性拿全部结果,不分页限制成默认的 5/10 篇 - 第一次在控制台执行会立刻返回
null——因为这是一个异步网络请求,执行的瞬间数据还没回来;再按一次上箭头重新执行,这时候数据已经到了,会返回一个包含全部 Professor 文章对象的数组 useSelect:从@wordpress/data导入的 React Hook,把上面控制台里手写的那套查询逻辑包装成一个能在组件里直接用的工具——传给它一个函数(这里参数命名为select,含义是「这是给你用来查数据的工具」),函数体内return的值,就是useSelect()调用后拿到的结果useSelect(select => { return select("core").getEntityRecords(...) }):函数体内的写法跟控制台里试验的代码几乎一模一样,只是把wp.data.select换成了参数名selectuseSelect自动处理异步等待:请求还没返回时,allProfs的值是undefined;一旦 WordPress 侦测到底层数据仓库真正拿到了数据,会自动重新调用一次这个函数、组件重新渲染,allProfs就变成真正的文章数组——不需要自己手写useState/useEffect去管理这个「加载中 → 加载完成」的过程if (allProfs == undefined) return <p>Loading...</p>:函数组件里提前return,后面的代码(包括真正的下拉框 JSX)就不会执行——这是个通用的编程技巧,不只是 React 独有:只要函数执行到return,函数体后面的语句就不会再运行。效果是页面刚加载的一瞬间会显示「Loading...」,数据一到马上换成真正的下拉框allProfs.map(prof => {...}):拿到的allProfs是一个普通数组,用数组的.map()方法遍历每一个 Professor 文章对象、为每个对象生成一个<option><option value={prof.id} selected={props.attributes.profId == prof.id}>:prof.id是这篇 Professor 文章的文章 ID(数字),跟属性profId(存的是字符串)用==宽松相等比较——JS 的==会自动做类型转换,所以数字id和字符串profId依然能正确比较{prof.title.rendered}:WordPress REST/data 仓库返回的文章对象,标题字段是一个嵌套对象title: {rendered: "..."},要取.rendered才是真正渲染好的标题文字(这是 WordPress 数据结构的通用规律,因为title底下理论上还可能有raw、protected等其他字段)- 调试时用过一行
console.log(allProfs)打印出来看数据结构长什么样(比如确认id是小写、title.rendered这层嵌套关系)——这是开发过程中的临时验证手段,最终代码里不需要保留
[截图:Gutenberg 编辑器里下拉框已改成真正从数据库查出来的全部 Professor 姓名列表]
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
wp.data.select("core").getEntityRecords(类型, 具体类型名, 参数) | WordPress 全局数据 API | 查询指定文章类型的全部/部分文章,返回文章对象数组 |
useSelect(callback) | @wordpress/data React Hook | 把 wp.data.select 查询包装成 React 组件可用的形式,自动处理异步加载状态 |
array.map(callback) | JS 数组内建方法 | 遍历数组,为每个元素生成新的值(这里是生成一组 <option> JSX) |
常见坑
- 以为
wp.data.select(...).getEntityRecords(...)执行完立刻就能拿到数据——它是异步的,第一次调用几乎必然先拿到null/undefined,得等网络请求真正完成后才有真实数据 - 忘记处理「数据还没加载完」这个中间状态,直接对
allProfs调用.map()——如果allProfs还是undefined,调用undefined.map()会直接报错导致整个 Block 崩溃,一定要先判断加载完成再渲染依赖这份数据的内容 - 读取文章标题时忘记多一层
.rendered,直接用prof.title——拿到的是一个对象而不是字符串,渲染出来会不对 getEntityRecords不传{per_page: -1}——默认只会拿到前面几篇(分页限制),如果 Professor 文章数量超过默认分页大小,下拉框会缺项
延伸 / 后续讲座会用到
下一讲开始做前台部分:根据存下来的 profId 去查询那篇 Professor 文章、渲染出真正的推荐卡片内容。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 26, EP156