この記事はバージョン Spring ’26 において執筆しています。
現在の動作と異なる場合がありますので、ご認識おきください。
SalesforceのLightning Web Components(LWC)は、モダンなWeb標準に基づく強力なフレームワークですが、アプリケーションの規模が大きくなるにつれて「状態管理(State Management)」というフロントエンド特有の課題に直面します。
従来のLWCでは、コンポーネント内のデータ管理にJavaScriptのクラスプロパティや@trackデコレータを使用してきました。しかし、@trackによるリアクティビティはコンポーネント内部に限定されており、オブジェクトや配列の変更を深く監視するに留まります。
複数のコンポーネント間でデータを共有しようとした場合、従来は以下のような手法が取られてきました。
- プロップスドリリング: 親から子へ
@apiでデータを渡し、子から親へはCustomEventを都度発行する強結合なバケツリレー。 - Lightning Message Service (LMS): DOM階層が異なる無関係なコンポーネント間の通信には非常に有効ですが、密結合な単一のアプリケーションフロー(例えばマルチステップの入力フォームなど)内で状態を共有するために多用すると、予測不可能な再レンダリングやメモリリークを引き起こすリスクがあります。
こうした「UIとビジネスロジックの混在」や「複雑なイベント連鎖」を根本から解決するためにSalesforceがベータ版として提供を開始したのが、公式の状態管理ライブラリである@lwc/stateです。
本連載では全3回にわたり、この次世代アーキテクチャを実務に導入するためのステップを解説します。第1回となる今回は、基本となるコアプリミティブの概念と、Lightning Experience(アプリケーションページやレコードページなど)に配置して実際に動作を確認できる「単一コンポーネントでの状態管理」の完全なコードをご紹介します。
本機能はSpring ’26においてベータ版であり、Experience Cloudでの動作は未対応となっています。
今回の記事で作成したコンポーネントの全ソースコードをGitHubで公開しています。ぜひ Clone して、あなたの組織で動かしてみてください。
@lwc/stateとは?パラダイムシフトを理解する
@lwc/stateは、最新のWebフレームワークで普及している「Signals(シグナル)」の概念に似たリアクティブな状態管理を提供します。
このライブラリの最大の目的は、アプリケーションのデータ(状態)とそれを操作する関数(アクション)を、UIコンポーネントのレンダリングロジックから完全に分離し、専用のJavaScriptモジュールとして一元管理することです。状態が変更されると、LWCのリアクティビティシステムと自動的に連動し、そのデータを参照しているコンポーネントだけが効率的に再描画されます。
4つのコアプリミティブ(基本構成要素)
状態マネージャーを構築するためには、@lwc/stateが提供する以下の4つの基本関数(プリミティブ)を理解する必要があります。
defineState(状態の定義)
状態マネージャーを作成するためのエントリーポイントです。内部ロジックを定義するコールバック関数を受け取り、状態インスタンスを生成するためのファクトリ関数を返します。これにより、状態マネージャーのインスタンスごとに独立したスコープが保証されます。atom(リアクティブな単一データ)
状態を構成する最も基本的なビルディングブロックです。atom(初期値)として定義され、値が変更されると、それを監視しているコンポーネントや派生状態に変更を通知します。computed(自動計算される派生状態)
1つ以上のatomや他のcomputed値に依存し、依存元が変化した時にのみ自動的に再計算されるインテリジェントな値です。UI側で重い計算処理(フィルタリングやバリデーションなど)を行う必要がなくなります。setAtom(状態の安全な変異)atomの値を安全に更新するための唯一の公式メソッドです。アーキテクチャの制約上、直接的な代入はできず、必ずこの関数を経由して値を変更します。
LWCのコンポーネント設計哲学において、HTMLは「構造(UI)」、JavaScriptは「振る舞い」、そしてXMLは「構成(メタデータ)」を担うと定義されています。@lwc/stateを導入することで、JSファイルからさらに「データの状態とビジネスロジック」を切り離し、専用のAPIモジュールとして完全に独立させることが可能になります。
実践:シンプルなリアクティブフォーム
それでは、実際にコードを書いてみましょう。今回は「入力された名前に応じて、リアルタイムに保存ボタンの有効/無効が切り替わる」シンプルなプロフィール入力コンポーネントを作成します。
フォルダ構成
ベストプラクティスに従い、状態管理を行うための純粋な「APIモジュール」と、画面表示を行う「UIコンポーネント」の2つのフォルダを作成します。すべて force-app/main/default/lwc/ 配下に作成します。
force-app/main/default/lwc/
├── formStateManager/ <-- 状態管理APIモジュール (UIなし)
│ ├── formStateManager.js
│ └── formStateManager.js-meta.xml
│
└── userProfile/ <-- UIコンポーネント (画面配置用)
├── userProfile.html
├── userProfile.js
└── userProfile.js-meta.xml
状態管理モジュールの作成 (formStateManager)
このモジュールは画面を持たないため、HTMLファイルは不要です。
formStateManager.js
状態データとアクション(更新処理)をカプセル化します。
import { defineState } from '@lwc/state';
export default defineState(({ atom, computed, setAtom }) => {
// 1. atom: 入力値を保持するリアクティブな状態
const userName = atom('');
// 2. computed: 派生状態(文字が未入力であればボタンを無効化する判定)
const isSaveDisabled = computed([userName], (nameVal) => {
return nameVal.length === 0;
});
// 3. Actions: 状態を安全に更新するための関数
const updateName = (newName) => {
setAtom(userName, newName);
};
// 4. UIコンポーネントに公開するAPI
return {
userName,
isSaveDisabled,
updateName
};
});
formStateManager.js-meta.xml
このコンポーネント自体を画面に配置することはないため、isExposedはfalseに設定します。
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<isExposed>false</isExposed>
</LightningComponentBundle>
UIコンポーネントの作成 (userProfile)
作成した状態マネージャーを読み込み、UIを構築します。
userProfile.html
状態マネージャーの値をstate.value.xxx.valueという形式でバインドします。
<template>
<div class="slds-box slds-theme_default slds-m-around_small">
<h2 class="slds-text-heading_medium slds-m-bottom_medium">プロフィール入力</h2>
<lightning-input
label="ユーザー名"
value={state.value.userName}
onchange={handleNameChange}>
</lightning-input>
<div class="slds-m-top_medium">
<p>現在の入力値: {state.value.userName}</p>
<lightning-button
label="保存"
variant="brand"
disabled={state.value.isSaveDisabled}
onclick={handleSave}
class="slds-m-top_small">
</lightning-button>
</div>
</div>
</template>
userProfile.js
UIコンポーネント側のJSは、ユーザーの操作(イベント)を状態マネージャーのアクションに横流しするだけの「薄い」記述になります。
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
// 作成した状態マネージャーをインポート
import formStateManager from 'c/formStateManager';
export default class UserProfile extends LightningElement {
// 状態マネージャーのインスタンスを生成して保持
state = formStateManager();
handleNameChange(event) {
// 状態の直接書き換えはせず、公開されたアクションを呼び出す
this.state.value.updateName(event.target.value);
}
handleSave() {
// 保存処理のシミュレーション(本来はここでApexコールなどを行う)
this.dispatchEvent(
new ShowToastEvent({
title: '成功',
message: `保存しました: ${this.state.value.userName}`,
variant: 'success'
})
);
}
}
userProfile.js-meta.xml
このコンポーネントをLightning Experience画面に配置できるようにするためには、isExposedをtrueにし、対象(targets)にlightningCommunity__Pageを指定する必要があります。
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>User Profile (State Demo)</masterLabel>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
画面での実際の動き
コードを組織にデプロイし、[設定] から「Lightning アプリケーションビルダー」を開きます。任意のアプリケーションページやホームページを作成(または編集)し、左側のコンポーネントパネルから「User Profile (State Demo)」を画面にドラッグ&ドロップして保存します。
- 初期表示: 画面を開いた直後、入力欄は空です。この時、
isSaveDisabledというcomputedプロパティがtrueと評価されるため、「保存」ボタンは非活性(グレーアウト)状態で描画されます。 - 文字の入力: ユーザーが入力欄に文字を打つたびに、
updateNameアクションが実行され、内部のatomが更新されます。 - 即時リアクティビティ:
atomが更新された瞬間にcomputedが再評価され、文字が入っていれば「保存」ボタンがアニメーションのように即座に活性化(青色に点灯)します。
これらの一連のリアクティブな動作が、userProfile.js内に複雑なif文やバリデーションのロジックを書くことなく実現できている点が、@lwc/state最大のメリットです。


第1回のまとめと次回予告
今回は@lwc/stateの基礎と、Lightning Experienceで動かすためのファイル構成について解説しました。状態とUIが完全に分離されることで、コードが読みやすくなり、単体テストも劇的に書きやすくなります。
しかし、@lwc/stateの真骨頂は「複数のコンポーネント間で、同じ状態を安全に共有・同期する」機能にあります。
次回、「第2回:コンポーネント間の状態共有(Contextパターンの実践)」では、今回の概念を応用し、プロップスドリリング(バケツリレー)を完全に排除した実践的な「マルチステップ入力フォーム」の構築方法をご紹介します。お楽しみに!
参考URL
Manage State Across LWC Components with State Managers (Beta)
Reactivity for Fields, Objects, and Arrays




読者の声