anti scroll

ブラウザと小説の新しい関係を模索する

Angular + Ngrx所感

先月から試験的にAngular+Ngrxを使って縦書き文庫を作り直してみる、ということをしていたんですけど、パソコンが壊れバックアップも取ってなかったので、全て消え去りました(この記事はChromebookで書いてます)。

真面目にやると一年ぐらいかかる作業なので、一ヶ月程度の作業で済んだのは不幸中の幸い?なんでしょうかね。。。

Angular+Ngrxの感想としては、コツを掴むまでかなり時間がかかるということと、fluxなんでaction/reducer/state/effectの開発ループを回すのが面倒くさいなあ、ということです。

正直なところ「このまま続けるべきか?」と思ってたところなんで、もしかしたら中止できてよかったのかも。。。

学習コスト

TypescriptとRxに関しては多少ですが経験があったのでなんとかなりそうだったのですが、それ以外に覚えないといけないことが沢山あって大変でした。

よくAngular+Ngrxは大規模なチーム開発に最適などと言われますが、学習コストを嫌ってVueという選択をする人が多いのも、なんとなくわかるような気がします。

Ngrxについて

Ngrxを採用すると、とにかくRxでEffectsを書きまくることになるのですが、ここで割とわかりにくい地雷を踏みまくってしまいました。

特に危険なのはストリームを止めちゃう位置にcatchErrorを書いちゃったり、逆にストリームを止めるべきところでdispatch:falseを指定しないで無限ループを仕込んじゃったりとかです。

地雷1 catchErrorの場所

まずEffects中のcatchErrorですが、actions$直下のpipeではなく、map中のpipeで宣言しないといけません。

@Effects()
getFoo$ = this.actions$.pipe(
  ofType("foo"),
  switchMap(action => 
    return fetchFoo(action.payload.id).pipe(
      map(result => new GetFooDone(result.body)),
      // エラーは内側のpipeで補足する
      catchError(error => of(new GetFooFail({error})));
    );
  ),
  // ここに書いてはいけない
  // catchError(error => new GetFooFail({error}))
);

上の例は、fooに対応するactionを直下のactions$からフィルタリングし、そのデータを元にfetchFooでサーバーから何かしらのデータを取得しようとするコードです。

この処理の失敗を、action$直下のcatchErrorで補足すると、fetchFooでエラーが起きたときだけじゃなく、大本のactions$で起きたエラーも補足してしまいます。

上の例だと、GetFooFailというアクションを出したいのは、fetchFooが失敗したときだけですよね。

気をつけましょう。

地雷2 別のアクションに変化しない副作用

Effectsが新しいActionを返さないのにdispatch:falseをデコレーターに設定しないと、永遠にその処理が繰り返されます。

@Effect({dispatch: false})
logFoo$ = this.actions$.pipe(
  ofType("foo"),
  tap(_ => console.log("foo"))
);

上の処理で{dispatch: false}を忘れると、無限ループです。

自分はCPUがうるさくてパソコンが熱くなったので気づきました。自動ビルドで開発してたのですが、開いてる先のタブがいつのまにかCPU使用率100%になっていたりとか。

これがパソコンの熱暴走を起こし、寿命を縮めたんじゃないのか、と今では疑っています。

地雷3 Angular Materialとの相性

ngrx/storeの状態をangular materialのコンポーネントのディレクティブにダイレクトに反映させてしまうと、アニメーションが効かなかったり(例えばチェックボックスとか)、一部のUI要素が正しく計算できなくて表示できなくなったり(例えばタブコンポーネントの下線)みたいなことがあります。

なのでマテリアルコンポーネントを使用するときは、コンテナーというUIクッションを用意して、マテリアル要素はその下に置くようにします。

で、マテリアルなコンポーネントを含む下層のコンポーネントには「状態のコピー」を渡し、ローカルでその状態を操作させるようにします。そうしないと、アニメーションが正しく機能しないからです。その上で、子供の最新状態は@Outputを使って吸い上げます。

つまり一元管理された状態を使用して制御できるのはコンテナーUIまでで、そこから下は状態のコピーが作成されることになります。全てが理想通り、とはいかないですね。

余談1

ちなみに壊れたMacbookの修理はざっくり7万ぐらいとか言われたんですけど、まあ買い直すよりかは、と思って依頼しちゃいました。

でも年末で忙しいから14営業日かかるとのこと。。。今からだと来年の10日前後?

余談2

そういえば最近、中国の開発者が「Immutableなんてほとんどのケースで必要ない。本当の未来はshadow-domを使い、素直にmutableな構造で、かつVueっぽいものだ」みたいなコンセプトでなんかフレームワーク作ってて、すごいスターをもらってた記憶があるのですが、名前を忘れました。