我现在这个站用的主题,最早不是我自己写的。
它来自另一个站——一个物理教学网站,主题目录叫 jiaoxueshiping。我把它复制过来,改名成 caogenbj,然后开始改。
改 header、改 footer、改 CSS、加功能、修 bug。改得很顺手,因为文件就在那儿,打开就能编辑。
三年后我才发现,这个做法有个隐形成本:我再也拿不到上游的更新了。
不是说上游一定有好东西,而是说——从我把文件复制过来那一刻起,这条线就断了。以后上游修了一个安全漏洞,我不知道;加了一个新特性,我也用不上。这个主题彻底变成了”我一个人的分支”,它所有的问题都得我自己扛。
更具体的证据:我这次排查的时候,在 index.php 里发现一处背景图地址写的是:
background: url(http://www.caogenbj.com/wp-content/themes/jiaoxueshiping/notice_bg.gif)
目录早就改名成 caogenbj 了,这个路径还在指向老名字,所以那张图一直是 404。这就是”复制后直接改”留下的化石。
如果当初用的是子主题,这类问题根本不会存在——因为父主题的文件你一个字都不用动。
一句话:把”我要改的”和”别人写的”分开存放。
父主题(原来的主题)保持原样,一个字节不改。你所有修改都放在子主题目录里。WordPress 加载时,先去子主题找文件,找不到再去父主题找。
这个机制叫模板覆盖。它有两个直接好处:
两个文件就够。
第一个是 style.css,放在 wp-content/themes/caogenbj-child/ 下:
/*
Theme Name: Caogenbj Child
Template: caogenbj
Version: 1.0
*/
/* 下面写你自己的样式,会覆盖父主题 */
关键就是 Template: 那一行,值必须严格等于父主题的目录名。caogenbj 是目录名,不是 Theme Name。写错了,后台会提示”父主题不存在”。
第二个是 functions.php:
<?php
// 把父主题的样式表挂进来
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'caogenbj-parent',
get_template_directory_uri() . '/style.css'
);
} );
注意这里用的是 get_template_directory_uri()——它指向父主题。这个函数名很容易记反,下面单独说。
子主题只有四个函数容易搞混,但搞混了就是白屏或者样式全丢。做成一张表:
| 你要拿的东西 | 用哪个函数 | 指向 |
|---|---|---|
| 子主题的目录路径 | get_stylesheet_directory() |
子主题 |
| 子主题的 URL | get_stylesheet_directory_uri() |
子主题 |
| 父主题的目录路径 | get_template_directory() |
父主题 |
| 父主题的 URL | get_template_directory_uri() |
父主题 |
记法:stylesheet 是你现在用的(子),template 是你继承的(父)。
为什么这么命名?因为 WordPress 早期只有”当前主题”一个概念,管它叫 stylesheet;后来有了父子关系,”当前主题”变成子主题,但函数名没改。
这里有个真实的坑:你在子主题的 functions.php 里写 get_stylesheet_directory() . '/inc/seo.php',结果这个文件放在父主题里——那就 include 不到,直接报错。反过来也一样。所以每写一次,都要问自己一句:这个文件到底在谁的目录里?
这是子主题最反直觉的一点:子主题的 functions.php 先加载,父主题的后加载。
为什么这重要?因为如果父主题里写的是:
function my_setup() { ... }
add_action( 'after_setup_theme', 'my_setup' );
你想在子主题里”覆盖”这个函数,直接定义同名函数是不行的——PHP 会报 Cannot redeclare。正确做法是先用 remove_action 摘掉,再挂自己的:
<?php
add_action( 'after_setup_theme', function () {
remove_action( 'after_setup_theme', 'my_setup' );
// 然后 add_action 你自己的
}, 20 );
那个 20 是为了让”摘”这个动作发生在父主题已经挂上之后。因为子主题先加载,如果你在文件顶层直接 remove_action,父主题还没执行到 add_action 那一行,摘了个空。
这个顺序问题,是子主题新手最常踩的坑,没有之一。
能覆盖的:所有模板文件。header.php、footer.php、index.php、single.php、page.php、category.php、searchform.php、sidebar.php……你把同名文件放进子主题,WordPress 就优先用你的。
不能覆盖的:父主题的 functions.php。子主题的 functions.php 是追加执行,不是替换。父主题的函数照样全部生效。
还有一类容易忽略的:父主题用 get_template_part() 加载的片段。这些片段默认从父主题找,除非你传了 get_stylesheet_directory() 或者它内部用了 locate_template()。locate_template() 会自动先找子主题,所以父主题作者如果用对了函数,你覆盖片段就能生效;如果硬写 include,你就覆盖不了。
这就是”主题写得好不好用”的分水岭之一。
如果你现在正在改一个别人的主题,我的建议是:
我这个站就属于第二种。现在迁不迁?我暂时没迁——因为我改造的幅度已经太大了,几乎是重写,迁成子主题的意义不大。但我把这个决定记下来了:下次建站,从第一天就用子主题。
一句话记住:子主题不是”更高级的写法”,是”给自己留退路”。改别人的主题,两个文件就能留。
下一篇聊性能:脚本、图片、缓存,哪些是主题该管的,哪些不该。