Angularのlinked signalとcomputed signalの違いを徹底解説
AngularのSignalsは、アプリケーションの状態管理を効率的に行うための重要な機能です。特に、linked signal
とcomputed signal
の使い分けは、開発者にとって重要な判断ポイントとなります。この2つの違いを詳しく解説していきます。
Signalsの基本をおさらい
まずは、Signalsの基本から確認しましょう。Signalsは、Angularの新しいリアクティブプログラミングの基盤となる機能です。基本的な使い方は以下のようになります:
// 基本的なsignal
const count = signal(0);
// computed signal
const doubledCount = computed(() => count() * 2);
// effect
effect(() => {
console.log(`カウントが変更されました: ${count()}`);
});
このように、SignalsはシンプルなAPIで状態管理を実現できます。しかし、linked signal
とcomputed signal
の違いは、意外と理解しづらい部分があります。
Signal、Computed Signal、Linked Signalの違い
AngularのSignalsには、3つの主要なタイプがあります。それぞれの特徴と使い方を詳しく見ていきましょう。
Signal(基本的なSignal)
Signalは、Angularのリアクティブプログラミングの基本的な構成要素です。単一の値を保持し、その値の読み取りと更新を行うことができます。また、他のSignalやUIコンポーネントがこのSignalの値の変更を監視し、必要に応じて更新を行うことができます。
// 基本的なsignalの使用例
const count = signal(0);
// 値の読み取り
console.log(count()); // 出力: 0
// 値の更新
count.set(1);
console.log(count()); // 出力: 1
// 値の更新(現在の値に基づく)
count.update(value => value + 1);
console.log(count()); // 出力: 2
Signalは、アプリケーションの状態を保持する基本的な単位として使用されます。シンプルな状態管理が必要な場合に最適です。
Computed Signal
Computed Signalは、他のSignalの値に基づいて計算を行うためのものです。他のSignalの値が変更されると、自動的に再計算が行われます。ただし、Computed Signalは読み取り専用であり、直接値を設定することはできません。また、以前の値にアクセスすることもできません。
// computed signalの使用例
const firstName = signal('太郎');
const lastName = signal('山田');
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // 出力: 太郎 山田
firstName.set('一郎');
console.log(fullName()); // 出力: 一郎 山田
Computed Signalは、依存しているSignalの値が変更されたときだけ再計算を行います。これは、パフォーマンスの観点からも重要な特徴です。例えば、以下のような場合に特に有用です:
// 内部的な動作の例
const a = signal(1);
const b = signal(2);
const c = computed(() => {
console.log('再計算が実行されました');
return a() + b();
});
// 初回の読み取り
console.log(c()); // 出力: 3, "再計算が実行されました"
// aの値が変更された場合
a.set(2);
console.log(c()); // 出力: 4, "再計算が実行されました"
// bの値が変更された場合
b.set(3);
console.log(c()); // 出力: 5, "再計算が実行されました"
Linked Signal
Linked Signalは、Computed Signalよりも柔軟性の高い機能を提供します。以前の値にアクセスできることや、直接値を設定できることが特徴です。また、より複雑な状態遷移や外部からのイベントに対応することも可能です。
// linked signalの使用例
const inputText = signal('');
const filteredText = linkedSignal(
inputText,
(current, previous) => {
// 現在の値と以前の値にアクセスできる
if (current.length < previous.length) {
return current; // 文字が削除された場合はそのまま
}
return current.toUpperCase(); // 文字が追加された場合は大文字に
}
);
Linked Signalの内部的な動作は、ソースSignalの値が変更されるたびにコールバック関数が実行され、現在の値と以前の値の両方が渡されるという特徴があります。コールバック関数の戻り値が新しい値として設定されます。
// 内部的な動作の例
const source = signal(0);
const linked = linkedSignal(
source,
(current, previous) => {
console.log(`現在の値: ${current}, 以前の値: ${previous}`);
return current * 2;
}
);
// 値の変更
source.set(1); // 出力: "現在の値: 1, 以前の値: 0"
console.log(linked()); // 出力: 2
source.set(2); // 出力: "現在の値: 2, 以前の値: 1"
console.log(linked()); // 出力: 4
どっちを使うべき?具体的な判断基準
では、実際の開発ではどちらを選択すべきでしょうか?以下に具体的な判断基準を示します:
computed signalを使うとき
Computed Signalは、単純な計算や変換が必要な場合に最適です。他のSignalの値に基づいて値を導出する場合や、以前の値にアクセスする必要がない場合に使用します。また、パフォーマンスが重要な場合にも適しています。
例えば、以下のようなケースで使用します:
// 合計金額の計算
const items = signal([
{ name: 'りんご', price: 100 },
{ name: 'バナナ', price: 200 }
]);
const total = computed(() =>
items().reduce((sum, item) => sum + item.price, 0)
);
この例では、商品リストの合計金額を計算しています。商品が追加されたり削除されたりすると、自動的に合計金額が再計算されます。このような単純な集計処理は、Computed Signalの得意とする分野です。
// フィルタリング
const searchTerm = signal('');
const filteredItems = computed(() =>
items().filter(item =>
item.name.includes(searchTerm())
)
);
この例では、商品リストを検索キーワードでフィルタリングしています。検索キーワードが変更されるたびに、フィルタリング結果が自動的に更新されます。このように、他のSignalの値に基づいて新しい値を導出する処理は、Computed Signalの典型的な使用例です。
linked signalを使うとき
Linked Signalは、以前の値と比較して処理を変えたい場合や、外部からのイベントで値を直接設定したい場合に最適です。また、より複雑な状態遷移や非同期処理の結果を扱う場合にも適しています。
例えば、以下のようなケースで使用します:
// Undo/Redo機能
const history = linkedSignal(
currentState,
(current, previous) => {
if (current.action === 'undo') {
return previous; // 前の状態に戻る
}
return current; // 新しい状態に進む
}
);
この例では、ユーザーの操作履歴を管理するUndo/Redo機能を実装しています。現在の状態と以前の状態を比較し、ユーザーが「元に戻す」操作を行った場合は以前の状態に戻り、「やり直す」操作を行った場合は新しい状態に進みます。このように、以前の状態にアクセスして処理を分岐させる必要がある場合は、Linked Signalが適しています。
// 非同期処理の状態管理
const apiState = linkedSignal(
fetchState,
async (current, previous) => {
if (current.status === 'loading') {
try {
const data = await fetchData();
return { status: 'success', data };
} catch (error) {
return { status: 'error', error };
}
}
return current;
}
);
この例では、API呼び出しの状態を管理しています。API呼び出しの状態(loading、success、error)に応じて、適切な処理を行います。非同期処理の結果を扱う場合や、外部からのイベントに応じて状態を変更する必要がある場合は、Linked Signalが適しています。
実践的な例:フォームのバリデーション
実際のプロジェクトでよく使う例として、フォームのバリデーションを紹介します。Linked Signalを使用することで、フォームの状態管理を効率的に行うことができます。
const formData = signal({
email: '',
password: ''
});
// バリデーション状態
const validationState = linkedSignal(
formData,
(current, previous) => {
const errors = {
email: current.email ? '' : 'メールアドレスは必須です',
password: current.password.length >= 8 ? '' : 'パスワードは8文字以上必要です'
};
// 以前の状態と比較して、エラーメッセージを更新
if (JSON.stringify(errors) !== JSON.stringify(previous?.errors)) {
return { ...current, errors };
}
return previous;
}
);
// フォームの送信状態
const submitState = linkedSignal(
validationState,
async (current, previous) => {
if (current.isSubmitting) {
try {
await submitForm(current);
return { ...current, isSubmitting: false, isSubmitted: true };
} catch (error) {
return { ...current, isSubmitting: false, error };
}
}
return current;
}
);
パフォーマンスの考慮点
Computed SignalとLinked Signalの選択は、パフォーマンスにも影響します。Computed Signalは依存関係を追跡するためのメモリが必要ですが、依存関係が変更されたときのみ再計算を行うため、パフォーマンスが優れています。一方、Linked Signalは以前の値を保持するためのメモリが必要で、ソースの値が変更されるたびにコールバックが実行されます。
AngularのランタイムはComputed Signalを自動的に最適化しますが、Linked Signalの場合は開発者が手動で最適化する必要がある場合もあります。
まとめ
Signal、Computed Signal、Linked Signalの違いを理解し、適切に使い分けることで、Angularアプリケーションの状態管理をより効率的に行うことができます。
シンプルな状態管理が必要な場合は基本的なSignalを使用し、他のSignalの値に基づいて計算を行う場合はComputed Signalを使用します。以前の値にアクセスする必要がある場合や、より複雑な状態遷移が必要な場合はLinked Signalを使用します。
また、パフォーマンスが重要な場合はComputed Signalが適していますが、外部からのイベントで値を直接設定したい場合はLinked Signalが適しています。
Signalsを適切に使い分けることで、より保守性の高いAngularアプリケーションを開発することができます。
それでは、また。
Discussion