状態管理の部分が分からなすぎて今まで手をつけてなかったのですが、意を決してやってみます。
- とりあえず調べて記録した部分から公開していきます。
- めっちゃ基本的な部分も分かってませんが悪しからず。
Angular After Tutorialで学べること
- コンポーネントは単一責任原則にしたがって親子関係を分割する
- コンポーネントがもつべきでないビューと関係のない処理はサービスに移動する
- コマンド・クエリ分離原則にしたがって、副作用の発生し得る箇所を限定する
- ビューとの結合性が高く、単独の責務として定義しづらいビジネスロジックをUsecaseに集約し、API呼び出しや状態管理などのサービスの独立性を維持する
-
_state$
の_
の意味 -
queueScheduler
-
select<T>()
のの意味 -
distinctUntilChanged
-
firstValueFrom
- usecaseとは
ひとまず、何やってるのかほとんど理解できないstore.service.ts
の読解を行う。
store.service.ts
store.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, queueScheduler } from 'rxjs';
import { map, distinctUntilChanged } from 'rxjs/operators';
import { State, initialState } from '../shared/state.model';
@Injectable({
providedIn: 'root'
})
export class StoreService {
private _state$ = new BehaviorSubject<State>(initialState);
constructor() { }
update(fn: (state: State) => State) {
const current = this._state$.value;
queueScheduler.schedule(() => {
this._state$.next(fn(current));
});
}
select<T>(selector: (state: State) => T) {
return this._state$.pipe(
map(selector),
distinctUntilChanged(),
)
}
}
プロパティ
_state$
- Observable型の変数の末尾に
$
をつけるのは知ってた。 - privateな変数の頭に
_
(アンダースコア)をつけることがあるそう。 - あとは予約語を変数名に使いたいときに
_
をつけることがあるそう? -
_state$
でググるとgithubのコードとか結構出てくるので、状態管理をするときによく使うのだろうか?
BehaviorSubject
- 「Observableに対し値を流すためのデータソース」の役割を担うクラス。
-
Subject
が値を保持しないのに対し、BehaviorSubject
は最後に更新された値を保持する。 - インスタンス生成時に値の初期値を設定(今回で言えば
initialState
)
update
store.service.ts
update(fn: (state: State) => State) {
const current = this._state$.value;
queueScheduler.schedule(() => {
this._state$.next(fn(current));
});
}
update(fn: (state: State) => State){ }
- 関数の引数に関数を渡せる。
(state: State) => State
- 関数内のコードが式1つだけの場合は、中カッコ
{}
とreturn
が省略できる。 -
fn
は、State型の引数state
を受け取って、関数内でなんやかんやし、State型の何かを返す関数。
this._state$.next(fn(current));
-
現在の
_state$
になんやかんや手を加えて、また_state$
に流している。 -
この「なんやかんや」は、
fn
がどんな関数なのかによる。 -
よく分からんけど、
fn
によってcurrent state
を好きなように変えられるのがミソで、再利用がしやすそう。 -
queueScheduler
- よく分からん・・・
呼び出し側
user-list.usecase.ts
async fetchUsers() {
const users = await this.userApi.getAllUsers();
this.store.update(state => ({
...state,
userList: {
...state.userList,
items: users
}
}));
}
setNameFilter(nameFilter: string) {
this.store.update(state => ({
...state,
userList: {
...state.userList,
filter: {
nameFilter
}
}
}));
}
update
の引数で渡している関数について、
fetchUsers
ならstate
内のitems
のみを、setNameFilter
ならfilter
のみを更新できるようになっている。
state => ({ })
- 戻り値がオブジェクトリテラルの場合は、オブジェクトリテラルをカッコ
()
で囲む必要がある。
select
_state$
から必要な情報を取り出すメソッド
store.service.ts
select<T>(selector: (state: State) => T) {
return this._state$.pipe(
map(selector),
distinctUntilChanged(),
)
}
<T>
- ジェネリクスは、コードの共通化と型の安全性を両立するための言語機能。
-
<T>
は型変数名の定義で、慣習としてTがよく使われるがAでもTypeでも構わない。
distinctUntilChanged
- 直近最後に流れてきた値と異なる場合のみ値を流す
呼び出し側
user-list.usecase.ts
get users$() {
return this.store
.select(state => state.userList)
.pipe(
map(({ items, filter }) =>
items.filter(user =>
(user.first_name + user.last_name).includes(filter.nameFilter)
)
)
);
}
get filter$() {
return this.store.select(state => state.userList.filter);
}
ジェネリクスを使わない場合、users$
用のselectUsers
とfilter$
用のselectFilter
を用意しないといけないところ、ジェネリクスのおかげでselect
ひとつにまとめられた(?)