前回はフックについてでした。
WordPressのブロック開発メモ その8 フック(アクションとフィルター)
今回はRedux的なデータ操作について紹介します。
セレクタとアクション
状態を取得するセレクタ
と変更をするアクション
があります。
実行するにはそれぞれselect()
とdispatch()
を使います。
Reduxの知識がある人は何となく想像つくと思いますが、
WordPressが独自に拡張したものなので使い方には違和感があるかもしれません。
これまで古いJavaScriptの書き方をしてきませんでしたが、
ブラウザ上で実行出来るメリットがあるので今回はそちらを使っていきます。
状態を取得するセレクタはwp.data.select()
を、
状態を変更するアクションはwp.data.dispatch()
を使います。
どちらも引数に名前空間を指定します。
早速使っていきます。
core/notices
まずselect()とdispatch()の両者を最も比較しやすい名前空間がcore/notices
でしょうか。
投稿ページでブラウザの開発者ツールを開き、
コンソールから以下を入力してエンターを押します。
wp.data.select('core/notices').getNotices()
すると通知一覧が表示されます。
でも実際には空の配列が。
これは当然でまだ通知が無いからです。
ではその通知ってなんやねん、ってことで通知を追加していきましょう。
以下をコンソールから入力して円ターキーを押します。
wp.data.dispatch('core/notices').createSuccessNotice('Kurage Success !!!');
wp.data.dispatch('core/notices').createWarningNotice('Kurage Warning...');
wp.data.dispatch('core/notices').createErrorNotice('Kurage Error !');
wp.data.dispatch('core/notices').createInfoNotice('Kurage Info.');
すると通知が4つ表示されます。
もう一度、getNotices()を使って一覧表示してみましょう。
wp.data.select('core/notices').getNotices()
今度は4つ分取得出来てます。
この通知は×ボタンをクリックすることで削除出来ます。
試しにInfoとWarningを削除してみましょう。
そしてもう一度
wp.data.select('core/notices').getNotices()
ErrorとSuccessの2つが取得出来ました。
今度はコードから削除してみましょう。
以下を入力・・・
wp.data.select('core/notices').getNotices().map(notice => {
wp.data.dispatch('core/notices').removeNotice( notice.id );
});
残り二つも消えました。
- removeNotice()の引数には通知の
id
を渡します。
このように、
getNotices()のような取得系はwp.data.select()
を使い、
createSuccessNotice()やremoveNotice()のような更新系はwp.data.dispatch()
を使います。
そしてその引数の名前空間(cpre/notices
)は通知に関するデータを扱えます。
もちろんこれは例に挙げやすかったのでこの名前空間を使いましたが、他にも複数の名前空間が用意されてます。
core
core
という名前だけあってWordPressの中核的なデータを扱う名前空間です。
主にエンティティの取得操作、Undo/Redoなどが存在。
Undo/Redo
Undo/Redoとは元に戻す
とやり直す
のことで、
セレクタには元に戻す、やり直すことがあるかどうかを論理値で返すhasUndo
/hasRedo
それらの情報を取得するのgetUndoEdit
/getRedoEdit
があります。
アクションにはそれらを実行するundo
、redo
があります。
投稿をちゃちゃっと編集して実験してみましょう。
編集した直後、元に戻す
はあってもやり直す
は無いはずです。
wp.data.select('core').hasUndo()
true
wp.data.select('core').hasRedo()
false
ただし、undo()をディスパッチした場合にはやり直す
が出現するはずです。
wp.data.dispatch('core').undo()
Promise {<fulfilled>: undefined}
wp.data.select('core').hasRedo()
true
ブロックパターン
ブロックパターンについては別途ググってください。
パターンカテゴリ
wp.data.select('core').getBlockPatternCategories();
パターン一覧
wp.data.select('core').getBlockPatterns();
エンティティレコード
REST API の結果を取得できます。
投稿タイプ一覧を取得
getEntitiesConfig(kind)
以下の例はpostType
を指定した時です。
wp.data.select('core').getEntitiesConfig('postType');
{kind: 'postType', baseURL: '/wp/v2/posts', baseURLParams: {…}, name: 'post', label: '投稿', …}
{kind: 'postType', baseURL: '/wp/v2/pages', baseURLParams: {…}, name: 'page', label: '固定ページ', …}
{kind: 'postType', baseURL: '/wp/v2/media', baseURLParams: {…}, name: 'attachment', label: 'メディア', …}
{kind: 'postType', baseURL: '/wp/v2/menu-items', baseURLParams: {…}, name: 'nav_menu_item', label: 'ナビゲーションメニューの項目', …}
{kind: 'postType', baseURL: '/wp/v2/blocks', baseURLParams: {…}, name: 'wp_block', label: '再利用ブロック', …}
{kind: 'postType', baseURL: '/wp/v2/templates', baseURLParams: {…}, name: 'wp_template', label: 'テンプレート', …}
{kind: 'postType', baseURL: '/wp/v2/template-parts', baseURLParams: {…}, name: 'wp_template_part', label: 'テンプレートパーツ', …}
{kind: 'postType', baseURL: '/wp/v2/navigation', baseURLParams: {…}, name: 'wp_navigation', label: 'ナビゲーションメニュー', …}
エンティティ系の第一引数はkind
(種類の意味)となってますが、これはpostType
やtaxonomy
があるようです。
結果を見れば大体どんな情報が得れるか理解できます。
エンティティレコードを取得
getEntityRecord(kind, name, key)
以下の例は種類が投稿タイプで、投稿タイプの名前がpost
で、主キーが335
の場合です。
wp.data.select('core').getEntityRecord('postType', 'post', 335);
投稿のデータですね、内部では@wordpress/api-fetch
を使ってREST APIにアクセスし投稿データを取得してます。
ちなみに最初に実行した時はundefined
が返って来ることに気づきましたか?
ブラウザを更新して、もう一度実行するとやっぱりundefined
が返ってきます。
実際に投稿データが返ってくるのはそれ以降実行した場合ですね。
このミステリーについては次回のリゾルバ
の項で解説します。
getEntityRecord()
と似たgetRawEntityRecord()
がありますがこちらはtitle
やcontent
などの結果が変わります。
Raw付の方は生のデータが入っているのに対し、Raw無しの方は生のデータとレンダリング後のデータが入ってます。
エディタ情報の取得
getEntityRecordEdits(kind, name, recordId)
以下の例は現在編集中の投稿IDが421
の時です。
wp.data.select('core').getEntityRecordEdits('postType', 'post', 421);
他にもいろんなセレクタやアクションがありますが多すぎるのでこの辺にしておきます。
大まかな使い方は分かったと思います。
ブロックでの使い方
これをいつものブロックで使います。
useSelect()とuseDispatch()があります。
import React, { useState } from 'react';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { Button, TextControl } from '@wordpress/components';
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
// @ts-ignore
const notices = useSelect(select => select(noticesStore).getNotices());
const { createSuccessNotice } = useDispatch(noticesStore);
const [message, setMessage] = useState('');
const addNotice = () =>
{
// @ts-ignore
createSuccessNotice(message);
}
return (
<div {...useBlockProps()}>
<h2>Notices</h2>
{
notices.map(_ => <p>{_.status} : {_.content}</p>)
}
<h2>Editor</h2>
<div>
<p>Message</p>
<TextControl value={message} onChange={e => setMessage(e)} />
<Button variant="primary" onClick={addNotice}>追加する</Button>
</div>
</div>
);
}
テキストコントロールにそれぞれHello Kurage!
, Kurage Attack!
, Kurage vs Hitode!
を入力してはボタンを押すを繰り返します。
通知が3つ表示されます。
ブロックには現在表示されている通知一覧を表示してます。
まず重要なのがストアの取得です。
import { store as noticesStore } from '@wordpress/notices';
この通知に関するデータは@wordpress/notices
にあるストアから取得することが出来ます。
これまでのようなセレクタの使い方をするにはuseSelect()
を使います。
const notices = useSelect(select => select(noticesStore).getNotices());
コールバックの引数でselect
を受け取り、そこにストアを渡します。
getNotices()
から通知一覧を取得してます。
一方アクションの場合はuseDispatch()
から取得します。
const { createSuccessNotice } = useDispatch(noticesStore);
useDispatch()にストアを渡し関数を取得出来ます。
今回はcreateSuccessNotice()
関数を取得しています。
ボタンをクリックした時、通知を追加する貯めに定義した関数addNotice()
により使用されます。
さてこのストア
なんですが、データ毎にストアが用意されています。
例えば名前空間がcore
なら@wordpress/core-data
から取得します。
例
import { store as coreStore } from '@wordpress/core-data';
import React, { useState } from 'react';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
// @ts-ignore
const config = useSelect(select => select(coreStore).getEntitiesConfig('postType'));
return (
<div {...useBlockProps()}>
<h2>Notices</h2>
{
config.map(_ => <p>{_.name} : {_.baseURL}</p>)
}
</div>
);
}
コアのgetEntitiesConfig
セレクタをブロックで描画しました。
やっていることはこれと一緒ですね。
wp.data.select('core').getEntitiesConfig('postType');
また型について文句言われる時は@ts-ignore
を使ったり、とりあえず以下の型定義をインストールするなどして対処します。
npm install --save-dev @types/wordpress__core-data
npm install --save-dev @types/wordpress__notices
npm install --save-dev @types/wordpress__data
高階コンポーネント
セレクタやアクションをコンポーネントから使えるようにする高階コンポーネントのwithSelect()
とwithDispatch()
についてです。
withSelect()
import React, { useState } from 'react';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { store as noticesStore } from '@wordpress/notices';
import { withSelect } from '@wordpress/data';
const Edit = (props: BlockEditProps<KurageExampleBlockProps> & any) =>
{
const { notices } = props;
return (
<div {...useBlockProps()}>
<h2>Notices</h2>
{
notices.map(_ => <div>{_.status} : {_.content}</div>)
}
</div>
);
}
// @ts-ignore
export default withSelect(select => {
const notices = select(noticesStore).getNotices();
return { notices };
})(Edit);
edit.tsx
を変更しました。
これまでのコンポーネントをwithSelect()
で拡張します。
export default withSelect(select => {
const notices = select(noticesStore).getNotices();
return { notices };
})(Edit);
引数からselect
を取得出来ます。
戻り値でリテラルオブジェクトを返すと、そのプロパティをコンポーネントのprops
から取得出来るようになります。
const { notices } = props;
withDispatch()
次にwithDispatch()
についてです。
import React from 'react';
import { useState } from '@wordpress/element';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { store as noticesStore } from '@wordpress/notices';
import { withSelect } from '@wordpress/data';
import { withDispatch } from '@wordpress/data';
import { Button, TextControl } from '@wordpress/components';
import { compose } from '@wordpress/compose';
const Edit = (props: BlockEditProps<KurageExampleBlockProps> & any) =>
{
// propsから createSuccessNotice と createErrorNotice も取得出来る
const { notices, createSuccessNotice, createErrorNotice } = props;
const [success, setSuccess] = useState('');
const [error, setError] = useState('');
const onSuccess = () => createSuccessNotice(success);
const onError = () => createErrorNotice(error);
return (
<div {...useBlockProps()}>
<h2>Notices</h2>
{
notices.map(_ => <div>{_.status} : {_.content}</div>)
}
<div>
Success:
<TextControl value={success} onChange={setSuccess} />
<Button variant="primary" onClick={onSuccess}>追加</Button>
</div>
<div>
Error:
<TextControl value={error} onChange={setError} />
<Button variant="primary" onClick={onError}>追加</Button>
</div>
</div>
);
}
// @ts-ignore
export default compose(
withSelect(select => {
const notices = select(noticesStore).getNotices();
return { notices };
}),
withDispatch(dispatch => {
const { createSuccessNotice, createErrorNotice } = dispatch(noticesStore);
return { createSuccessNotice, createErrorNotice }
})
)(Edit);
SuccessとErrorの通知を追加出来るようにしてます。
withDispatch()高階関数を使うことで引数からdispatch
を取得出来ます。
リテラルオブジェクトを返すとそれをコンポーネントのprops
から取得出来るようになります。
withDispatch(dispatch => {
const { createSuccessNotice, createErrorNotice } = dispatch(noticesStore);
return { createSuccessNotice, createErrorNotice }
})
以下がprops
から取得している箇所です。
const { notices, createSuccessNotice, createErrorNotice } = props;
今回はwithSelect()
とwithDispatch()
を2つ使ってるので可読性を上げるためcompose()
を使って合成してます。
次回はストア
についてです。