この短い記事では、外部ライブラリを使用せずに信号を使用してコンポーネントを構築する方法を説明したいと思います。もちろん、NgRx のようなものはコードをより堅牢にするために大きな役割を果たしますが、まずはシンプルに始めましょう!
まず最初に、すべての状態をシグナルで定義します。
export class TodoListComponent { todos = signal<Todo[]>([]); }
入力にも同じことが当てはまります。コンポーネントに入力が必要な場合は、Angular の新しい input() 関数を使用してそれを宣言します。これにより、シグナルも得られます。それがルートパラメータである場合は、input.required().
を使用します。次に、別の状態から導出できる状態を表示したい場合は、常に computed を使用します。
completedTodos = computed(() => this.todos().filter(t => t.completed));
私を知っている方なら、私がクラス メソッド内で直接非同期副作用を実行することをどれほど嫌っているかご存知でしょう...?
export class TodoListComponent { todoService = inject(TodoService); toggleTodo(id: string) { this.todoService.toggle(id).subscribe(newTodo => ...); } }
なぜ聞くのですか?なぜなら、そのメソッドが副作用を直接開始するものである場合 (この場合、subscribe を呼び出します)、バックプレッシャー を制御することができないからです。
バックプレッシャーは次のように要約できます。前の通話が終了していないときにユーザーが ToDo を切り替えたらどうなりますか?
たとえば、次のような問題が数多くあります。
RxJS を知っていれば (そしてこれを読んでいるなら、もう知っているはずです!)、最初の問題は 4 つの平坦化演算子 (mergeMap、concatMap、switchMap、exhaustMap) を使用して簡単に解決できることがわかります。
RxJS をよく知っている場合は、groupBy と呼ばれる素晴らしい演算子を使用して 2 番目の問題を解決できることがわかります!
しかし、この利点をすべて利用するには、メソッドではなく、Observable ソースが必要です。
サブジェクトを、開いた (完了していない) 空の Observable のように考えてください。これはカスタム イベントを表すのに最適なツールです。
コンポーネント内のすべてのイベントは、サブジェクトによって表すことができます:
export class TodoListComponent { ... toggleTodo$ = new Subject<string>(); deleteTodo$ = new Subject<string>(); addTodo$ = new Subject<void>(); }
その後、テンプレートは必要に応じて、メソッドを呼び出す代わりに単にそれらを呼び出すことができます。
<button (click)="deleteTodo$.next(todo.id)">delete</button>
ソースが Observable になったので、親愛なるオペレーターを使用できます。エフェクトを作成しましょう。
コンポーネントが破棄されたときに takeUntilDestroyed() オペレーターを使用してエフェクトをクリーンアップできるように、コンストラクター内でエフェクトを定義するのが好きです。たとえば、次のようになります。
constructor() { this.addTodo$.pipe( concatMap(() => this.todoService.add()) takeUntilDestroyed() ).subscribe(newTodo => this.todos.update(todos => [...todos, newTodo])); }
ここでは、応答の順序を保持するために concatMap を使用し、todo が順番に追加されるようにしています。これは、同時呼び出しがないことを意味します。これは追加操作には最適だと思いますが、他の呼び出しには間違った選択である可能性があります。たとえば、GET リクエストの場合は、ユースケースに応じて、通常、exhaustMap または switchMap を使用する方が良いです。
私は 悲観的更新 と呼ばれるアプローチも使用しています。これは、呼び出しが終了するのを待って内部状態を更新することを意味します。これは個人的な好みです。 todo をすぐに追加し、API 呼び出しがエラーになった場合は catchError を使用して元に戻すことができます。
次に、信号と組み合わせて使用することを目的とした Angular の実際のエフェクト関数があります。私はこの関数を同期タスクに使用します。たとえば、URL 内のパラメータが変更された場合 (新しいエンティティ ID を参照する)、新しいエンティティでフォームを更新する必要がある場合があります:
// This comes from the router id = input.required<string>(); // Always stores the current invoice information currentInvoice = toSignal(toObservable(this.id).pipe( switchMap(id => this.invoiceService.get(id)) )); constructor() { effect(() => { // Assuming the 2 structures match, every time we browse // to a new invoice, the form gets populated this.form.patchValue(this.currentInvoice()); }) }
この手法ではバックプレッシャーを制御できないことに注意してください。この種のことについては問題ありませんが、覚えておいてください。だからこそ、バグのないアプリを作成するには RxJS が依然として必要です。または、この複雑さを内部で抽象化する別のライブラリ。
信号で表す多くの状態は、技術的には派生非同期状態と見なすことができます。たとえば、Todo リストはサーバーからの派生状態とみなすことができます:
// Trigger this when you need to refetch the todos fetchTodos$ = new Subject<void>(); todos = toSignal(toObservable(this.fetchTodos$).pipe( switchMap(id => this.todoService.getAll()) ));
このアプローチは、TanStack Query などのライブラリで使用されるアプローチと似ており、新しいデータが必要なときにクエリを手動で無効にします。言い換えれば、ミューテーションごとに常にサーバーにアクセスすることになります。
これはシナリオによっては良い場合もありますが、考慮すべき点が 2 つあります。
要するに、通常は推奨しません。そして私はいつもと言いました! :)
我希望你喜歡這篇短文!總結一下:
我確信,如果您遵循這些原則,您的應用程式將會更容易維護!
以上がSignals を使用して Angular コンポーネントを構造化する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。