WordPress wp_nav_menu函数如何通过Walker_Nav_Menu实现递归菜单渲染

WordPress 菜单渲染的奥秘:wp_nav_menuWalker_Nav_Menu

大家好!今天,我们来深入探讨 WordPress 中菜单渲染的核心机制,特别是 wp_nav_menu 函数如何借助 Walker_Nav_Menu 类实现强大的递归菜单渲染。 这不仅能帮助你更深入地理解 WordPress 的运作方式,还能让你在自定义菜单时拥有更大的灵活性和控制权。

wp_nav_menu:菜单渲染的入口

wp_nav_menu 是 WordPress 提供的一个用于在主题中显示导航菜单的函数。 它的功能看似简单,但背后却隐藏着精巧的设计和强大的扩展性。

基本用法:

wp_nav_menu( array(
    'theme_location' => 'primary', // 注册菜单的位置名称
    'menu_class'     => 'nav-menu', // 菜单容器的 CSS 类名
    'menu_id'        => 'primary-menu', // 菜单容器的 ID
    'depth'          => 2, // 菜单深度,0 表示无限制
    'walker'         => new My_Custom_Walker() // 自定义 Walker 类
) );

在上述代码中,我们通过一个关联数组向 wp_nav_menu 传递各种参数,控制菜单的显示方式。 其中,theme_location 指定了要显示的菜单的位置,这个位置需要在主题的 functions.php 文件中使用 register_nav_menu 函数注册。 menu_classmenu_id 用于为菜单的 HTML 容器添加 CSS 类和 ID,方便我们进行样式控制。

depth 参数控制菜单的深度,也就是允许显示的子菜单层级。 设置为 0 表示显示所有层级的菜单。

walker 参数,则是我们今天的主角。 它允许我们使用自定义的 Walker 类来完全控制菜单的 HTML 输出。

wp_nav_menu 的内部流程:

  1. 参数解析: wp_nav_menu 函数首先解析传入的参数,并与默认参数合并。
  2. 获取菜单对象: 根据 theme_locationmenu 参数,获取相应的菜单对象。 如果没有找到菜单,则可能显示备用菜单或错误信息。
  3. Walker 实例化: 如果指定了 walker 参数,则实例化指定的 Walker 类。 否则,使用默认的 Walker_Nav_Menu 类。
  4. 递归渲染: wp_nav_menu 调用 Walker 类的 walk 方法,开始递归地遍历菜单项,并生成相应的 HTML 代码。
  5. 输出 HTML: 最后,将生成的 HTML 代码输出到页面中。

Walker_Nav_Menu:菜单结构的遍历者

Walker_Nav_Menu 是 WordPress 内置的一个用于生成导航菜单 HTML 的类。 它继承自 Walker 类,实现了递归遍历菜单项并生成 HTML 的核心逻辑。

Walker 类:

Walker 类是 WordPress 提供的一个抽象类,用于遍历树状结构的数据。 它定义了以下几个关键方法:

  • start_lvl( &$output, $depth = 0, $args = array() ): 在开始遍历一个子层级时调用。
  • end_lvl( &$output, $depth = 0, $args = array() ): 在结束遍历一个子层级时调用。
  • start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ): 在遍历到一个菜单项时调用。
  • end_el( &$output, $item, $depth = 0, $args = array() ): 在结束遍历一个菜单项时调用。

Walker_Nav_Menu 类通过重写这些方法,实现了针对菜单项的 HTML 生成逻辑。

Walker_Nav_Menu 的核心方法:

方法名 作用
start_lvl 在开始一个子菜单 (ul) 时输出 HTML。 通常用于输出 <ul> 标签。
end_lvl 在结束一个子菜单 (ul) 时输出 HTML。 通常用于输出 </ul> 标签。
start_el 在开始一个菜单项 (li) 时输出 HTML。 这是最核心的方法,负责输出 <li> 标签,链接,以及其他菜单项相关的信息。
end_el 在结束一个菜单项 (li) 时输出 HTML。 通常用于输出 </li> 标签。
display_element 用于判断是否显示当前菜单项,并调用 start_elend_el 方法。
walk 核心的递归遍历方法,负责调用上述方法,实现菜单的递归渲染。

Walker_Nav_Menu 的工作流程:

  1. wp_nav_menu 函数调用 Walker_Nav_Menu 对象的 walk 方法,传入菜单项数组和菜单深度等参数。
  2. walk 方法遍历菜单项数组,对于每个菜单项,调用 display_element 方法。
  3. display_element 方法判断是否应该显示当前菜单项,如果应该显示,则调用 start_el 方法。
  4. start_el 方法根据菜单项的属性(如标题、链接、目标等),生成相应的 HTML 代码,并添加到输出缓冲区。
  5. 如果当前菜单项有子菜单,则递归调用 walk 方法,遍历子菜单项。
  6. 在遍历完子菜单项后,调用 end_lvl 方法,输出子菜单的结束标签。
  7. 最后,调用 end_el 方法,输出当前菜单项的结束标签。
  8. walk 方法返回生成的 HTML 代码,wp_nav_menu 函数将代码输出到页面。

自定义 Walker 类:掌控菜单的输出

通过自定义 Walker 类,我们可以完全掌控菜单的 HTML 输出。 这为我们提供了极大的灵活性,可以根据自己的需求定制菜单的样式和结构。

创建自定义 Walker 类:

要创建自定义 Walker 类,我们需要继承 Walker_Nav_Menu 类,并重写 start_lvlend_lvlstart_elend_el 这四个方法。

class My_Custom_Walker extends Walker_Nav_Menu {

    /**
     * 在开始一个子菜单时输出 HTML.
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param int    $depth  Depth of page. Used for padding.
     * @param array  $args   An array of arguments. @see wp_nav_menu()
     */
    public function start_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = ( $depth > 0  ? str_repeat( "t", $depth ) : '' ); // code indent
        $display_depth = ( $args->depth > 0 ? $args->depth : 0 ); // no limit for the depth
        if($display_depth == 0 || ( is_array($args->depth) && in_array($depth, $args->depth )) ){
            $output .= "n" . $indent . '<ul class="sub-menu">' . "n";
        }
    }

    /**
     * 在结束一个子菜单时输出 HTML.
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param int    $depth  Depth of page.
     * @param array  $args   An array of arguments. @see wp_nav_menu()
     */
    public function end_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = ( $depth > 0  ? str_repeat( "t", $depth ) : '' );
        $display_depth = ( $args->depth > 0 ? $args->depth : 0 ); // no limit for the depth
        if($display_depth == 0 || ( is_array($args->depth) && in_array($depth, $args->depth )) ){
            $output .= $indent . "</ul>n";
        }
    }

    /**
     * 在开始一个菜单项时输出 HTML.
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param object $item   Menu item data object.
     * @param int    $depth  Depth of menu item. Used for padding.
     * @param array  $args   An array of arguments. @see wp_nav_menu()
     * @param int    $id     Menu item ID.
     */
    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = ( $depth ) ? str_repeat( "t", $depth ) : '';

        $li_attributes = '';
        $class_names = $value = '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;

        $classes[] = 'menu-item-' . $item->ID;

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
        $class_names = ' class="' . esc_attr( $class_names ) . '"';

        $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
        $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

        $li_attributes .= $id . $class_names;

        $output .= $indent . '<li' . $li_attributes . '>';

        $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
        $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
        $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
        $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

    /**
     * 在结束一个菜单项时输出 HTML.
     *
     * @param string $output Passed by reference. Used to append additional content.
     * @param object $item   Page data object. Not used.
     * @param int    $depth  Depth of page.
     * @param array  $args   An array of arguments. @see wp_nav_menu()
     */
    public function end_el( &$output, $item, $depth = 0, $args = array() ) {
        $output .= "</li>n";
    }
}

使用自定义 Walker 类:

wp_nav_menu 函数中使用 walker 参数,指定我们自定义的 Walker 类。

wp_nav_menu( array(
    'theme_location' => 'primary',
    'walker'         => new My_Custom_Walker()
) );

自定义 Walker 类的应用示例:

  1. 添加自定义 CSS 类:

    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ...
        $classes[] = 'my-custom-class'; // 添加自定义 CSS 类
        // ...
    }
  2. 添加自定义属性:

    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ...
        $attributes .= ' data-custom-attribute="value"'; // 添加自定义属性
        // ...
    }
  3. 修改链接文本:

    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ...
        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= '<span>' . apply_filters( 'the_title', $item->title, $item->ID ) . '</span>'; // 修改链接文本
        $item_output .= '</a>';
        $item_output .= $args->after;
        // ...
    }
  4. 添加 Font Awesome 图标:

    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        // ...
        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= '<i class="fa fa-home"></i> ' . apply_filters( 'the_title', $item->title, $item->ID ); // 添加 Font Awesome 图标
        $item_output .= '</a>';
        $item_output .= $args->after;
        // ...
    }

深度参数的灵活运用:

depth参数不仅可以在wp_nav_menu函数中使用,也可以在自定义Walker类中使用,从而实现更精细化的菜单控制。例如,我们可以根据菜单的深度,应用不同的CSS类或显示不同的内容。

wp_nav_menu中设置depth参数:

wp_nav_menu( array(
    'theme_location' => 'primary',
    'depth'          => 2, // 只显示两级菜单
    'walker'         => new My_Custom_Walker()
) );

在自定义Walker类中使用depth参数:

public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    if ($depth == 0) {
        $classes[] = 'top-level-menu-item'; // 为顶级菜单项添加特定的CSS类
    } elseif ($depth == 1) {
        $classes[] = 'sub-menu-item'; // 为二级菜单项添加特定的CSS类
    }
    // ...
}

实际案例:创建 Bootstrap 风格的导航菜单

Bootstrap 是一种流行的前端框架,提供了丰富的 CSS 类和组件,可以快速构建美观的网站。 我们可以通过自定义 Walker 类,轻松地创建 Bootstrap 风格的导航菜单。

class Bootstrap_Nav_Walker extends Walker_Nav_Menu {

    public function start_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat( "t", $depth );
        $output .= "n$indent<ul class="dropdown-menu">n";
    }

    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = ( $depth ) ? str_repeat( "t", $depth ) : '';

        $li_attributes = '';
        $class_names = $value = '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        if ($args->has_children) {
            $classes[] = 'dropdown';
        }

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
        $class_names = ' class="' . esc_attr( $class_names ) . '"';

        $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
        $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

        $li_attributes .= $id . $class_names;

        $output .= $indent . '<li' . $li_attributes . '>';

        $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
        $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
        $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
        $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';

        // 如果有子菜单,添加 dropdown-toggle 类和 data-toggle 属性
        if ($args->has_children) {
            $attributes .= ' class="dropdown-toggle" data-toggle="dropdown"';
        }

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        // 如果有子菜单,添加 caret
        if ($args->has_children) {
            $item_output .= ' <b class="caret"></b>';
        }
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

    public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
        if ( ! $element ) {
            return;
        }

        $id_field = $this->db_fields['id'];

        // 显示链接的子菜单,并记录
        if ( isset( $children_elements[ $element->$id_field ] ) ) {
            $element->has_children = true;
        }

        parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
    }
}

使用方法:

wp_nav_menu( array(
    'theme_location'    => 'primary',
    'depth'             => 2,
    'container'         => 'div',
    'container_class'   => 'collapse navbar-collapse',
    'container_id'      => 'bs-example-navbar-collapse-1',
    'menu_class'        => 'nav navbar-nav',
    'fallback_cb'       => 'WP_Bootstrap_Navwalker::fallback',
    'walker'            => new Bootstrap_Nav_Walker(),
));

常见问题和注意事项:

  • $output 参数: 始终通过引用传递 (&$output),以便在递归调用中累积 HTML 代码。
  • $depth 参数: 用于跟踪菜单的深度,方便根据深度应用不同的样式或逻辑。
  • $args 参数: 包含 wp_nav_menu 函数传递的参数,例如 beforeafterlink_beforelink_after,用于自定义链接的前后内容。
  • $item 参数: 包含菜单项的属性,例如 titleurlclassestarget
  • 避免无限递归: 确保菜单结构没有循环引用,否则可能导致无限递归。
  • 性能优化: 对于大型菜单,可以考虑使用缓存来提高性能。
  • 安全性: 对用户输入进行适当的转义,以防止 XSS 攻击。
  • filter hook:walker_nav_menu_start_el:允许你修改每一个菜单项的输出内容,更灵活的控制。

深入理解,灵活定制

通过理解 wp_nav_menu 函数和 Walker_Nav_Menu 类的运作方式,以及如何自定义 Walker 类,你就能完全掌控 WordPress 菜单的渲染过程,实现各种各样的菜单效果。 希望今天的讲解能帮助你更好地理解 WordPress,并在实际项目中灵活运用这些知识。

掌握了这些知识,你就能轻松创建各种各样的菜单,满足不同的需求。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注