如何创建 WordPress 子主题
创建WordPress子主题,让自定义内容在父主题更新后得以保留。了解经典主题与区块主题的设置、样式加载、模板覆盖,以及何时应改用插件。
要创建 WordPress 子主题,只需在 wp-content/themes 下新建一个文件夹,在其中放入一个 style.css 文件,并确保其 Template 头部字段与父主题的文件夹名称一致,然后启用该主题即可。子主题会继承父主题的模板、样式和功能,只需存放你自己的修改内容。
如果你曾直接修改主题文件,却在下一次主题更新后发现改动全部被覆盖,那么子主题正是避免这种情况的方法。
本指南介绍在现代 WordPress 中行之有效的配置方式,内容包括:如何正确加载样式表的经典子主题、模板与函数的覆盖方法、Twenty Twenty-Four 等区块主题的不同之处,以及哪些情况下根本不需要子主题。
核心要点
- 子主题唯一必需的文件是
style.css。其Template头部字段必须与父主题的文件夹名称完全一致,例如 Twenty Twenty-Four 对应的值为twentytwentyfour。 - 子主题的
functions.php不会替换父主题的同名文件。WordPress 会同时加载两者,且子主题的文件先执行。 - 应在
wp_enqueue_scripts钩子上使用wp_enqueue_style()加载子主题样式,而不是使用@import。 - 要覆盖模板、模板部件或样板,只需在子主题中放入同名文件。区块样板还需要使用相同的
Slug。 - 需要在切换主题后仍然保留的改动(例如自定义文章类型)应放在插件中,而不是子主题中。
为什么不应直接修改父主题?
主题更新时,WordPress 会用新版本替换主题的所有文件,因此你在主题文件夹内所做的任何修改都会丢失。子主题将你的改动存放在一个独立的文件夹中,更新永远不会触及它。主题开发手册中的子主题指南将其定位为一种既能获取父主题更新、又不必放弃自定义修改的方式。WordPress 主题系统只支持两层结构,即父主题和子主题,因此无法在子主题之上再安装”孙主题”。
如何在 WordPress 中创建子主题(经典主题)
经典子主题需要一个文件夹、一个包含 Template 头部字段的 style.css,以及在大多数情况下用于加载样式表的 functions.php。其目录结构与我们关于使用 Bootstrap 构建响应式 WordPress 主题的指南中介绍的文件夹、style.css 和 functions.php 结构相同,区别在于子主题只包含需要修改的部分。
wp-content/themes/
├── twentytwentyfour/
└── tt4-child/
├── style.css
└── functions.php
style.css 头部信息
严格来说,只有 style.css 是必需的。根据主样式表文档,任何主题都至少需要一个 Theme Name 头部字段,而子主题还要在此基础上添加 Template 字段。
/*
* Theme Name: TT4 Child
* Template: twentytwentyfour
* Version: 1.0.0
* Text Domain: tt4-child
*/
Template 的值是父主题的文件夹名称,而不是其显示名称。如果两者不能逐字符完全匹配,WordPress 将无法将子主题与父主题关联起来。
使用 enqueue 而非 @import 加载样式表
应在 wp_enqueue_scripts 钩子上使用 wp_enqueue_style() 加载子主题样式,而不是使用 @import。许多较早的教程会在子主题 style.css 的顶部通过 @import 加载父主题样式,这种做法已经过时:
/* Outdated: do not use */
@import url("../twentytwentyfour/style.css");
浏览器必须先下载并解析子主题样式表,才能发现被导入的文件,因此两个请求只能串行执行,而且都会阻塞渲染。此外,@import 游离于 WordPress 的 enqueue 机制之外,WordPress 无法管理其依赖关系、为其添加版本号或进行去重。
正确的替代方案是在挂载到 wp_enqueue_scripts 钩子的函数中调用 wp_enqueue_style()。同一个钩子也用于加载脚本,相关内容可参阅我们关于向 WordPress 主题添加自定义 JavaScript 的文章。
<?php
// functions.php
add_action( 'wp_enqueue_scripts', 'tt4child_enqueue_styles' );
function tt4child_enqueue_styles() {
wp_enqueue_style(
'tt4child-style',
get_stylesheet_uri(), // the active (child) theme's style.css
array(), // $deps: handles that must load first
'1.0.0' // $ver: appended as a cache-busting query string
);
}
子主题应使用哪种 enqueue 代码,取决于父主题本身已经加载了哪些样式表:
| 父主题加载的内容 | 子主题需要做的事 |
|---|---|
| 同时加载自身和子主题的样式表 | 无需任何操作 |
| 仅加载自身的样式表 | 使用 get_stylesheet_uri() 加载子主题样式(见上方代码) |
仅通过 get_stylesheet_uri() 加载当前启用主题的样式表 | 使用 get_parent_theme_file_uri( 'style.css' ) 加载父主题样式 |
| 不加载任何样式表(据手册所述,Twenty Twenty-Four 属于此类) | 使用 get_stylesheet_uri() 加载子主题样式 |
对于表格中的第三种情况,请使用以下代码:
function tt4child_enqueue_styles() {
wp_enqueue_style(
'tt4child-parent-style',
get_parent_theme_file_uri( 'style.css' )
);
}
要确定属于哪种情况,可以在父主题的 functions.php 中搜索 wp_enqueue_style,或在页面源代码中查找样式表的 <link> 标签。样式表的输出顺序并不固定,而是取决于 enqueue 调用的顺序;$deps 参数接受一个句柄(handle)数组,其中的样式表必须先行加载。若要让子主题样式在父主题样式之后输出,请将父主题的句柄传入 $deps,句柄名称可从父主题代码中查到。如果子主题样式表的 URL 有误,请求将返回 404,页面只会以父主题的 CSS 进行渲染。借助能在可视化回放旁同步记录网络请求的会话回放工具,你可以将失败的请求与样式缺失的页面对照查看。
如何覆盖父主题模板?
要覆盖父主题模板,请将该文件复制(而不是移动)到子主题中相同的相对路径下。WordPress 在解析模板层级时会先检查子主题文件夹,再检查父主题,因此子主题中的同名文件会优先生效。如果移动而非复制文件,就等于修改了父主题,而且下一次更新时该文件仍会被恢复。
twentytwentyfour/page.php → copy to → tt4-child/page.php
同样的规则也适用于模板部件。你还可以添加父主题中不存在的新模板。
覆盖父主题函数
子主题的 functions.php 不会替换父主题的同名文件。根据手册说明,WordPress 会运行这两个文件:先运行子主题的文件,紧接着运行父主题的文件。这一加载顺序带来两个结论:用 function_exists() 包裹的父主题函数(即可插拔函数,pluggable functions)可以在子主题中重新定义;其他父主题函数则只能通过移除钩子或追加功能的方式来处理。另外,切勿将父主题 functions.php 中的代码粘贴到子主题中:重复声明同名函数会引发致命错误,导致网站崩溃。
父主题中的可插拔函数如下所示:
// Parent theme
if ( ! function_exists( 'parenttheme_credit' ) ) {
function parenttheme_credit() { /* ... */ }
}
由于子主题先运行,只要在子主题中定义同名函数,父主题的版本就会被跳过:
// Child theme
function parenttheme_credit() {
echo '<p>Custom credit line</p>';
}
如果函数不是可插拔的,可以使用 remove_action() 将其移除,且必须使用与注册时相同的钩子和优先级。如果将 remove_action() 直接写在子主题文件的顶层,它会在父主题添加钩子之前执行,此时还没有可移除的内容。因此应在 after_setup_theme 中调用它,该钩子会在两个主题的 functions.php 都加载完毕后触发:
// Parent theme
add_action( 'init', 'parenttheme_setup_extra', 20 );
// Child theme
add_action( 'after_setup_theme', function () {
remove_action( 'init', 'parenttheme_setup_extra', 20 );
} );
remove_filter() 对过滤器的用法与此相同。如果你想在父主题函数的基础上追加功能而非替换它,可以将自己的函数挂载到同一个钩子上,并设置更晚的优先级。add_action() 的默认优先级为 10,数值越大,执行越晚:
add_action( 'parent_footer', 'tt4child_footer_extra', 20 );
区块主题的子主题如何运作?
区块主题的子主题(例如基于 Twenty Twenty-Four 构建的子主题)通常只需要 style.css 和 theme.json。由于样式主要定义在 theme.json 中,手册指出通常无需 enqueue style.css,许多区块子主题甚至完全没有 functions.php。
| 经典子主题 | 区块子主题 | |
|---|---|---|
| 必需文件 | style.css | style.css |
| 样式存放位置 | style.css(通过 enqueue 加载) | theme.json |
functions.php | 通常需要,用于加载样式 | 通常不需要 |
| 模板 | 主题根目录下的 .php 文件 | templates/ 中的 .html 文件 |
| 模板部件 | header.php 等 .php 文件 | parts/ 中的 .html 文件 |
| 样板 | patterns/ 中的 PHP 文件或 register_block_pattern() | patterns/ 中的 PHP 文件 |
tt4-child/
├── style.css
├── theme.json
├── templates/
│ └── single.html
├── parts/
│ └── header.html
└── patterns/
└── (same filename as the parent pattern).php
要覆盖模板、模板部件或样板,只需添加同名文件。对于样板,注册时使用的 Slug 头部字段也必须与父主题一致,因此请从父主题文件中复制该字段:
<?php
/**
* Title: Footer
* Slug: parent-slug/pattern-name
*/
?>
一个最简的 theme.json 会指定 schema,并使用 version 3(需要 WordPress 6.6 或更高版本):
{
"$schema": "https://schemas.wp.org/wp/6.6/theme.json",
"version": 3,
"styles": {
"typography": {
"lineHeight": "1.7"
}
}
}
父主题与子主题的 theme.json 会按照特定的合并规则组合,子主题的值优先。在站点编辑器中保存的自定义内容会存储在数据库中,在手册描述的分层模型中,其优先级高于子主题。如果子主题模板似乎没有生效,请在站点编辑器中清除该模板已保存的自定义内容。
哪些情况下不需要子主题
对于细微的视觉调整或功能性特性,并不需要子主题。在经典主题中,几条 CSS 规则通过定制器的”额外 CSS”即可实现。启用区块主题时,自定义 CSS 则改为通过站点编辑器的”样式”面板添加。自定义文章类型等功能性改动应放在插件中,这样在切换主题后依然有效。如果你的修改规模大到难以通过子主题维护,手册建议将父主题 fork 为你自己的独立主题。
总结
子主题的核心不过是一个文件夹,加上一个 Template 值与父主题文件夹名称相匹配的 style.css。对于经典主题,需要根据父主题已加载的内容添加相应的 enqueue 代码;对于区块主题,则由 theme.json 和同名模板文件来完成工作。建议先检查父主题加载了哪些样式表,再只添加你真正需要修改的文件。
常见问题
在子主题中,get_template_directory_uri() 和 get_stylesheet_directory_uri() 有什么区别?
当子主题处于启用状态时,get_template_directory_uri() 返回父主题目录的 URL,而 get_stylesheet_directory_uri() 返回子主题目录的 URL。对于存放在子主题中的文件,应使用 stylesheet 系列函数。自 WordPress 4.7 起,get_theme_file_uri() 通常是更好的选择:如果子主题中存在该文件的副本,它会返回子主题中的文件,否则回退到父主题的文件。
为什么启用子主题后,定制器设置和菜单位置都不见了?
WordPress 会为每个主题单独存储定制器设置(theme mods),并以当前启用主题的文件夹名称作为键,因此子主题会从一套空白设置开始,不会继承父主题的设置。你的菜单仍然存在,但需要重新将其分配到菜单位置,并重新填写或导入其他设置。父主题的设置仍保留在数据库中,切换回父主题后即可恢复。
复制到子主题中的模板文件,在父主题更新后会怎样?
子主题中的副本会保持你保存时的原样,WordPress 会继续使用它来替代父主题更新后的文件。父主题对该文件所做的任何 bug 修复、安全补丁或标记改动都不会同步到你的覆盖文件中。每次父主题更新后,都应将覆盖的文件与新版父主题文件进行比对,并合并所需的改动。为便于维护,请尽量减少需要覆盖的文件数量。
能否无需编写代码就为区块主题创建子主题?
可以。WordPress.org 上的官方插件 Create Block Theme 提供了 Create Child Theme 选项。它可以为当前启用的区块主题(例如 Twenty Twenty-Four)生成子主题,并将你在站点编辑器中所做的修改写入子主题的文件。该插件专为区块主题设计,因此经典父主题的子主题仍需按上文所述配置 style.css 和 functions.php。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k