WP DEVELOP

EP224. “用 create-block 脚手架起步,迁移编辑器端旧代码”

首页 WordPress 开发课程 INTERACTIVITY API · EP224
约 26 分钟· #EP224#INTERACTIVITY API
🔒 登录后可标记已读

用 WordPress 官方脚手架工具 @wordpress/create-blockinteractive 模板生成一个全新插件(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(...) 调用就会因为 answersundefined 而报错——这是这一讲刻意展示的一个真实报错案例:先让代码跑起来、根据控制台报错「无法在 undefined 上调用 .map」反推出问题在哪
  • JSON 语法要求所有属性名/字符串值都要加双引号:跟这门课其他 block.json 迁移场景(Block Theme 章节)完全一致的坑——JS 对象字面量可以省略引号、写 undefined,纯 JSON 不行,correctAnswer 的默认值只能用空字符串 "" 表示,不能写 undefined
  • 加上 <div {...blockProps}> 外层包装解决「点击 Block 没有选中反馈」的问题:跟 Block Theme 章节反复出现的坑一样——apiVersion: 3 下必须手动接管选中态外壳,不加的话点击 Block 不会显示蓝色边框、右侧检查器面板也不会正确联动
  • 清理脚手架自带的占位样式,替换成旧插件的 CSSstyle.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-blockinteractive-template官方脚手架工具生成预置 Interactivity API 文件结构的全新插件
block.json"supports": {"interactivity": true}配置项声明这个 Block 使用 Interactivity API
block.jsonviewScriptModule配置项声明前台要加载的 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