TypeScript/JavaScript 用に状態管理のライブラリを作りました。
広く使ってもらうためというよりは、自分のアプリケーションで何度も同じコードを書きたくないのがライブラリ化のモチベーションです。
Inspired by repatch and ngrx/store
lacolaco/reactive-store の設計はrepatchとngrx/storeに影響を受けています。Angular でアプリケーションを書くことが多いので、RxJS フレンドリーなストアとして ngrx/store を使っていましたが、2 つの不満な点がありました。
- 現在の状態のスナップショットが取れない(非同期 API しか存在しない)
- Action と Reducer が冗長
代わりになるストアのライブラリを探したところ、repatch は自分がほしいものにとても近かったのですが、RxJS の Observable と互換性がないことと、subscribe できる単位がストア全体という部分が不満で、結局自分で作ることにしました。
import { Store } from "@lacolaco/reactive-store";
const store: Store<string> = new Store("initialState");
store.getValue(); //=> 'initialState';
store.subscribe(state => {});
store.patch(state => "updated!");
lacolaco/reactive-store の実装は型定義を除くと 30 行程度で、ほとんどは RxJS の BehaviorSubject の機能をそのまま使っています。追加したのは patch メソッドと select メソッドと Middleware です。
store.patch((state: T ) => T)
現在の状態から新しい状態を作る関数を渡します。Redux 的な文脈で言えば Reducer と似ています。repatch に影響を受けています。
store.select((state: T) => any)
ストアが管理している状態のいち部分だけの Observable を作るためのメソッドです。具体的には map して distinctUntilChanged した結果を返します。ngrx/store に影響を受けています。
import { Store } from "@lacolaco/reactive-store";
const store: Store<{ count: number }> = new Store({ count: 0 });
const count$: Observable<number> = store.select(state => state.count);
count$.subscribe(count => {
console.log(count);
});
store.patch(state => {
return {
count: state.count + 1
};
});
Middleware
Middleware は dispatch メソッドに介入する仕組みです。lacolaco/reactive-store では dispatch された新しい状態を Observable に流す一番基本の部分も Middleware と同列に扱われています。実装の参考になったのは Angular の HttpInterceptor です。
まず Middleware の前に、state を受け取り何かを返す関数として StateHandler
という型があります。
export type StateHandler = (state: any) => any;
そして Middleware は、StateHandler を受け取り StateHandler を返す関数です。
export type Middleware = (next: StateHandler) => StateHandler;
何もしない Middleware は次のように書けます。
const noopMiddleware = next => state => next(state);
大した実装ではないので詳しくはソースコードを読んで下さい。ユニットテストも書いてあります。具体的なユースケースとしては Logging などが考えられます。次の例ではすべての StateHandler が処理を終えたあとに、新しい state をコンソールに書き出しています。
import { Store } from "@lacolaco/reactive-store";
const loggingMiddleware = next => {
return state => {
const newState = next(state);
console.log(`[State]`, newState);
return newState;
};
};
const store = new Store(0, [loggingMiddleware]);
利点
- 実装が薄いので RxJS さえ信用できれば安心して使える
- RxJS のエコシステムと簡単に接続できる
- 型安全
もし興味があれば使ってみて Issue とかツイッターでフィードバックとかもらえるとうれしいです。