宣言型と命令型
宣言型プログラミングは、プログラムが達成すべき何を説明します。これは、結果を達成するためのコマンドや手順を明示的に列挙することなく、望ましい結果に焦点を当てます。それに対して、命令型プログラミングは、プログラムの状態を操作するための各ステップを明示的に述べることによって、タスクを達成する方法を指定します。
命令型アプローチ
ウェブ開発の初期には、命令型アプローチが主流でした。この方法は、JavaScriptを使用してDOMを手動で更新し、変更を反映させることを含みます。
例えば、次のような2つのボタンと段落を含むインタラクティブなブロックを考えてみましょう:
- 表示/非表示ボタン: 段落の可視性を切り替え、「アクティブ化」ボタンを有効/無効にします。
- アクティブ化/非アクティブ化ボタン: 段落のテキストと色を「アクティブ」(緑)と「非アクティブ」(赤)の間で切り替えます。
<div id="my-interactive-plugin">
<button
id="show-hide-btn"
aria-expanded="false"
aria-controls="status-paragraph"
>
show
</button>
<button id="activate-btn" disabled>activate</button>
<p id="status-paragraph" class="inactive" hidden>this is inactive</p>
</div>
<script>
const showHideBtn = document.getElementById( 'show-hide-btn' );
const activateBtn = document.getElementById( 'activate-btn' );
const statusParagraph = document.getElementById( 'status-paragraph' );
showHideBtn.addEventListener( 'click', () => {
if ( statusParagraph.hasAttribute( 'hidden' ) ) {
statusParagraph.removeAttribute( 'hidden' );
showHideBtn.textContent = 'hide';
showHideBtn.setAttribute( 'aria-expanded', 'true' );
activateBtn.removeAttribute( 'disabled' );
} else {
if ( statusParagraph.classList.contains( 'active' ) ) {
statusParagraph.textContent = 'this is inactive';
statusParagraph.classList.remove( 'active' );
activateBtn.textContent = 'activate';
}
statusParagraph.setAttribute( 'hidden', true );
showHideBtn.textContent = 'show';
showHideBtn.setAttribute( 'aria-expanded', 'false' );
activateBtn.setAttribute( 'disabled', true );
}
} );
activateBtn.addEventListener( 'click', () => {
if ( activateBtn.textContent === 'activate' ) {
statusParagraph.textContent = 'this is active';
statusParagraph.classList.remove( 'inactive' );
statusParagraph.classList.add( 'active' );
activateBtn.textContent = 'deactivate';
} else {
statusParagraph.textContent = 'this is inactive';
statusParagraph.classList.remove( 'active' );
statusParagraph.classList.add( 'inactive' );
activateBtn.textContent = 'activate';
}
} );
</script>
ご覧のように、各条件に対して、変更されたDOM内のすべてをJavaScriptを使用して修正する必要があり、前の状態を考慮する必要があります。
宣言型アプローチ
宣言型アプローチは、何が起こるべきかに焦点を当てることでプロセスを簡素化します。UIは状態の変化に応じて自動的に更新されます。以下は、インタラクティビティAPIの宣言型アプローチを使用した類似の例です:
<div id="my-interactive-plugin" data-wp-interactive="myInteractivePlugin">
<button
data-wp-on--click="actions.toggleVisibility"
data-wp-bind--aria-expanded="state.isVisible"
data-wp-text="state.visibilityText"
aria-controls="status-paragraph"
>
show
</button>
<button
data-wp-on--click="actions.toggleActivation"
data-wp-bind--disabled="!state.isVisible"
data-wp-text="state.activationText"
>
activate
</button>
<p
id="status-paragraph"
data-wp-bind--hidden="!state.isVisible"
data-wp-class--active="state.isActive"
data-wp-class--inactive="!state.isActive"
data-wp-text="state.paragraphText"
>
this is inactive
</p>
</div>
import { store } from '@wordpress/interactivity';
const { state } = store( 'myInteractivePlugin', {
state: {
isVisible: false,
isActive: false,
get visibilityText() {
return state.isVisible ? 'hide' : 'show';
},
get activationText() {
return state.isActive ? 'deactivate' : 'activate';
},
get paragraphText() {
return state.isActive ? 'this is active' : 'this is inactive';
},
},
actions: {
toggleVisibility() {
state.isVisible = ! state.isVisible;
if ( ! state.isVisible ) state.isActive = false;
},
toggleActivation() {
state.isActive = ! state.isActive;
},
},
} );
この宣言型の例では、UIは現在の状態に基づいて自動的に更新されます。開発者が行う必要があるのは、必要な状態、派生状態、状態を変更するアクション、およびDOMのどの部分が状態のどの部分に依存しているかを宣言することだけです。フレームワークは、DOMを常に現在の状態と同期させるために必要なすべての更新を行います。ロジックは、フレームワークによって制御される要素の数に関係なく、シンプルで保守可能なままです。
バグを見つけられますか?
命令型の例では、教育目的のために意図的にバグが導入されています。見つけられますか?簡単ではありません!
答えを見せてください!
「表示」ボタンが最初に押され、その後「アクティブ化」ボタンが押され、最後に「非表示」ボタンが押された場合、inactive
クラスがstatusParagraph.classList.add('inactive');
を使用して追加されません。したがって、次回ユーザーが「表示」を押すと、段落は赤で表示されません。
この種のバグは、すべての条件を手動で制御する必要があるため、命令型コードでは非常に一般的です。一方、宣言型コードでは、フレームワークがDOMの更新を管理し、何も忘れないため、これらのバグは存在しません。
宣言型アプローチの利点
示されたように、命令型アプローチは詳細な手順とDOMの直接操作を必要とし、インタラクティビティの複雑さが増すにつれて迅速に複雑になり、保守が難しくなります。可能な状態と要素が増えるほど、追加する必要のある条件付きロジックが増え、コードが指数関数的に複雑になります。一方、宣言型アプローチは、状態を管理し、フレームワークにDOMの更新を処理させることでプロセスを簡素化します。これにより、より読みやすく、保守可能で、スケーラブルなコードが得られます。
リアクティビティ
インタラクティビティAPIは、リアクティビティを活用することで宣言型フレームワークです。リアクティブシステムでは、データの変更が自動的にユーザーインターフェースの更新をトリガーし、ビューが常にアプリケーションの現在の状態を反映することを保証します。
リアクティビティの仕組み
インタラクティビティAPIは、細かい粒度のリアクティビティシステムを使用しています。以下はその仕組みです:
- 1. リアクティブ状態: インタラクティビティAPIでは、グローバル状態とローカルコンテキストの両方がリアクティブです。これは、これらのデータソースのいずれかが変更されると、それに依存するUIの部分が自動的に更新されることを意味します。
- グローバル状態: これは、インタラクティブブロック全体でアクセスできるグローバルデータです。
- ローカルコンテキスト: これは、特定の要素とその子要素に特有のローカルデータです。
- 派生状態: 基本的な状態プロパティに加えて、依存関係が変更されると自動的に更新される計算プロパティを定義できます。
詳細については、グローバル状態、ローカルコンテキスト、派生状態の理解ガイドを訪れて、インタラクティビティAPIでのさまざまなタイプのリアクティブ状態の扱い方を学んでください。
- 2. アクション: これらは、通常イベントハンドラーによってトリガーされる関数で、グローバル状態またはローカルコンテキストを変更します。
- 3. リアクティブバインディング: HTML要素は、
data-wp-bind
、data-wp-text
、またはdata-wp-class
のような特別な属性を使用してリアクティブ状態値にバインドされます。 - 4. 自動更新: アクションがグローバル状態またはローカルコンテキストを変更すると、インタラクティビティAPIは、その状態に依存するDOMのすべての部分(直接または派生状態を通じて)を自動的に更新します。
これらの概念を前の例を見直すことで分解してみましょう:
const { state } = store( 'myInteractivePlugin', {
state: {
isVisible: false,
isActive: false,
get visibilityText() {
return state.isVisible ? 'hide' : 'show';
},
// ... other derived state
},
actions: {
toggleVisibility() {
state.isVisible = ! state.isVisible;
},
// ... other actions
},
} );
このコードでは:
isVisible
とisActive
は基本的な状態プロパティです。visibilityText
はisVisible
が変更されると自動的に更新される派生状態です。toggleVisibility
は状態を変更するアクションです。
HTMLバインディングは次のようになります:
<button
data-wp-on--click="actions.toggleVisibility"
data-wp-text="state.visibilityText"
data-wp-bind--aria-expanded="state.isVisible"
>
show
</button>
リアクティビティが実際にどのように機能するかは次のとおりです:
- 1. ボタンがクリックされると、
toggleVisibility
アクションがトリガーされます。 - 2. このアクションは
state.isVisible
を更新します。 - 3. インタラクティビティAPIはこの変更を検出し、自動的に:
可変性と不変性
多くの他のリアクティブフレームワークとは異なり、インタラクティビティAPIは、グローバル状態またはローカルコンテキストを更新する際に不変性の使用を要求しません。オブジェクトや配列を直接変更でき、リアクティビティシステムは期待通りに機能します。これにより、多くの場合、より直感的で簡潔なコードが得られます。
例えば、次のように配列に新しいアイテムを追加できます:
const { state } = store( 'myArrayPlugin', {
state: {
list: [ 'item 1', 'item 2' ],
},
actions: {
addItem() {
// Right:
state.list.push( 'new item' );
// Wrong:
state.list = [ ...state.list, 'new item' ]; // Don't do this!
},
},
} );
他のフレームワークで行うように新しい配列を作成したり、スプレッド演算子を使用したりする必要はありません。インタラクティビティAPIはこの変更を検出し、state.list
に依存するUIの部分を更新します。
リアクティブな副作用
UIを自動的に更新することに加えて、インタラクティビティAPIは、data-wp-watch
のようなディレクティブを使用してリアクティブデータが変更されたときに副作用を実行することを許可します。副作用は、ログ記録、API呼び出し、またはUIに直接結びついていないアプリケーションの他の部分を更新するなどのタスクに役立ちます。
次のようにdata-wp-watch
を使用する例を示します:
<div
data-wp-interactive="myCounterPlugin"
data-wp-context='{ "counter": 0 }'
data-wp-watch="callbacks.logCounter"
>
<p>Counter: <span data-wp-text="context.counter"></span></p>
<button data-wp-on--click="actions.increment">Increment</button>
</div>
store( 'myCounterPlugin', {
actions: {
increment() {
const context = getContext();
context.counter += 1;
},
},
callbacks: {
logCounter: () => {
const context = getContext();
console.log( `The counter is now: ${ context.counter }` );
},
},
} );
この例では:
- 1.
data-wp-context
ディレクティブは、値が0
であるプロパティcounter
を持つローカルコンテキストを追加します。 - 2.
data-wp-watch
ディレクティブはcallbacks.logCounter
に設定されています。 - 3.
context.counter
が変更されるたびに、logCounter
コールバックが実行されます。 - 4.
logCounter
コールバックは、現在のカウンターをコンソールにログします。
これにより、データの変更に応じて自動的に実行される宣言型の副作用を作成できます。data-wp-watch
の他の使用例には、次のようなものがあります:
- データが変更されたときに
localStorage
にデータを保存する。 - 分析イベントを送信する。
- アクセシビリティの目的でフォーカスを変更する。
- ページタイトル、メタタグ、または
<body>
属性を更新する。 - アニメーションをトリガーする。
結論
インタラクティビティAPIを使用し続ける中で、状態、アクション、副作用の観点で考えることを忘れないでください。データを定義し、どのように変更されるべきかを説明し、インタラクティビティAPIに残りを処理させてください。このメンタルシフトには時間がかかるかもしれませんが、特により命令型のプログラミングスタイルに慣れている場合は、これを受け入れることで、インタラクティビティAPIの真のダイナミックでインタラクティブなWordPressブロックを作成するための完全な可能性を引き出すことができます。