WordPress源码深度解析之:`Block`的`hooks`:`render_block`和`pre_render_block`的底层实现。

各位观众老爷,欢迎来到今天的WordPress源码深度解析专场!今天咱们聊点刺激的,扒一扒WordPress Block的“hooks”——render_blockpre_render_block这两个小妖精的底层实现。准备好了吗?Let’s dive in!

一、Block Hooks:Render Before & After

在WordPress的世界里,Block不仅仅是静态的内容块,它更是一个动态的、可定制的组件。而render_blockpre_render_block这两个hooks,就是赋予Block强大生命力的重要手段。它们允许我们在Block渲染的前后,对Block的内容进行干预,从而实现各种各样的定制需求。

  • pre_render_block: 这个钩子在Block即将被渲染之前触发。你可以用它来修改Block的属性、内容,甚至完全替换掉Block的渲染结果。想象一下,你可以在渲染前根据用户的权限,动态地显示或隐藏Block的某些部分。
  • render_block: 这个钩子在Block已经渲染完毕之后触发。你可以用它来对Block的输出进行后处理,例如添加额外的HTML标签、修改CSS样式,或者进行一些数据统计。

二、源码追踪:render_block的寻根之旅

为了搞清楚render_blockpre_render_block是如何工作的,我们需要深入到WordPress的源码中去一探究竟。

  1. do_blocks() 函数:渲染的起点

    do_blocks()函数是WordPress渲染Block的入口。它负责解析文章内容中的Block标记,并调用相应的渲染函数。

    // wp-includes/blocks.php
    function do_blocks( $content ) {
       if ( ! has_blocks( $content ) ) {
           return $content;
       }
    
       $blocks = parse_blocks( $content );
    
       if ( empty( $blocks ) ) {
           return $content;
       }
    
       $output = '';
       foreach ( $blocks as $block ) {
           $output .= render_block( $block );
       }
    
       return $output;
    }

    可以看到,do_blocks()函数首先使用parse_blocks()函数将文章内容解析成Block数组,然后遍历数组,调用render_block()函数来渲染每个Block

  2. render_block() 函数:核心渲染逻辑

    render_block()函数是Block渲染的核心。它负责根据Block的类型,调用相应的渲染函数,并应用pre_render_blockrender_block这两个hooks

    // wp-includes/blocks.php
    function render_block( array $block ) {
       $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
    
       if ( ! $block_type ) {
           return ''; // Or a placeholder, depending on your needs
       }
    
       // Apply pre_render_block hook
       $pre_render_content = apply_filters( 'pre_render_block', null, $block );
    
       if ( null !== $pre_render_content ) {
           $content = $pre_render_content;
       } else {
           // Retrieve attributes
           $attributes = $block['attrs'];
    
           // Render the block
           $content = $block_type->render( $attributes, $block['content'] );
       }
    
       // Apply render_block hook
       $content = apply_filters( 'render_block', $content, $block );
    
       return $content;
    }

    这个函数做了几件重要的事情:

    • 获取Block类型: 根据BlockblockName,从WP_Block_Type_Registry中获取Block类型对象。
    • 应用pre_render_block: 调用apply_filters( 'pre_render_block', null, $block )应用pre_render_block钩子。如果任何一个filter返回了非null的值,那么Block的渲染结果将被替换为这个值。
    • 渲染Block: 如果pre_render_block没有返回任何值,那么就调用$block_type->render()函数来渲染Block
    • 应用render_block: 调用apply_filters( 'render_block', $content, $block )应用render_block钩子。这个钩子允许你对Block的渲染结果进行后处理。
  3. apply_filters() 函数:钩子的灵魂

    apply_filters()函数是WordPress钩子机制的核心。它负责遍历所有注册到指定钩子的函数,并依次调用它们,将Block的数据传递给这些函数,并接收它们的返回值。

    // wp-includes/plugin.php
    function apply_filters( $tag, $value, ...$args ) {
        global $wp_filter, $wp_current_filter;
    
        $args = func_get_args();
        $tag  = array_shift( $args );
        $value = array_shift( $args );
    
        if ( ! isset( $wp_filter[ $tag ] ) ) {
            return $value;
        }
    
        // Sort filters by priority
        if ( ! isset( $wp_filter[ $tag ]['callbacks'] ) ) {
            $wp_filter[ $tag ]->build_preinitialized_callbacks();
        }
    
        $wp_current_filter[] = $tag;
    
        $filtered = $value;
        foreach ( $wp_filter[ $tag ]->callbacks as $priority => $functions ) {
            foreach ( $functions as $function ) {
                $args[0] = $filtered;
                $result = call_user_func_array( $function['function'], $args );
                if ( isset( $result ) ) {
                    $filtered = $result;
                }
            }
        }
    
        array_pop( $wp_current_filter );
    
        return $filtered;
    }

    简单来说,apply_filters()函数做了以下几件事:

    • 检查钩子是否存在: 检查是否存在注册到指定tag(即钩子名称)的函数。
    • 遍历注册函数: 遍历所有注册到钩子的函数,按照优先级顺序依次调用它们。
    • 传递参数: 将Block的数据作为参数传递给这些函数。
    • 接收返回值: 接收这些函数的返回值,并将返回值作为下一个函数的输入。
    • 返回最终结果: 返回经过所有函数处理后的最终结果。

三、pre_render_blockrender_block的实战演练

光说不练假把式,现在让我们通过几个实际的例子,来演示一下pre_render_blockrender_block的用法。

  1. pre_render_block:权限控制

    假设我们想根据用户的权限,来控制Block的显示。我们可以使用pre_render_block钩子来实现这个功能。

    add_filter( 'pre_render_block', 'my_custom_pre_render_block', 10, 2 );
    
    function my_custom_pre_render_block( $pre_render, $block ) {
       if ( 'core/paragraph' === $block['blockName'] ) {
           if ( ! current_user_can( 'edit_posts' ) ) {
               return '<div>您没有权限查看此段落。</div>';
           }
       }
       return $pre_render;
    }

    这段代码的作用是:如果当前用户没有edit_posts权限,那么就将core/paragraph这个Block的渲染结果替换为一个提示信息。

  2. render_block:添加额外HTML

    假设我们想在所有的core/paragraph Block的底部添加一个版权声明。我们可以使用render_block钩子来实现这个功能。

    add_filter( 'render_block', 'my_custom_render_block', 10, 2 );
    
    function my_custom_render_block( $content, $block ) {
       if ( 'core/paragraph' === $block['blockName'] ) {
           $content .= '<p class="copyright">Copyright © 2023</p>';
       }
       return $content;
    }

    这段代码的作用是:在所有的core/paragraph Block的底部添加一个带有copyright class的段落,显示版权信息。

  3. 更复杂的例子:动态修改Block属性

    假设我们想根据当前日期,动态修改Block的背景颜色。这需要用到pre_render_block,因为我们需要在渲染前修改Block的属性。

    add_filter( 'pre_render_block', 'my_dynamic_background_color', 10, 2 );
    
    function my_dynamic_background_color( $pre_render, $block ) {
        if ( 'core/group' === $block['blockName'] ) { // 假设我们修改的是Group Block的背景色
            $current_day = date('N'); // 获取当前是星期几 (1-7)
    
            $background_colors = [
                '#f0f0f0', // 星期一
                '#e0e0e0', // 星期二
                '#d0d0d0', // 星期三
                '#c0c0c0', // 星期四
                '#b0b0b0', // 星期五
                '#a0a0a0', // 星期六
                '#909090',  // 星期日
            ];
    
            $block['attrs']['backgroundColor'] = $background_colors[$current_day - 1]; // 设置backgroundColor属性
    
            //  返回null,让WordPress使用修改后的属性渲染Block
            return null;
        }
        return $pre_render;
    }

    这个例子中,我们首先获取当前是星期几,然后根据星期几,从一个颜色数组中选择一个背景颜色,最后将这个颜色设置到BlockbackgroundColor属性中。 注意,这里我们返回了null,这意味着我们只是修改了Block的属性,并没有完全替换Block的渲染结果。WordPress会使用修改后的属性来渲染Block。 这个例子需要Block本身支持backgroundColor属性。

四、pre_render_block vs render_block:选择的艺术

既然有了pre_render_blockrender_block这两个钩子,那么我们应该在什么时候使用哪个钩子呢?

特性 pre_render_block render_block
触发时机 Block渲染之前 Block渲染之后
主要用途 修改Block的属性、内容,甚至完全替换Block的渲染结果。例如:权限控制、动态修改属性。 Block的输出进行后处理。例如:添加额外的HTML标签、修改CSS样式、数据统计。
返回值影响 如果返回非null的值,Block的渲染结果将被替换为这个值。如果返回null,则WordPress会使用原始Block数据进行渲染. 返回值将作为最终的Block输出。
适用场景 需要在渲染前对Block进行修改的场景。例如:根据用户权限控制Block的显示、根据当前日期动态修改Block的背景颜色。 需要在渲染后对Block进行后处理的场景。例如:在Block的底部添加版权声明、对Block的输出进行格式化。
注意事项 如果多个pre_render_block钩子都返回了非null的值,那么只有第一个钩子的返回值会被使用。谨慎使用,避免冲突。 必须返回一个字符串,否则可能会导致页面显示异常。

总的来说:

  • 如果需要在渲染前修改Block,或者完全替换Block的渲染结果,那么应该使用pre_render_block
  • 如果只需要对Block的输出进行后处理,那么应该使用render_block

五、总结与展望

render_blockpre_render_block是WordPress Block生态系统中非常重要的两个hooks。它们允许我们对Block的渲染过程进行高度定制,从而实现各种各样的功能。 掌握了这两个hooks,你就可以轻松地打造出个性化的WordPress网站。

希望今天的讲座能够帮助大家更好地理解WordPress Block的底层实现。记住,源码是最好的老师,多读源码,多实践,你也能成为WordPress的高手!

下次再见!

发表回复

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