WP DEVELOP

EP220. “迁移 Slide 与 Slideshow:复制近似 Block 起步”

首页 WordPress 开发课程 现代化 BLOCK 开发标准做法 · EP220
约 19 分钟· #EP220#现代化 BLOCK 开发标准做法
🔒 登录后可标记已读

延续同一套迁移套路,把 Slide、Slideshow 两个 Block 也搬到新的 block.json 标准做法——这一讲进一步验证了一个高效技巧:不是每次都从最简单的占位 Block 复制,而是复制「结构最相似的那个已迁移 Block」(Slide 复制自刚迁移完的 Banner,因为两者都有背景图选择器;Slideshow 复制自 Slide,因为两者都是简单的 InnerBlocks 容器),能省下大量重复劳动。这一讲也再次印证「先跑起来看报错」的调试节奏——反复出现「忘记 import useBlockProps」这个同一类小错误,靠浏览器控制台报错信息迅速定位修复,比试图一次写对更省心。


涉及文件

  • wp-content/themes/fictional-clean-blocks/functions.php (修改,slide/slideshow 从旧 JSXBlock 切到 register_block_type_from_metadata
  • wp-content/themes/fictional-clean-blocks/src/slide/ 整套(新建,复制自 banner/
  • wp-content/themes/fictional-clean-blocks/src/slideshow/ 整套(新建,复制自 slide/
  • 删除旧的 our-blocks/slide.js/slide.phpslideshow.js/slideshow.php

代码实现

src/slide/block.json(复制自 banner/block.json 再改,新增 themeimage 属性)

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "ourblocktheme/slide",
  "title": "Fictional University Slide",
  "supports": {
    "align": ["full"]
  },
  "attributes": {
    "themeimage": { "type": "string" },
    "align": { "type": "string", "default": "full" },
    "imgID": { "type": "number" },
    "imgURL": { "type": "string" }
  },
  "editorScript": "file:./index.js",
  "render": "file:./render.php"
}

src/slide/edit.js(完整文件,从旧的 our-blocks/slide.js 搬运,两处默认图回退逻辑都改用 ourThemeData.themePath

import apiFetch from "@wordpress/api-fetch"
import { Button, PanelBody, PanelRow } from "@wordpress/components"
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  MediaUpload,
  MediaUploadCheck
} from "@wordpress/block-editor"
import { useEffect } from "@wordpress/element"

export default function Edit(props) {
  const blockProps = useBlockProps()

  useEffect(function () {
    if (props.attributes.themeimage) {
      props.setAttributes({
        imgURL: `${ourThemeData.themePath}/images/${props.attributes.themeimage}`
      })
    }
    if (!props.attributes.themeimage && !props.attributes.imgURL) {
      props.setAttributes({ imgURL: `${ourThemeData.themePath}/images/library-hero.jpg` })
    }
  }, [])

  useEffect(
    function () {
      if (props.attributes.imgID) {
        async function go() {
          const response = await apiFetch({
            path: `/wp/v2/media/${props.attributes.imgID}`,
            method: "GET"
          })
          props.setAttributes({
            themeimage: "",
            imgURL: response.media_details.sizes.pageBanner.source_url
          })
        }
        go()
      }
    },
    [props.attributes.imgID]
  )

  function onFileSelect(x) {
    props.setAttributes({ imgID: x.id })
  }

  return (
    <>
      <InspectorControls>
        <PanelBody title="Background" initialOpen={true}>
          <PanelRow>
            <MediaUploadCheck>
              <MediaUpload
                onSelect={onFileSelect}
                value={props.attributes.imgID}
                render={({ open }) => {
                  return <Button onClick={open}>Choose Image</Button>
                }}
              />
            </MediaUploadCheck>
          </PanelRow>
        </PanelBody>
      </InspectorControls>

      <div {...blockProps}>
        <div
          className="hero-slider__slide"
          style={{ backgroundImage: `url('${props.attributes.imgURL}')` }}
        >
          <div className="hero-slider__interior container">
            <div className="hero-slider__overlay t-center">
              <InnerBlocks
                allowedBlocks={["ourblocktheme/genericheading", "ourblocktheme/genericbutton"]}
              />
            </div>
          </div>
        </div>
      </div>
    </>
  )
}

src/slideshow/edit.js(完整文件,比 Slide 简单得多,只是一层 InnerBlocks 容器)

import { InnerBlocks, useBlockProps } from "@wordpress/block-editor"

export default function Edit(props) {
  const blockProps = useBlockProps()

  return (
    <div {...blockProps}>
      <div style={{ backgroundColor: "#333", padding: "35px" }}>
        <p style={{ textAlign: "center", fontSize: "20px", color: "#FFF" }}>Slideshow</p>
        <InnerBlocks allowedBlocks={["ourblocktheme/slide"]} />
      </div>
    </div>
  )
}

关键改动点:

  • 迁移顺序按「结构相似度」选复制源,不是每次都从头搭:Slide 几乎和 Banner 一模一样(都要背景图选择器 + 只允许嵌套标题/按钮),直接复制 banner/ 整个文件夹改名字比重新搭快得多;Slideshow 内容比 Slide 简单很多(没有背景图,只是个容器),但复制 slide/ 依然比复制 banner/ 省事——只需要删掉不需要的部分,而不是从零补充
  • block.jsonattributes 要跟着实际需求增减:Slide 比 Banner 多了 themeimage 属性(模板文件里给幻灯片指定默认示意图的机制,Block Theme 章节 EP205-207 学过);Slideshow 则完全不需要 imgID/imgURL/themeimage 这几个跟背景图相关的属性,block.json 里直接删掉这些不需要的字段
  • themeimage 相关的两层回退逻辑,写法上跟旧版本本质一致,只是数据来源变了:原本旧版本靠 JSXBlock 构造函数第三参数注入的 slide.themeimagepath,现在统一用 EP219 建立的全局变量 ourThemeData.themePath——两层判断逻辑(有 themeimage 就拼主题目录里的对应文件;themeimageimgURL 都没有就退回通用的 library-hero.jpg)完全照搬,只改了变量名
  • 多次遇到「忘记导入 useBlockProps」的报错apiVersion: 3 强制要求手动接管选中态外壳,这个 Hook 但凡漏导入就会在控制台报「useBlockProps 未定义」——这一讲连续在 Slide 和 Slideshow 上都踩到同样的错,每次都是靠「刷新页面 → 打开控制台 → 看报错信息 → 找到漏导入的那一行」这套流程快速定位,印证了「不追求一次写对,靠报错信息迭代」是更实际的工作节奏
  • 需要额外补一层 <div {...blockProps}> 包装外壳时的位置技巧:如果原本借用的旧代码本身已经有一个「看起来适合当外壳」的最外层 <div>,可以直接在这个 <div> 上加 {...blockProps};但像 Slide 这种最外层其实是 InspectorControls(不适合当选中态外壳的元素)打头的场景,需要额外单独包一层新的 <div {...blockProps}> 才行,不能图省事直接套在语义不合适的元素上
  • save 函数完全不用改动:只要 Block 本身是靠 InnerBlocks.Content 保存内容(Slide/Slideshow 都是),复制过来的 index.js 里这部分代码原封不动就能用,不需要额外调整
  • 验证嵌套功能仍然正常:迁移完成后重新测试「往 Slideshow 里插入第 4 张 Slide、往 Slide 里插入标题和按钮」,确认整条嵌套链路(Slideshow → Slide → 标题/按钮)没有因为迁移而损坏

Hook / Function 速查

名称类型用途
(复用 EP217-219 已介绍过的工具,本讲无新增)

常见坑

  • 从零开始逐行重写相似的 Block,而不是先判断「有没有更接近的已迁移 Block 可以复制」——浪费大量重复劳动
  • 忘记在新文件里导入 useBlockProps——apiVersion: 3 下漏了这个 Hook 会导致编辑器直接报错,且很容易在复制粘贴时被忽略
  • {...blockProps} 加在语义不合适的中间层元素上(比如 InspectorControls 或者应该保持纯样式作用的内层 div),而不是新建一个专门的最外层包装 <div>
  • 迁移完 Block 后忘记同时验证「嵌套」链路(Slideshow 里插 Slide、Slide 里插标题/按钮)依然正常工作,只测试了单个 Block 本身

延伸 / 后续讲座会用到

下一讲要迁移标题(genericheading)和按钮(genericbutton),这两个不需要 render.php 文件,作者预告会「更快更简单」。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 29, EP220