12k
All articles

如何创建 WordPress 子主题

创建WordPress子主题,让自定义内容在父主题更新后得以保留。了解经典主题与区块主题的设置、样式加载、模板覆盖,以及何时应改用插件。

OpenReplay Team
OpenReplay Team
如何创建 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.cssstyle.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。

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.