シンプルでリアクティブなマイナスカウンターを作成して、WordPressのInteractivity APIの仕様・挙動を確認します。
※リアクティブ:データの変化に自動で反応して UI を更新する仕組み
雛形を生成する
Interactivity APIに対応したブロックの雛形をインストールする。
npx @wordpress/create-block@latest minus-counter --template @wordpress/create-block-interactive-template
各ファイルの初期設定を確認する
package.json
{
"scripts": {
"build": "wp-scripts build --experimental-modules",
"start": "wp-scripts start --experimental-modules"
},
}
minus-counter.php
ブロックを登録する。
function create_block_minus_counter_block_init() {
register_block_type_from_metadata( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_minus_counter_block_init' );
block.json
Interactivity APIの有効化と、モジュールとしてview.jsをビルドする設定。
{
"supports": {
"interactivity": true
},
"viewScriptModule": "file:./view.js"
}
stateをグローバルで管理する場合
edit.js
import { useBlockProps } from "@wordpress/block-editor";
export default function Edit() {
const blockProps = useBlockProps();
return (
<p {...blockProps}>
<p className="counter-text">0</p>
<button className="counter-button">Minus Counter</button>
</p>
);
}
render.php
<div
data-wp-interactive="minus-counter"
<?php echo get_block_wrapper_attributes(); ?>>
<p
data-wp-text="state.count"
class="counter-text"></p>
<button
data-wp-on--click="actions.countDown"
class="counter-button">Minus Counter</button>
</div>
data-wp-interactive=”minus-counter”
このディレクティブが書かれたHTML要素をWordPress Interactivity API の「インタラクティブな対象」として識別し、JavaScript 側と連携するための識別子。この場合、view.jsで定義したminus-counterというstoreと紐づいていることを表す。ラッパー要素にだけ指定すれば子要素に伝播する。
data-wp-text=”state.count”
WordPress Interactivity API の「テキストバインディング」機能。このHTML要素のテキスト内容を、state.count の値にリアルタイムに反映するように指示している。
view.js
/**
* WordPress dependencies
*/
import { store } from "@wordpress/interactivity";
const { state } = store("minus-counter", {
state: {
count: 10,
},
actions: {
countDown: () => {
return state.count--;
},
},
callbacks: {},
});
state.count
初期値は10
actions.countDown
state.countの値をマイナス1して呼び出し元に返す
このブロックの場合、stateがグローバルな状態のため、複数個ブロックを挿入すると値が連動してしまう。
stateをローカルで管理する場合
stateをローカルスコープで定義する場合、stateが独立した値として保持される。そのため、同一ブロックを複数回挿入してもstateの値が連動しない。
edit.js
import { useBlockProps } from "@wordpress/block-editor";
export default function Edit() {
const blockProps = useBlockProps();
return (
<p {...blockProps}>
<p className="counter-text">0</p>
<button className="counter-button">Minus Counter</button>
</p>
);
}
変更なし。
render.php
<?php
$context = [
'minusCount' => 10,
]
?>
<div
data-wp-interactive="minus-counter"
<?php echo wp_interactivity_data_wp_context($context); ?>
<?php echo get_block_wrapper_attributes(); ?>>
<p
data-wp-text="state.count"
class="counter-text"></p>
<button
data-wp-on--click="actions.countDown"
class="counter-button">Minus Counter</button>
</div>
wp_interactivity_data_wp_context($context);
PHP で定義した値(配列$context)を JSON に変換して、HTML の data-wp-context 属性として出力する。HTMLの出力結果は次のようになる。
data-wp-context='{"minusCount":10}'
このdata-wp-context によって、JavaScript 側の getContext() で minusCount を参照できるようになる。
なぜこのような仕組みなのかというと、PHP と JavaScript は実行されるタイミングと環境が完全に別物のため、render.php(PHP)で定義した配列に、view.js(JavaScript)から直接アクセスできないから。PHPがHTMLソースとして書き出すことで、JavaScript への値の受け渡しができるようになっている。
また、wp_interactivity_data_wp_context( $context ) を使って data-wp-context を HTML に出力すると、Interactivity API の「ローカル state(スコープ)」として動作するようになる。なぜなら、data-wp-context があると、このブロック専用の状態オブジェクト(context)を内部的に作り、その DOM 要素を「state のローカルスコープ」として認識するため。
PHP側で $context = [ ‘minusCount’ => 10 ]; を定義して data-wp-context に出力するのは、「初期値を各ブロックごとに独立して持たせたい(=共有したくない)」から。
view.js
/**
* WordPress dependencies
*/
import { store, getContext } from "@wordpress/interactivity";
const { state } = store("minus-counter", {
state: {
get count() {
return getContext().minusCount;
},
},
actions: {
countDown: () => {
getContext().minusCount--;
},
},
});
store()
関数
- 第1引数:このストアの名前(識別子)。HTML 側の data-wp-interactiveと結びつく
- 第2引数:ストアの中身(振る舞い) を定義。状態・アクション・コールバックなどをまとめたオブジェクト
state: {
get count() {
return getContext().minusCount;
}
},
state
は「表示に使うリアクティブな状態」を定義count
は getter なので、HTML 側のdata-wp-text="state.count"
で使えるget count()
は、get
キーワードを使って「count という名前のゲッター関数」を定義- 呼び出すときは関数なのに()をつけない。data-wp-text=”state.count”
- 見た目はプロパティ(変数)だが、裏で関数が呼び出されている
getContext().minusCount
にアクセスすることで、PHP から渡されたdata-wp-context='{"minusCount": 10}'
を参照する
actions: {
countDown: () => {
getContext().minusCount--;
}
}
actions
は「ユーザーの操作に反応して状態を変更する関数群」- 例:
data-wp-on--click="actions.countDown"
としてボタンにバインド可能 - この関数では
getContext().minusCount--
によって状態を 1 減らす
カウントがマイナスになったら処理を停止する
view.js
const { state } = store("minus-counter", {
state: {
get count() {
return getContext().minusCount;
},
get isDisabled() {
return getContext().minusCount <= 0;
},
},
actions: {
countDown: () => {
if (getContext().minusCount > 0) {
getContext().minusCount--;
}
},
},
});
render.php
<?php
$context = [
'minusCount' => 10,
];
?>
<div
data-wp-interactive="minus-counter"
<?php echo wp_interactivity_data_wp_context($context); ?>
<?php echo get_block_wrapper_attributes(); ?>>
<p
data-wp-text="state.count"
class="counter-text"></p>
<button
data-wp-on--click="actions.countDown"
data-wp-bind--disabled="state.isDisabled"
class="counter-button">Minus Counter</button>
</div>
HTMLにdisabled属性を付与する処理
view.jsにて、isDisabledというゲッター関数を定義し、HTMLから取得したminusCountが0以下になった時にtrueを返す。
render.phpから生成されたHTMLにて、data-wp-bind–disabled=”state.isDisabled”のstate.isDisabledがtrueになると、disabled属性が自動的に出力される。data-wp-bind--属性名="式"
は、その式が true
の場合にのみ、該当の HTML 属性が出力される。
カウントが0以下になった場合に処理を停止する
view.jsに(getContext().minusCount > 0)という条件を追加し、minusCount が0以下になった場合には、minusCountの更新は行わないためカウントダウンが停止する。