状態管理のライブラリを作りました

TypeScript/JavaScript用に状態管理のライブラリを作りました。

広く使ってもらうためというよりは、自分のアプリケーションで何度も同じコードを書きたくないのがライブラリ化のモチベーションです。

Inspired by repatch and ngrx/store

lacolaco/storeの設計はrepatchngrx/storeに影響を受けています。Angularでアプリケーションを書くことが多いので、RxJSフレンドリーなストアとしてngrx/storeを使っていましたが、2つの不満な点がありました。

  • 現在の状態のスナップショットが取れない(非同期APIしか存在しない)
  • ActionとReducerが冗長

代わりになるストアのライブラリを探したところ、repatchは自分がほしいものにとても近かったのですが、RxJSのObservableと互換性がないことと、subscribeできる単位がストア全体という部分が不満で、結局自分で作ることにしました。

import { Store } from '@lacolaco/store';
const store: Store<string> = new Store('initialState');
store.getValue(); //=> 'initialState';
store.subscribe(state => { });
store.dispatch(state => 'updated!');

lacolaco/storeの実装は型定義を除くと30行程度で、ほとんどはRxJSのBehaviorSubjectの機能をそのまま使っています。追加したのはdispatchメソッドとselectメソッドとMiddlewareです。

store.dispatch((state: T ) => T)

現在の状態から新しい状態を作る関数を渡します。Redux的な文脈で言えばReducerと似ています。repatchに影響を受けています。

store.select((state: T) => any)

ストアが管理している状態のいち部分だけのObservableを作るためのメソッドです。具体的にはmapしてdistinctUntilChangedした結果を返します。ngrx/storeに影響を受けています。

import { Store } from '@lacolaco/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.dispatch(state => {
return {
count: state.count + 1
};
});

Middleware

Middlewareはdispatchメソッドに介入する仕組みです。lacolaco/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/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とかツイッターでフィードバックとかもらえるとうれしいです。