Bài 9: Static Block (Block tĩnh) và Dynamic Block (Block động)
Block Markup trả về giao diện đầu nhìn (Frontend) có thể được tạo động từ Server (khi Block đó được yêu cầu) (Dynamic Block) hoặc được tạo theo dạng tĩnh và lưu trực tiếp trong Block Editor (Static Block)
Lý do Nam đưa hình này vào là bởi vì nó thực sự quan trọng trong việc thực sự hiểu về loại Block và cách nó được tạo ra như thế nào.
Hình trên cho thấy khi Update những thuộc tính (attrs) sẽ được lưu lại vào CSDL dưới dạng Block Markup. Lúc này khi người dùng thực hiện yêu cầu lấy dữ liệu từ Server, Block Markup sẽ được Server trả cho người dùng và thực hiện render ra giao diện mong muốn (Server-Rendering – bước này sử dụng ngôn ngữ PHP)
Như đã theo dõi ở các bài trước thì một Block được tạo ra sẽ có 3 kiểu dữ liệu được định nghĩa
Để làm rõ hơn, bạn có thể tham khảo trong bài Block Wrapper
Như vậy, Static Block sẽ được xác định khi bạn tinh chỉnh các thuộc tính (Định nghĩa bởi edit.js) và lưu vào (save.js) thì dữ liệu sẽ theo đó lưu vào CSDL dưới dạng Block Markup
Ngược lại, Dynamic Block sẽ sử dụng kiểu dữ liệu render loại Block theo yêu cầu từ phía người dùng, lúc này lệnh từ save.js sẽ bị bỏ qua.
Dưới đây là ví dụ về cách save.js xử lý khối preformatted core block
import { RichText, useBlockProps } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { content } = attributes;
return (
<pre { ...useBlockProps.save() }>
<RichText.Content value={ content } />
</pre>
);
}Lúc này từ trình Editor sẽ lưu vào kết quả khi ta soạn thảo xong
<!-- wp:preformatted -->
<pre class="wp-block-preformatted">This is some preformatted text</pre>
<!-- /wp:preformatted -->Và đây là đích đến từ đầu người dùng khi có yêu cầu.
<pre class="wp-block-preformatted">This is some preformatted text</pre>Dynamic Block là block động và sẽ có dữ liệu thay đổi thường xuyên tùy vào yêu cầu của người dùng, ví dụ khối core/latest-post – bài viết mới nhất
Theo Nam thì Dynamic Block khá giống với vòng loop post của cách làm giao diện truyền thống – Bạn có thể tham khảo tại link Nam trỏ tới để tìm hiểu về bài viết của anh Thạch Phạm.
Thông thường có thể xác định 1 block là Dynamic Block khi nó có sự xuất hiện của render callback hoặc có file render.php riêng
Lưu ý là render call back sẽ là file php bởi nó liên quan tới Server Rendering
function render_block_core_site_title( $attributes ) {
$site_title = get_bloginfo( 'name' );
if ( ! $site_title ) {
return;
}
$tag_name = 'h1';
$classes = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}";
if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
$classes .= ' has-link-color';
}
if ( isset( $attributes['level'] ) ) {
$tag_name = 0 === $attributes['level'] ? 'p' : 'h' . (int) $attributes['level'];
}
if ( $attributes['isLink'] ) {
$aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : '';
$link_target = ! empty( $attributes['linkTarget'] ) ? $attributes['linkTarget'] : '_self';
$site_title = sprintf(
'<a href="%1$s" target="%2$s" rel="home"%3$s>%4$s</a>',
esc_url( home_url() ),
esc_attr( $link_target ),
$aria_current,
esc_html( $site_title )
);
}
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classes ) ) );
return sprintf(
'<%1$s %2$s>%3$s</%1$s>',
$tag_name,
$wrapper_attributes,
// already pre-escaped if it is a link.
$attributes['isLink'] ? $site_title : esc_html( $site_title )
);
}Ở đoạn code trên hàm render_block_core_site_title sẽ lấy bloginfo là tên của trang
Tại database, block markup được lưu vào như sau
<!-- wp:site-title /-->Tại đầu Frontend của người dùng nó sẽ trả về kết quả
<h1 class="wp-block-site-title"><a href="https://www.wp.org" target="_self" rel="home">My WordPress Website</a></h1>Lưu ý là đoạn render callback kia khá phức tạp, nhưng bản chất nó chỉ xác định từng trường hợp và các thuộc tính cần có mà thôi
Đối với Dynamic Block thì hàm save.js sẽ trả về null tức là không có giá trị, điều này chỉ dẫn trình editor chỉ lưu những thuộc tính (attributes) mà thôi, thuộc tính này sẽ được truyền vào hàm rendering callback (render.php) nhằm thể hiện block theo cách mong muốn.
Khi hàm save là null, Block Editor sẽ bỏ qua bước xác minh, tránh trường hợp thường xuyên kiểm tra để thay đổi Markup.
Tương tự như vòng loop, dynamic block sẽ có một đoạn HTML như dạng backup nếu trong trường hợp
Dưới đây là ví dụ của Dynamic Block đối với plugins giúp gọi ra 1 vòng lặp bài viết (Post Loop)
import { registerBlockType } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';
registerBlockType( 'gutenberg-examples/example-dynamic', {
apiVersion: 3,
title: 'Example: last post',
icon: 'megaphone',
category: 'widgets',
edit: () => {
const blockProps = useBlockProps();
const posts = useSelect( ( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'post' );
}, [] );
return (
<div { ...blockProps }>
{ ! posts && 'Loading' }
{ posts && posts.length === 0 && 'No Posts' }
{ posts && posts.length > 0 && (
<a href={ posts[ 0 ].link }>
{ posts[ 0 ].title.rendered }
</a>
) }
</div>
);
},
} );Đoạn code này nằm trong file index.js, trong trường hợp không sử dụng block.json để lấy dữ liệu, mà khai báo trực tiếp luôn, lưu ý rằng dynamic block không có hàm save() để xử lý dữ liệu trước khi lưu vào Database (do vốn chúng sẽ lôi dữ liệu từ Backend ra xử lý – gọi bài viết, ảnh đại diện, logo của trang…)
Ở hàm này thì biến posts sẽ được gọi bằng hàm useSelect (từ thư viện @wordpress/data), trả về lựa chọn bản ghi hướng tới loại postType là post.
Kết quả của hàm edit sẽ trả về trình soạn thảo một danh sách bài viết
Tuy vậy như đã nói, Dynamic Block cần phải render ở đầu server thì mới trả ra kết quả ở trình Frontend – giao diện người dùng được.
<?php
/**
* Plugin Name: Gutenberg examples dynamic
*/
function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) {
$recent_posts = wp_get_recent_posts( array(
'numberposts' => 1,
'post_status' => 'publish',
) );
if ( count( $recent_posts ) === 0 ) {
return 'No posts';
}
$post = $recent_posts[ 0 ];
$post_id = $post['ID'];
return sprintf(
'<a class="wp-block-my-plugin-latest-post" href="%1$s">%2$s</a>',
esc_url( get_permalink( $post_id ) ),
esc_html( get_the_title( $post_id ) )
);
}
function gutenberg_examples_dynamic() {
// automatically load dependencies and version
$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');
wp_register_script(
'gutenberg-examples-dynamic',
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version']
);
register_block_type( 'gutenberg-examples/example-dynamic', array(
'api_version' => 3,
'editor_script' => 'gutenberg-examples-dynamic',
'render_callback' => 'gutenberg_examples_dynamic_render_callback'
) );
}
add_action( 'init', 'gutenberg_examples_dynamic' );2 hàm này sẽ được đặt ở plugin-name.php, một hàm để đăng ký index.js (chứa hàm edit) và xác định render_callback là hàm gutenberg_examples_dynamic_render_callback