WordPress函数:register_nav_menus 注册导航菜单

编辑文章

简介

register_nav_menus 是 WordPress 的核心函数,用于在主题中注册一个或多个导航菜单位置。这些位置可以让用户在后台的”外观 > 菜单”页面中分配具体的菜单内容,然后在主题模板的指定位置显示。

语法

此函数定义于 wp-includes/nav-menu.php 文件中。它内部会调用 register_nav_menu()register_nav_menus() 函数(具体取决于参数类型)来注册菜单。

register_nav_menus( array $locations = array() )
参数 类型 默认值 说明
$locations 数组 array() 一个关联数组,键是菜单位置标识符(slug),值是在后台显示给用户的描述文本。
返回值 空值 此函数不返回任何值。

用法

基础用法

最常见的用法是在主题的 functions.php 文件中注册一个或多个导航菜单位置。

<?php
/**
 * 在主题的 functions.php 文件中注册导航菜单
 */
function my_theme_register_nav_menus() {
    register_nav_menus(
        array(
            'primary-menu'   => esc_html__( '主导航菜单', 'my-theme' ),
            'footer-menu'    => esc_html__( '底部菜单', 'my-theme' ),
            'social-menu'    => esc_html__( '社交媒体菜单', 'my-theme' ),
        )
    );
}
add_action( 'after_setup_theme', 'my_theme_register_nav_menus' );

为什么使用 after_setup_theme 钩子? 这个钩子在主题初始化阶段被调用,是注册主题功能(如菜单、缩略图支持等)的标准位置。确保菜单注册在主题加载时执行,不会太早或太晚。

进阶用法

注册菜单后,需要在主题模板中相应位置显示菜单。使用 wp_nav_menu() 函数来输出菜单。

<?php
/**
 * 在 header.php 中显示主导航菜单
 */
?>
<header id="masthead" class="site-header">
    <nav id="site-navigation" class="main-navigation">
        <?php
        wp_nav_menu(
            array(
                'theme_location' => 'primary-menu', // 对应注册时的键
                'menu_id'        => 'primary-menu',
                'container'      => false, // 不包裹容器
                'fallback_cb'    => false, // 无菜单时不显示任何内容
            )
        );
        ?>
    </nav>
</header>

可以创建条件逻辑,只在某些页面显示特定菜单:

<?php
/**
 * 根据页面类型显示不同的菜单
 */
if ( is_front_page() ) {
    // 首页显示特殊菜单
    wp_nav_menu(
        array(
            'theme_location' => 'home-menu',
            'container'      => 'nav',
            'container_class' => 'home-navigation',
        )
    );
} elseif ( is_single() ) {
    // 文章页显示文章相关菜单
    wp_nav_menu(
        array(
            'theme_location' => 'single-menu',
            'container'      => false,
        )
    );
} else {
    // 其他页面显示主导航
    wp_nav_menu(
        array(
            'theme_location' => 'primary-menu',
            'container'      => false,
        )
    );
}
?>

易错点

  • 忘记添加 after_setup_theme 钩子:直接将 register_nav_menus() 调用放在 functions.php 的顶层可能会在某些情况下失效,因为主题功能可能尚未完全初始化。正确做法是将其包装在函数中并通过 after_setup_theme 钩子调用。

  • 键名与描述混淆register_nav_menus() 参数数组的键是菜单位置的标识符(slug),用于在代码中引用;值是显示在后台给用户看的描述。常见错误是将两者颠倒或使用相同的值。

  • 主题位置未在模板中调用:注册了菜单位置后,如果不在模板中使用 wp_nav_menu() 函数并指定相应的 theme_location 参数,该菜单位置将不会在网站上显示。注册和调用是两个独立步骤,缺一不可。

  • 未提供备用回调(fallback_cb):当用户未在后台为某个菜单位置分配菜单时,wp_nav_menu() 默认会显示一个页面列表(如果设置了 fallback_cbwp_page_menu)。这可能不是期望的行为,最好明确设置 fallback_cb 参数来控制无菜单时的表现。

  • 忘记翻译函数:主题通常需要支持多语言。在菜单描述文本中未使用翻译函数(如 __()esc_html__())会导致文本无法被翻译插件处理。

最佳实践

使用有意义的菜单位置标识符

菜单位置标识符应该清晰、有意义,并能反映菜单位置的用途。使用连字符分隔的小写字母,避免使用特殊字符或空格。

// 良好实践
register_nav_menus(
    array(
        'primary-navigation'   => esc_html__( '主导航', 'my-theme' ),
        'footer-legal-links'   => esc_html__( '底部法律链接', 'my-theme' ),
        'mobile-quick-access'  => esc_html__( '移动端快速访问', 'my-theme' ),
    )
);

控制无菜单时的备用行为

通过 wp_nav_menu()fallback_cb 参数明确控制当用户未在后台分配菜单时的行为:

wp_nav_menu(
    array(
        'theme_location' => 'primary-menu',
        'fallback_cb'    => false, // 完全不显示任何内容
        // 或者使用自定义回调函数
        // 'fallback_cb' => 'my_custom_fallback_menu',
    )
);

如果需要显示自定义的备用内容,可以创建回调函数:

function my_custom_fallback_menu() {
    // 显示一个简单的导航链接列表
    echo '<ul class="fallback-menu">';
    echo '<li><a href="' . esc_url( home_url( '/' ) ) . '">' . esc_html__( '首页', 'my-theme' ) . '</a></li>';
    echo '<li><a href="' . esc_url( admin_url( 'nav-menus.php' ) ) . '">' . esc_html__( '添加菜单', 'my-theme' ) . '</a></li>';
    echo '</ul>';
}

为菜单添加适当的ARIA角色和标签

为了网站的无障碍访问,导航菜单应该包含适当的ARIA角色和标签:

wp_nav_menu(
    array(
        'theme_location'  => 'primary-menu',
        'container'       => 'nav',
        'container_id'    => 'primary-navigation',
        'container_class' => 'main-navigation',
        'menu_id'         => 'primary-menu',
        'menu_class'      => 'menu primary-menu',
        'items_wrap'      => '<ul id="%1$s" class="%2$s" role="menubar">%3$s</ul>',
        'fallback_cb'     => false,
        'walker'          => new My_Custom_Walker_Nav_Menu(), // 可选:自定义菜单walker
    )
);

使用自定义Walker类实现复杂菜单结构

当需要实现复杂的菜单结构(如多级下拉菜单、添加图标或特殊标记)时,可以创建自定义的Walker类:

// 在functions.php中定义自定义Walker类
class My_Theme_Walker_Nav_Menu extends Walker_Nav_Menu {
    // 重写Walker类的方法来自定义输出
    function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
        // 自定义每个菜单项的开始标签
        // 注意:输出所有用户数据时都要进行转义
        $output .= '<li class="custom-menu-item">';
        $output .= '<a href="' . esc_url( $item->url ) . '">';
        $output .= '<span class="menu-icon"></span>';
        $output .= '<span class="menu-text">' . esc_html( $item->title ) . '</span>';
        $output .= '</a>';
    }

    // 可以添加其他方法来自定义不同级别的菜单
}

// 在模板中使用自定义Walker
wp_nav_menu(
    array(
        'theme_location' => 'primary-menu',
        'walker'         => new My_Theme_Walker_Nav_Menu(),
    )
);

为什么使用Walker类? Walker类提供了对菜单HTML结构的完全控制,允许开发者添加自定义HTML元素、CSS类或数据属性,而无需修改核心代码。这比在输出后使用JavaScript修改DOM更高效、更可靠。

缓存菜单输出提高性能

对于大型网站或复杂菜单,每次页面加载都重新生成菜单HTML可能影响性能。可以使用WordPress的瞬态API(Transients API)或对象缓存来缓存菜单输出:

function get_cached_nav_menu( $theme_location ) {
    $cache_key = 'cached_nav_menu_' . $theme_location . '_' . get_locale();
    $menu_html = get_transient( $cache_key );

    if ( false === $menu_html ) {
        // 缓存不存在,生成菜单
        ob_start();
        wp_nav_menu(
            array(
                'theme_location' => $theme_location,
                'echo'           => true,
                'fallback_cb'    => false,
            )
        );
        $menu_html = ob_get_clean();

        // 缓存菜单HTML,有效期1小时
        set_transient( $cache_key, $menu_html, HOUR_IN_SECONDS );
    }

    return $menu_html;
}

// 在模板中使用
echo get_cached_nav_menu( 'primary-menu' );

重要:当菜单在后台被修改时,需要清除相关缓存。可以在保存菜单时添加清理逻辑:

function clear_nav_menu_cache( $menu_id, $menu_data = array() ) {
    // 清除所有菜单缓存
    $locations = get_nav_menu_locations();
    foreach ( $locations as $location => $menu_id_at_location ) {
        if ( $menu_id_at_location == $menu_id ) {
            delete_transient( 'cached_nav_menu_' . $location . '_' . get_locale() );
        }
    }
}
add_action( 'wp_update_nav_menu', 'clear_nav_menu_cache', 10, 2 );

确保菜单安全性

虽然 wp_nav_menu() 默认会转义输出,但在自定义Walker类或直接处理菜单项数据时,必须手动转义所有用户提供的数据:

// 在自定义Walker中
function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
    // 安全做法:转义所有输出
    $output .= '<li>';
    $output .= '<a href="' . esc_url( $item->url ) . '"';
    $output .= ' title="' . esc_attr( $item->title ) . '"';
    $output .= '>';
    $output .= esc_html( $item->title );
    $output .= '</a>';
}

// 不安全做法:直接输出未转义的数据
// $output .= "<a href='{$item->url}'>{$item->title}</a>";

通过遵循这些最佳实践,你可以创建更健壮、更易维护且性能更好的导航菜单系统。