EP190-191. “ColorPalette 颜色选择器与「存名字不存色值」的抽象层”
🔒 登录后可标记已读📌 说明:EP190 的 transcript 在「Let me explain what I mean.」这句话结束,EP191 开头是同一句话的重复接续——两者是同一段实操内容被切成两个文件,合并成一篇笔记。
按钮 Block 这一讲轮到「选颜色」功能,并顺手修了一个上一讲遗留的小 bug(没选链接时按钮整个消失)。核心是用 WordPress 内建的 ColorPalette 组件在右侧检查器面板提供蓝/橙/深橙三个可选颜色。这一讲后半段是作者反复强调的一个设计哲学:数据库里存的应该是「颜色的名字」(比如 "blue"),而不是「颜色的十六进制值」(比如 "#0d3b66")——这样以后设计团队想调整「蓝色」具体是哪个色号,只需要改一处 CSS class 定义,不用满数据库找出所有存了旧色号的记录去替换。为了在「用户点击的是色值」和「要存的是名字」之间转换,用到 WordPress 提供的 getColorObjectByColorValue() 反查工具函数。最后把颜色列表抽成独立文件 inc/ourColors.js,方便以后被多个 Block 复用。
涉及文件
wp-content/themes/fictional-university-block-theme/our-blocks/genericbutton.js(修改)wp-content/themes/fictional-university-block-theme/inc/ourColors.js(新建)
代码实现
inc/ourColors.js(新建,抽出来方便复用):
const ourColors = [
{ name: "blue", color: "#0d3b66" },
{ name: "orange", color: "#ee964b" },
{ name: "dark-orange", color: "#f95738" }
]
export default ourColors
our-blocks/genericbutton.js(完整文件):
import ourColors from "../inc/ourColors"
import { link } from "@wordpress/icons"
import { ToolbarGroup, ToolbarButton, Popover, Button, PanelBody, PanelRow, ColorPalette } from "@wordpress/components"
import { RichText, InspectorControls, BlockControls, __experimentalLinkControl as LinkControl, getColorObjectByColorValue } from "@wordpress/block-editor"
import { registerBlockType } from "@wordpress/blocks"
import { useState } from "@wordpress/element"
registerBlockType("ourblocktheme/genericbutton", {
title: "Generic Button",
attributes: {
text: { type: "string" },
size: { type: "string", default: "large" },
linkObject: { type: "object", default: { url: "" } },
colorName: { type: "string", default: "blue" }
},
edit: EditComponent,
save: SaveComponent
})
function EditComponent(props) {
const [isLinkPickerVisible, setIsLinkPickerVisible] = useState(false)
function handleTextChange(x) {
props.setAttributes({ text: x })
}
function buttonHandler() {
setIsLinkPickerVisible(prev => !prev)
}
function handleLinkChange(newLink) {
props.setAttributes({ linkObject: newLink })
}
const currentColorValue = ourColors.filter(color => {
return color.name == props.attributes.colorName
})[0].color
function handleColorChange(colorCode) {
// from the hex value that the color palette gives us, we need to find its color name
const { name } = getColorObjectByColorValue(ourColors, colorCode)
props.setAttributes({ colorName: name })
}
return (
<>
<BlockControls>
<ToolbarGroup>
<ToolbarButton onClick={buttonHandler} icon={link} />
</ToolbarGroup>
<ToolbarGroup>
<ToolbarButton isPressed={props.attributes.size === "large"} onClick={() => props.setAttributes({ size: "large" })}>
Large
</ToolbarButton>
<ToolbarButton isPressed={props.attributes.size === "medium"} onClick={() => props.setAttributes({ size: "medium" })}>
Medium
</ToolbarButton>
<ToolbarButton isPressed={props.attributes.size === "small"} onClick={() => props.setAttributes({ size: "small" })}>
Small
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
<InspectorControls>
<PanelBody title="Color" initialOpen={true}>
<PanelRow>
<ColorPalette disableCustomColors={true} clearable={false} colors={ourColors} value={currentColorValue} onChange={handleColorChange} />
</PanelRow>
</PanelBody>
</InspectorControls>
<RichText allowedFormats={[]} tagName="a" className={`btn btn--${props.attributes.size} btn--${props.attributes.colorName}`} value={props.attributes.text} onChange={handleTextChange} />
{isLinkPickerVisible && (
<Popover position="middle center">
<LinkControl settings={[]} value={props.attributes.linkObject} onChange={handleLinkChange} />
<Button variant="primary" onClick={() => setIsLinkPickerVisible(false)} style={{ display: "block", width: "100%" }}>
Confirm Link
</Button>
</Popover>
)}
</>
)
}
function SaveComponent(props) {
return (
<a href={props.attributes.linkObject.url} className={`btn btn--${props.attributes.size} btn--${props.attributes.colorName}`}>
{props.attributes.text}
</a>
)
}
关键改动点:
- 先修一个 bug:
linkObject补默认值:linkObject: {type: "object", default: {url: ""}}——之前没有默认值时,如果用户插入按钮却没点链接图标选一个链接,save组件里的props.attributes.linkObject.url会因为linkObject整个是undefined而报错,导致整个按钮在前台完全不显示;给一个带空字符串url的默认对象,就能保证「哪怕没选链接,按钮本身依然正常显示,只是点了没反应」 ColorPalette(@wordpress/components):WordPress 内建的颜色选择器组件,效果类似一排小圆点色块供用户点选——放进InspectorControls+PanelBody(标题「Color」)+PanelRow这套右侧检查器面板的标准写法里(跟 Featured Professor 章节学过的模式一致)disableCustomColors={true}+clearable={false}——这才是作者选择自己写颜色选择器、而不是用theme.json调色板的真正原因:disableCustomColors关掉「点击色块旁边的加号自己挑一个任意颜色」的功能,clearable关掉「清除已选颜色」的选项——效果是终端用户只能从预先设定好的几个颜色里选,不能自定义任何颜色。作者提到theme.json的调色板机制目前没有办法做到这种「完全锁死不给end user 覆盖空间」的效果,所以选颜色这个功能宁愿自己在 Block 代码里手写- 核心设计哲学:数据库存「颜色名字」,不存「颜色色值」:
colorName: {type: "string", default: "blue"}——属性存的是"blue"/"orange"/"dark-orange"这样的名字字符串,不是"#0d3b66"这样的十六进制色值。作者给的理由:色值只是给用户在选择器界面上「看一眼大概是什么颜色」用的预览手段,真正决定「这个名字对应的颜色到底长什么样」的权威定义应该只存在于 CSS class(btn--blue等)里;如果反过来把色值存进数据库,以后设计团队想统一微调「蓝色」的具体色号,就得把数据库里存过的所有旧色值挨个找出来替换,而存名字的话只需要改一处 CSS 定义就全站生效 ColorPalette组件本身只认色值,不认名字:这就是为什么需要两个转换方向的胶水代码——- 色值 → 名字(用户点击某个颜色时触发):
getColorObjectByColorValue(ourColors, colorCode)是 WordPress 提供的官方工具函数(从@wordpress/block-editor导入),传入「颜色列表」和「用户点的色值」,返回匹配的那个颜色对象;用解构const {name} = getColorObjectByColorValue(...)直接取出name字段,存进colorName属性 - 名字 → 色值(
ColorPalette需要用色值来正确高亮显示"当前选中的是哪个"):官方没有提供反过来的工具函数,作者自己用数组.filter()写:ourColors.filter(color => color.name == props.attributes.colorName)[0].color——遍历颜色数组,筛出名字匹配当前colorName的那一项(结果数组只会有一项),取它的.color字段
- 色值 → 名字(用户点击某个颜色时触发):
className从写死的btn--blue换成动态拼接 `btn--${props.attributes.colorName}:edit(RichText)和save(最终<a>` 标签)两处都要同步改- 颜色列表抽成独立文件
inc/ourColors.js:用export default ourColors导出、在需要的 Block 文件里import ourColors from "../inc/ourColors"引入——这样如果以后有更多 Block(不只是按钮)也需要用到同一套品牌色,不用在每个文件里各自重复定义一遍 - 改动
save输出结构后,已插入的旧 Block 实例会再次出现「区块已被修改」的冲突提示:这是这几讲反复遇到的问题(JS 端的save函数决定的输出内容一旦变化,跟数据库里存的旧版本对不上就会报冲突),作者借这个机会又一次预告:马上要学的 PHPrender_callback方式能从根本上避免这个烦恼
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
ColorPalette(@wordpress/components) | React 组件 | 提供一组预设颜色供用户点选,disableCustomColors/clearable 控制是否允许自定义/清除 |
getColorObjectByColorValue(颜色数组, 色值)(@wordpress/block-editor) | WP 官方工具函数 | 根据色值反查对应的颜色对象(含名字),官方没有提供反方向(名字→色值)的对应函数 |
常见坑
- 给对象类型的属性(如
linkObject)不设默认值——用户没有主动设置这个属性时,save函数里访问它的子字段会报错,导致整个 Block 在前台消失 - 数据库直接存十六进制颜色色值而不是颜色名字——以后统一调整某个品牌色时,没法一次性生效,需要挨个查找替换所有旧记录
- 忘记同时处理「色值→名字」和「名字→色值」两个方向的转换——
ColorPalette组件的value需要色值才能正确高亮当前选中项,onChange拿到的却是色值需要转成名字存起来,两个方向缺一个都会导致选择器要么不能正确回显、要么存错数据 - 只用
theme.json的调色板机制却指望能完全禁止终端用户自定义颜色——录课当时的theme.json机制无法做到这一点,需要终端用户完全不能覆盖颜色选项时应该像这里一样自己写 Block 代码
[截图:点击按钮 Block 后右侧检查器面板的 Color 分组,展开显示蓝/橙/深橙三个色块的 ColorPalette 选择器]
延伸 / 后续讲座会用到
下一讲要回到 Banner Block,学习作者更推荐的 PHP render_callback 方式,同时加上「上传/选择背景图」的功能。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 28, EP190, EP191