前回はワードプレスのデータを扱うストアの紹介をしました。
WordPressのブロック開発メモ その10 Redux ストアの作成
今回、この投稿は若干難易度が高いので飛ばしてもらっても構いません。
ストア機能の一部(リゾルバ)は非同期でREST API
にアクセスします。
WordPressにはより簡単にREST APIにアクセスできるようにapiFetch()
が用意されてます。
より内部的なことを詳しく知りたい人向けに、ここでは標準のfetch()
を使う方法と@wordpress/api-fetch
のapiFetch()
を使う方法を紹介します。
投稿などの情報を取得したければ普通にgetEntityRecords()
など用意されたものを使った方がいいです。
REST API
その前にREST API
についてです。
REST APIを知っていることが前提です。
知らない人向けに軽く解説すると、サーバとHTMLではなくJSONをやり取りしてデータを取得したり操作したりしようって機能です。
WordPressはREST API
を公開してます。
詳しい仕様については以下を参照してください(私も全部読んでないです)。
https://developer.wordpress.org/rest-api/
https://ja.wp-api.org/reference/posts/
本来ブロック開発の外の話ですが、一応最低限の使い方だけ先に説明します。
始めに
もしWordPressを以下のURLで運用していたとします。
https://kurage-worker.com
そこからwp-json/wp/v2
に移動します。
https://kurage-worker.com/wp-json/wp/v2
アクセスすると何やらブラウザ画面にJSON
が素で表示されます。
ブラウザに直接表示すると見づらいので開発者ツールから以下のコードを打ってJSONを整形して表示させて見ます。
(async () => console.log(JSON.stringify(await (await fetch('https://kurage-worker.com/wp-json/wp/v2')).json(), null, "\t")))();
上記はhttps://kurage-worker.com/wp-json/wp/v2
にアクセスしデータ取ってきてJSONを整形してコンソールに出力します。
上がそのまま表示されたJSON、下が整形して表示されたJSON。
投稿一覧
さらにposts
に移動します。
https://kurage-worker.com/wp-json/wp/v2/posts'
(async () => console.log(JSON.stringify(await (await fetch('https://kurage-worker.com/wp-json/wp/v2/posts')).json(), null, "\t")))();
これで投稿10件分のデータが取得出来ました。
ただちょっと分かりづらいので投稿の中からIDとタイトルを抜き出して表示させることにしましょう。
(async () => {
const json = (await (await fetch('https://kurage-worker.com/wp-json/wp/v2/posts')).json());
console.log( json.map(_ => `${_.id}: ${_.title.rendered}`).join("\n") );
})();
645: WordPressのブロック開発メモ その10 Redux ストアの作成
629: WordPressのブロック開発メモ その9 データの操作(セレクタとアクション&ディスパッチ)
626: WordPressのブロック開発メモ その10ー1 リゾルバの解読
615: WordPressのブロック開発メモ その4ー2 ダイナミックブロック
580: WordPressのブロック開発メモ その8 フック(アクションとフィルター)
573: WordPressのブロック開発メモ その7 HOC(高階コンポーネント)
528: WordPressのブロック開発メモ その6 スロット
484: WordPressのブロック開発メモ その3 編集
470: WordPressのブロック開発メモ その2 TypeScript編
460: WordPressのブロック開発メモ その1
カテゴリ一覧
次はカテゴリ一覧を取得してみましょう。
categories
に移動します。
https://kurage-worker.com/wp-json/wp/v2/categories
(async () => {
const json = (await (await fetch('https://kurage-worker.com/wp-json/wp/v2/categories')).json());
console.log( json.map(_ => `${_.id}: ${_.name} (${_.count})`).join("\n") );
})();
13: Doctrine (10)
7: PHP (10)
5: React (8)
8: Slim (6)
4: TypeScript(JavaScript) (2)
11: Ubuntu (3)
2: WordPress (4)
24: WordPress-Block (15)
6: その他 (4)
18: アルゴリズム (3)
カテゴリのID、タイトル、アイテム数の列をが表示されました。
タグ一覧
タグ一覧を表示してみましょう。
tags
に移動します。
https://kurage-worker.com/wp-json/wp/v2/tags
(async () => {
const json = (await (await fetch('https://kurage-worker.com/wp-json/wp/v2/tags')).json());
console.log( json.map(_ => `${_.id}: ${_.name}`).join("\n") );
})();
14: certificate
20: Docker
25: Gutenberg
21: Hyper-V
15: openssl
9: PHP
23: React
22: ReduxToolkit
12: ubuntu
16: オレオレ認証局
他にも固定ページのpages
や、ユーザー情報のusers
などいろんな種類があります。
クエリ
結果を制御することが出来ます。
最大数
投稿一覧では最大10記事まで取得出来ました。
クエリにper_page
を追加することで最大数を変更出来ます。
https://kurage-worker.com/wp-json/wp/v2/posts?per_page=15
645: WordPressのブロック開発メモ その10 Redux ストアの作成
629: WordPressのブロック開発メモ その9 データの操作(セレクタとアクション&ディスパッチ)
626: WordPressのブロック開発メモ その10ー1 リゾルバの解読
615: WordPressのブロック開発メモ その4ー2 ダイナミックブロック
580: WordPressのブロック開発メモ その8 フック(アクションとフィルター)
573: WordPressのブロック開発メモ その7 HOC(高階コンポーネント)
528: WordPressのブロック開発メモ その6 スロット
484: WordPressのブロック開発メモ その3 編集
470: WordPressのブロック開発メモ その2 TypeScript編
460: WordPressのブロック開発メモ その1
592: WordPressのブロック開発メモ 目次!
544: WordPressのブロック開発メモ その6-1 スロットとプラグイン
519: WordPressのブロック開発メモ その5 ツールバーとサイドバー
515: WordPressのブロック開発メモ その4ー1 アイコン一覧
493: WordPressのブロック開発メモ その4 コンポーネント
今度は15件の投稿が表示されました。
カテゴリを選択
カテゴリを指定して投稿一覧を取得出来ます(11はUbuntuカテゴリ)。
https://kurage-worker.com/wp-json/wp/v2/posts?categories=11
203: プライベートなSSL、オレオレ認証局メモ
72: VirtualHost の DocumentRoot を HomeDirectory にしたい。
63: Ubuntuを使っていてネットワークの反応がとにかく遅い
ただしcategories
にはカテゴリのIDを指定します。
以下のように複数指定することも出来ます(2はWordPressカテゴリ)。
https://kurage-worker.com/wp-json/wp/v2/posts?categories[]=11&categories[]=2
326: WordPressの記事を書く時のmoreが面倒臭い! カテゴリ一覧表示で「続きを読む」を自動的に。
298: WordPressのプラグイン「WP Githuber MD」のPrism.jsでハイライトされない、行番号表示されない問題
203: プライベートなSSL、オレオレ認証局メモ
72: VirtualHost の DocumentRoot を HomeDirectory にしたい。
63: Ubuntuを使っていてネットワークの反応がとにかく遅い
21: アイキャッチ一覧をカードデザインで表示する。
14: wp_nav_menu()の挙動がおかしい!?
タグを選択
タグを指定して投稿一覧を取得出来ます(25はGutenberg)
https://kurage-worker.com/wp-json/wp/v2/posts?tags=25
645: WordPressのブロック開発メモ その10 Redux ストアの作成
629: WordPressのブロック開発メモ その9 データの操作(セレクタとアクション&ディスパッチ)
626: WordPressのブロック開発メモ その10ー1 リゾルバの解読
- 管理者のぐーたらな性格で投稿時タグを指定し忘れて3つしか取得出来てない・・・orz
ソート
カテゴリをソートします。
orderby
でソートする項目、order
で昇順・降順を指定します。
以下はカテゴリ名でソートした場合です。
https://kurage-worker.com/wp-json/wp/v2/categories?orderby=name&order=desc
1: 未分類 (0)
19: 技術的なメモ (3)
18: アルゴリズム (3)
6: その他 (4)
24: WordPress-Block (15)
2: WordPress (4)
11: Ubuntu (3)
4: TypeScript(JavaScript) (2)
8: Slim (6)
5: React (8)
https://kurage-worker.com/wp-json/wp/v2/categories?orderby=name&order=asc
13: Doctrine (10)
7: PHP (10)
5: React (8)
8: Slim (6)
4: TypeScript(JavaScript) (2)
11: Ubuntu (3)
2: WordPress (4)
24: WordPress-Block (15)
6: その他 (4)
18: アルゴリズム (3)
他にもいろいろありますが割愛します。
アクション
REST APIなのでアクションの種類でCRUD出来ます。
実運用で実験するわけにはいかないのでhttps://kurage-worker.com
からローカル環境のhttps://kurage
に移してます。
まずはテスト用の投稿をします。
特定のIDの投稿を取得する
アクション: GET
https://kurage/wp-json/wp/v2/posts/427
このようにpostsの後に投稿ID(今回は427
)を指定することで特定の投稿を取得出来ます。
(async () => {
const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts/427')).json());
console.log( `${post.id}: ${post.title.rendered}` );
})();
特定のIDの投稿を編集する
アクション: POST
https://kurage/wp-json/wp/v2/posts/427
今回はタイトルを変更するのでbody
にtitle
: Update Post!!!
を追加したリテラルオブジェクトを渡しました。
(async () => {
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '**********' },
body: JSON.stringify( { title: 'Update Post!!!' } )
}
const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts/427', options)).json());
console.log( post );
})();
ヘッダーにJSONであること、
そしてX-WP-Nonce
にナンスを渡すことをしないといけないのがちょっと面倒ですが変更出来ました。
'X-WP-Nonce': '**********'
ナンスは伏せてますが、自身のナンスの取得はwp_create_nonce('wp_rest')
で出来ます。
ここで得られたナンスは一定時間有効ですが時間帯によっては直ぐ変更されてしまいます。
開発環境下であれば以下のように一時的にthe_content
フィルタで取得するのもありですね。
プレビューから記事下部にナンスが表示されます。
add_filter('the_content', function($content){
$nonce = wp_create_nonce('wp_rest');
return $content . "<div>$nonce</div>";
});
特定のIDの投稿を削除する
アクション: DELETE
https://kurage/wp-json/wp/v2/posts/427
アクションをDELETE
にしてアクセスすると削除出来ます。
こちらでもナンスが必要なんす!
(async () => {
const options = {
method: 'DELETE',
headers: { 'X-WP-Nonce': '**********' },
}
const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts/427', options)).json());
console.log( post );
})();
投稿を新規追加する
アクション: POST
https://kurage/wp-json/wp/v2/posts
(async () => {
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '**********' },
body: JSON.stringify( { title: 'New post item!!', content: 'REST APIで投稿しちゃうぞ!', status: 'publish' } )
}
const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts', options)).json());
console.log( post );
})();
content
で本文を投稿出来ますが、ブロックエディタとは相性悪そうですね。
status
でpublish
にしないと公開
にならず、初期値の下書き
になってしまいます。
纏め
このようにアクションとURLの組合せでリスト取得、対象取得、対象変更、対象削除、新規作成が出来ます。
操作 | アクション | URL |
---|---|---|
一覧取得 | GET | https://kurage/wp-json/wp/v2/posts |
対象取得 | GET | https://kurage/wp-json/wp/v2/posts/427 |
対象変更 | POST | https://kurage/wp-json/wp/v2/posts/427 |
対象削除 | DELETE | https://kurage/wp-json/wp/v2/posts/427 |
新規作成 | POST | https://kurage/wp-json/wp/v2/posts |
対象変更に関してはPUT
が一般的だと思うのですがドキュメントにはそう書いてあったのでPOST
にしました。
PUTでもいけるようですが。
他にもカテゴリやタグなどのエンドポイントもありますが割愛します。
@wordpress/api-fetch
先ほどはJavaScriptの標準関数fetch()
を使ってきました。
今回はWordPressで用意されている@wordpress/api-fetch
のapiFetch
を使って操作してみましょう。
apiFetch
を使うとJSON.stringify()
を使ったり、ナンスを設定したりContent-Type
を設定する手間が省けます。
まずはブラウザから軽く実行してみましょう。
const { apiFetch, url } = wp;
const { addQueryArgs } = url;
const test = async () =>
{
const posts = await apiFetch({
path: '/wp/v2/posts'
});
console.log(posts.map(_ => `${_.id}: ${_.title.rendered}`));
const query = {
per_page: 5,
categories: [11, 2],
orderby: 'title',
order: 'desc'
};
const qposts = await apiFetch({
path: addQueryArgs('/wp/v2/posts', query)
});
console.log(qposts.map(_ => `${_.id}: ${_.title.rendered}`));
}
test();
apiFetch()
のオプションでpath
を指定します。
投稿一覧の場合は/wp/v2/posts
を指定します(カテゴリの場合は/wp/v2/categories
)
const posts = await apiFetch({ path: '/wp/v2/posts' });
投稿が10件取得されます。
またaddQueryArgs()
を使うことでクエリ付きのパスを作成出来ます。
const qposts = await apiFetch({ path: addQueryArgs('/wp/v2/posts', query) });
このクエリ部分を実際に表示したのが以下になります。
const query = {
per_page: 5,
categories: [11, 2],
orderby: 'title',
order: 'desc'
};
wp.url.addQueryArgs('/wp/v2/posts', query);
/wp/v2/posts?per_page=5&categories%5B0%5D=11&categories%5B1%5D=2&orderby=title&order=desc
edit.tsx
では以下のようにも書けます。
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 apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
import { Button } from '@wordpress/components';
const test = async () =>
{
const posts = await apiFetch({
path: '/wp/v2/posts'
});
// @ts-ignore
console.log(posts.map(_ => `${_.id}: ${_.title.rendered}`));
const query = {
per_page: 5,
categories: [11, 2],
orderby: 'title',
order: 'desc'
};
const qposts = await apiFetch({
path: addQueryArgs('/wp/v2/posts', query)
});
// @ts-ignore
console.log(qposts.map(_ => `${_.id}: ${_.title.rendered}`));
}
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
Hello World
<Button variant="primary" onClick={e => test()}>実験</Button>
</div>
);
}
もしPOST
などの独自のアクションを設定するには、オプションにmethod
とdata
を追加します。
以下は新しく投稿する場合のコードです。
wp.apiFetch({
path: '/wp/v2/posts',
method: 'POST',
data: {
title: 'Add New Post!!!',
content: 'Hello Kurage Network!',
status: 'publish',
}
})
以前のfetch()
を使った時と比較してみましょう。
(async () => {
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': '**********' },
body: JSON.stringify( { title: 'New post item!!', content: 'REST APIで投稿しちゃうぞ!', status: 'publish' } )
}
const post = (await (await fetch('https://kurage/wp-json/wp/v2/posts', options)).json());
console.log( post );
})();
apiFetch()
を使うとfetch()
に比べ次のようなメリットがあります。
- JSON.stringify()を使う必要がない
- Content-Typeでapplication/jsonを指定する必要がない
- X-WP-Nonceにナンスを指定する必要がない
追記
ただし、
1
及び2
においてはデフォルトのハンドラを使用していること、POSTのリクエストでdata
を設定していること。
- 他にもあるかもしれませんがそこまで調べてません!
1と2はデフォルトハンドラで実装積み、3についてはミドルウェアで自動的に追加されます。
どういうことかを次で説明します。
apiFetch()の仕組み
apiFetch()は独自のミドルウェアの仕組みを実装してます。
デフォルトでいくつかのミドルウェアがあり、さらに独自に追加することも出来ます。
ミドルウェアは順に実行されていき、最後にハンドラ
を実行します。
このハンドラは内部でwindow.fetch()
を実行します。
ミドルウェア
ミドルウェアを追加することでハンドラ
の実行前後を制御できるようになります。
ミドルウェアを追加するにはapiFetch.use()
を使用します。
デフォルトハンドラ
apiFetch()
はデフォルトのハンドラを実行します。
このハンドラではオプションのdata
はJSON(String)化され、ヘッダーにはContent-Type
にapplication/json
が設定されます。
そしてそのオプションを元にwindow.fetch()
を実行します。
ハンドラを上書したい場合はapiFetch.setFetchHandler()
を使用します。
サンプル
これまでのことを踏まえ、ミドルウェアとハンドラを追って見ましょう
edit.tsx
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 apiFetch, { } from '@wordpress/api-fetch';
import { Button } from '@wordpress/components';
const test = async () =>
{
// A middleware
apiFetch.use((options, next) => {
console.log('A Middleware');
options = {
...options,
headers: {
...options.headers,
'X-A': 'AAA'
}
};
return next(options);
});
// B middleware
apiFetch.use((options, next) => {
console.log('B Middleware');
options = {
...options,
headers: {
...options.headers,
'X-B': 'BBB'
}
};
return next(options);
});
// C middleware
apiFetch.use((options, next) => {
console.log('C Middleware');
options = {
...options,
headers: {
...options.headers,
'X-C': 'CCC'
}
};
return next(options);
});
// Handler(ドキュメントを読んでください。面倒なので非同期関数を渡してます)
apiFetch.setFetchHandler(async options => {
console.log('Handler');
console.log(options);
// デフォルトのハンドラではoptionsを元にwindow.fetch()を実行する
return 'DATAAAAAAAAAAAAAAA';
});
const value = await apiFetch({ path: 'myPath' });
console.log(value);
};
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
Hello World
<Button variant="primary" onClick={e => test()}>実験</Button>
</div>
);
}
ミドルウェアにはA middleware
, B Middleware
, C Middleware
を追加しました。
ミドルウェアはそれぞれヘッダーにX-A
, X-B
, X-C
を追加する単純な機能です。
ハンドラも上書しました。
デフォルトではwindow.fetch()
を実行しますがaxios
などに書き換えるような使い方をします。
今回は適当に文字列返しているだけの実装です。
コンソールの出力を見るとC
B
A
Handler
の順で実行されているのが確認出来ます。
ミドルウェアは追加順とは逆の順で実行され(next()よりも上にあるコードの場合)、最後にハンドラが実行されているのが確認出来ます。
オプションのヘッダーにはX-A
X-B
X-C
が追加されていることが確認できます。
Nonce
【メモ】
ナンスはミドルウェアとして実装してあり、そのミドルウェアはapiFetch.createNonceMiddleware()
関数にナンスを渡すことで作成出来る。
このミドルウェアが実行される時、ヘッダにX-WP-Nonce
が設定される。
ブラウザ上で以下を実行すると
wp.apiFetch.nonceMiddleware
すでにNonceのミドルウェアが設定してあることに気づく。
ところがGutenbergのソースコード内ではその設定箇所が見当たらない。
ブラウザから検索するとpost.php
内に記述してあった。
WordPress本体から検索するとscript-loader.php
にてインラインで書かれているみたいだ。
ナンスの取得
ところでナンスを取得するにはどうすればいいのでしょう!?
結構どうでもいい話なんですが、以前全く別の件でワードプレスのREST API
にアクセスする時にナンスの取得に難儀したことがありました。
調べていたら以下のようなコードがありました。
なるほど。
まずはナンスを取得するページを取得して、
後はこんな感じで取得すればいいのね。
fetch('https://kurage-worker.com/wp-admin/admin-ajax.php?action=rest-nonce')
.then(_ => _.text())
.then(_ => console.log(_));
これでノンスが簡単に取得出来ました。
次回はブロックの作成についてです。