EP224. “用 create-block 脚手架起步,迁移编辑器端旧代码”
🔒 登录后可标记已读用 WordPress 官方脚手架工具 @wordpress/create-block 的 interactive 模板生成一个全新插件(interactivity-quiz),先只搞定编辑器(后台)那一侧——把旧版「Are You Paying Attention」插件里已经写好、验证过的编辑器 JSX(问题输入框、答案列表、加星标记正确答案、颜色选择器、对齐工具栏)原样迁移过来。这一讲不碰任何 Interactivity API 本身的新概念,纯粹是「新脚手架 + 旧编辑器代码」的缝合工作,为下一讲正式动手写前台交互打好地基。
涉及文件
wp-content/plugins/interactivity-quiz/(新建,由脚手架工具生成)wp-content/plugins/interactivity-quiz/src/block.json(修改,补上attributes)wp-content/plugins/interactivity-quiz/src/edit.js(修改,替换成旧版编辑器 JSX)wp-content/plugins/interactivity-quiz/src/style.scss/src/editor.scss(修改,清理脚手架自带的占位样式,搬入旧版 CSS)wp-content/plugins/interactivity-quiz/package.json(修改,新增react-color依赖)
代码实现
终端命令:用官方脚手架生成新插件:
cd 你的WordPress安装目录/wp-content/plugins
npm create @wordpress/create-block@latest interactivity-quiz -- --template @wordpress/create-block/interactive-template
src/block.json(补上从旧插件搬运的 attributes,其余字段是脚手架自动生成):
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "create-block/interactivity-quiz",
"version": "0.1.0",
"title": "Interactivity Quiz",
"category": "widgets",
"attributes": {
"question": { "type": "string" },
"answers": { "type": "array", "default": [""] },
"correctAnswer": { "type": "number", "default": "" },
"bgColor": { "type": "string", "default": "#EBEBEB" },
"theAlignment": { "type": "string", "default": "left" }
},
"icon": "media-interactive",
"description": "An interactive block with the Interactivity API",
"example": {},
"supports": {
"interactivity": true
},
"textdomain": "interactivity-quiz",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php",
"viewScriptModule": "file:./view.js"
}
src/edit.js(完整文件,编辑器 JSX 从旧版「Are You Paying Attention」插件的 index.js 搬运,只调整了外层包装 div 与 useBlockProps):
import {
TextControl,
Flex,
FlexBlock,
FlexItem,
Button,
Icon,
PanelBody,
PanelRow,
ColorPicker
} from "@wordpress/components"
import { InspectorControls, BlockControls, AlignmentToolbar } from "@wordpress/block-editor"
import { ChromePicker } from "react-color"
import { __ } from "@wordpress/i18n"
import { useBlockProps } from "@wordpress/block-editor"
export default function Edit(props) {
const blockProps = useBlockProps()
function updateQuestion(value) {
props.setAttributes({ question: value })
}
function deleteAnswer(indexToDelete) {
const newAnswers = props.attributes.answers.filter(function (x, index) {
return index != indexToDelete
})
props.setAttributes({ answers: newAnswers })
if (indexToDelete == props.attributes.correctAnswer) {
props.setAttributes({ correctAnswer: undefined })
}
}
function markAsCorrect(index) {
props.setAttributes({ correctAnswer: index })
}
return (
<div {...blockProps}>
<div
className="paying-attention-edit-block"
style={{ backgroundColor: props.attributes.bgColor }}
>
<BlockControls>
<AlignmentToolbar
value={props.attributes.theAlignment}
onChange={x => props.setAttributes({ theAlignment: x })}
/>
</BlockControls>
<InspectorControls>
<PanelBody title="Background Color" initialOpen={true}>
<PanelRow>
<ChromePicker
color={props.attributes.bgColor}
onChangeComplete={x => props.setAttributes({ bgColor: x.hex })}
disableAlpha={true}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
<TextControl
label="Question:"
value={props.attributes.question}
onChange={updateQuestion}
style={{ fontSize: "20px" }}
/>
<p style={{ fontSize: "13px", margin: "20px 0 8px 0" }}>Answers:</p>
{props.attributes.answers.map(function (answer, index) {
return (
<Flex>
<FlexBlock>
<TextControl
autoFocus={answer == undefined}
value={answer}
onChange={newValue => {
const newAnswers = props.attributes.answers.concat([])
newAnswers[index] = newValue
props.setAttributes({ answers: newAnswers })
}}
/>
</FlexBlock>
<FlexItem>
<Button onClick={() => markAsCorrect(index)}>
<Icon
className="mark-as-correct"
icon={props.attributes.correctAnswer == index ? "star-filled" : "star-empty"}
/>
</Button>
</FlexItem>
<FlexItem>
<Button isLink className="attention-delete" onClick={() => deleteAnswer(index)}>
Delete
</Button>
</FlexItem>
</Flex>
)
})}
<Button
isPrimary
onClick={() => {
props.setAttributes({ answers: props.attributes.answers.concat([undefined]) })
}}
>
Add another answer
</Button>
</div>
</div>
)
}
关键改动点:
@wordpress/create-block是官方脚手架工具:跟社区里各种第三方脚手架相比,作者特别偏好这个官方包——npm create @wordpress/create-block@latest 文件夹名 -- --template @wordpress/create-block/interactive-template这条命令会在当前目录(要先cd到 WordPress 的wp-content/plugins/目录)生成一个全新插件,--template参数指定用哪个「蓝图」,这里选的interactive-template专门针对 Interactivity API 场景预置了合适的文件结构- 脚手架自动生成的插件已经能直接激活使用:生成后会自动跑一次
npm install,激活插件、在编辑器里插入这个 Block,能看到一个「Toggle」按钮和一段可以展开/收起的占位文字——这就是官方模板自带的最小 Interactivity API 演示 - 这一讲的核心工作是「移植」,不是「新写」:把旧插件(Block Theme 章节前那个「Are You Paying Attention」插件)
src/index.js里已经调试好的编辑器 JSX,原样迁移到新插件的src/edit.js——包括三个import行(组件、Block 编辑器工具、自定义颜色选择器)、return的整段 JSX、以及 JSX 之上定义的几个处理函数(updateQuestion/deleteAnswer/markAsCorrect) - 新旧两套代码「拿 props 的方式」不同,需要统一:脚手架自动生成的新代码习惯用解构(
export default function Edit({attributes, setAttributes})),而旧代码里到处都是props.attributes/props.setAttributes这种写法——为了让搬运过来的旧代码不用逐行修改,直接把Edit函数的参数改回不解构的props,两种写法本身没有优劣之分,纯粹是为了跟已有代码兼容 attributes必须同步搬进block.json:光搬 JSX 代码不够,block.json里如果没有声明answers这个属性(默认值是空数组),JSX 里对props.attributes.answers.map(...)调用就会因为answers是undefined而报错——这是这一讲刻意展示的一个真实报错案例:先让代码跑起来、根据控制台报错「无法在undefined上调用.map」反推出问题在哪- JSON 语法要求所有属性名/字符串值都要加双引号:跟这门课其他
block.json迁移场景(Block Theme 章节)完全一致的坑——JS 对象字面量可以省略引号、写undefined,纯 JSON 不行,correctAnswer的默认值只能用空字符串""表示,不能写undefined - 加上
<div {...blockProps}>外层包装解决「点击 Block 没有选中反馈」的问题:跟 Block Theme 章节反复出现的坑一样——apiVersion: 3下必须手动接管选中态外壳,不加的话点击 Block 不会显示蓝色边框、右侧检查器面板也不会正确联动 - 清理脚手架自带的占位样式,替换成旧插件的 CSS:
style.scss(前后台都生效)删除脚手架默认的浅褐色内边距样式,改用旧插件的index.css全部内容;editor.scss(只在编辑器生效)同样清理默认样式,只保留必要的少量调整(比如给 Block 补一点底部外边距) - 必须停用旧插件,确认新插件完全自给自足:迁移过程中一度发现新 Block 的星标图标(
dashicons)等资源加载不正常——排查后发现是因为旧插件还处于激活状态、意外提供了这些资源;停用旧插件后新插件必须自己把所有需要的 CSS/图标资源加载完整,确保不依赖任何旧插件残留的副作用 react-color依赖需要重新npm install:新插件是全新的package.json,之前在旧插件里装过的第三方包不会自动带过来,颜色选择器要用到的ChromePicker组件需要重新执行一次npm install react-color- 验证到此为止的成果:编辑器里可以正常填写题目、增删答案、标记正确答案、选背景色、选对齐方式,数据也能正确存取——但前台目前还是脚手架自带的占位「Toggle」交互,跟 Quiz 功能完全无关,真正用 Interactivity API 实现前台判分逻辑是下一讲的内容
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
@wordpress/create-block(interactive-template) | 官方脚手架工具 | 生成预置 Interactivity API 文件结构的全新插件 |
block.json 的 "supports": {"interactivity": true} | 配置项 | 声明这个 Block 使用 Interactivity API |
block.json 的 viewScriptModule | 配置项 | 声明前台要加载的 JS 模块文件(Interactivity API 的标准入口,下一讲展开) |
常见坑
- 迁移编辑器 JSX 后忘记同步把
attributes也搬进block.json——JSX 里引用某个属性(比如answers)时会因为它是undefined而报错(比如对undefined调用.map()) block.json里沿用 JS 写法(省略引号、写undefined)——JSON 语法不允许,必须全部用双引号包住属性名和字符串值,undefined要改成空字符串等合法 JSON 值- 忘记给新 Block 加
<div {...blockProps}>外层包装——apiVersion: 3下点击 Block 不会显示选中边框 - 保留旧插件处于激活状态、依赖它残留提供某些 CSS/图标资源——新插件应该自己独立提供所有需要的资源,不能依赖别的插件恰好也装着
[截图:新插件 Interactivity Quiz 的编辑器界面,问题输入框、答案列表、星标标记、背景色选择器、对齐工具栏全部正常工作的效果]
延伸 / 后续讲座会用到
下一讲正式开始学习 Interactivity API 本身的核心概念,用它重新实现 Quiz Block 的前台判分交互。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 30, EP224