きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

配列型のJSONから gRPC(protobuf) 用のモックデータを読み込む

gRPC のハンズオンがてら適当なサーバを書いてみて、ある程度ハンドラが充実してきたので今度は適当なデータをJSONで用意して読み込んでサーバで返すようにしよう、としたときに意外と大変だったという記録です。

動作するサーバコードは以下のリポジトリに置いてあります。

色々試したところそもそもやっていることの筋が悪いような気がしたのですが、一応動いたは動いたので記録として残しておきます。

サーバ・データ定義

サービス定義

サービス定義は以下のようなよくある ToDo アプリです。

syntax = "proto3";

import "google/protobuf/timestamp.proto";

service ReadOnlyTodo {
  rpc GetTodos(GetTodosRequest) returns (GetTodosResponse) {}
}

message Todo {
  int32 id = 1;
  string title = 2;
  google.protobuf.Timestamp deadline = 3;
}

message GetTodosRequest {
}

message GetTodosResponse {
  repeated Todo todos = 1;
}

サーバ定義

サーバ定義は以下のようにしています。

type todoServer struct {
    todos []*pb.Todo
    mu    sync.Mutex
}

今回はこの todosJSONのデータをロードするための関数を生やそうと試行錯誤しています。ロードするJSONは以下のようなものです。

[
  {
    "id": 1,
    "title": "First ToDo",
    "deadline": "2018-09-01T12:34:56.789Z"
  },
  {
    "id": 2,
    "title": "Second ToDo",
    "deadline": "2018-09-01T12:34:56.789Z"
  }
]

配列形式で複数のエントリが表されています。[ptypes]

試したパターン

実際に試した順番からはちょうど逆転しますが成功したパターンからご紹介します。

成功したパターン

まずは成功したパターンです。

golang/protobuf#675 のコメントを参考にして書きました。 普段の json.Unmarshal と比較するととても複雑なことをしているように見えます。

func (s *todoServer) loadTodos() {

    jsonBytes, _ := ioutil.ReadFile(dataFile)
    jsonString := string(jsonBytes)
    jsonDecoder := json.NewDecoder(strings.NewReader(jsonString))

    // read open bracket
    if _, err := jsonDecoder.Token(); err != nil {
        log.Fatal(err)
    }

    for jsonDecoder.More() {
        todo := pb.Todo{}
        if err := jsonpb.UnmarshalNext(jsonDecoder, &todo); err != nil {
            log.Fatal(err)
        }
        s.todos = append(s.todos, &todo)
    }
}

Unmarshal に標準の encoding/json ではなく protobuf/jsonpb というパッケージを利用しています。 叩いたところ無事データが返ってきました。

$ grpc_cli call localhost:10000 GetTodos ""
connecting to localhost:10000
todos {
  id: 1
  title: "First ToDo"
  deadline {
    seconds: 1535805296
    nanos: 789000000
  }
}
todos {
  id: 2
  title: "Second ToDo"
  deadline {
    seconds: 1535805296
    nanos: 789000000
  }
}

直感的に jsonpb を使った場合

いちいち UnmarshalNext などせずに配列型を直接データの格納先に指定してやればいいのではと思い試してみました。

func (s *todoServer) loadTodos_byInstinctiveJSONPBUsage() {

    reader, _ := os.Open(dataFile)

    if err := jsonpb.Unmarshal(reader, &s.todos); err != nil {
        log.Fatal(err)
    }
}

これを実行すると...というよりコンパイルの時点でエラーが出て

*[]*todo.Todo does not implement "github.com/golang/protobuf/proto".Message (missing ProtoMessage method)

と言われます。つまり pb.TodoMessage の interface を満たしているけれど []pb.Todo には ProtoMessage のメソッドが存在しないので jsonpb.Unmarshalシグネチャ違反ですよと言われているんですね。

標準の encoding/json パッケージを利用した場合

そもそも jsonpb を利用せずに json を利用したらどうなるかです。

func (s *todoServer) loadTodos_byStandardJSONPackage() {

    jsonBytes, _ := ioutil.ReadFile(dataFile)

    if err := json.Unmarshal(jsonBytes, &s.todos); err != nil {
        log.Fatal(err)
    }
}

これを実行すると...

json: cannot unmarshal string into Go struct field Todo.deadline of type timestamp.Timestamp

と返ってきます。string 型を timestamp.Timestamp 型に Unmarshal できないと言っていますね。timestamp.Timestamp 型は Protocol Buffer の ptype (もしくは well-known type) です。

Go 自体そこまで詳しくはないのですが、Unmarshal の振る舞いを変えられたら json でもなんとかなる...んでしょうか。

Go の gRPC のシンプルな Interceptor を自作して理解を深める

Go の gPRC のシンプルな Interceptor を適当に書いたら割とすんなり動いたので基本的な部分をまとめていきます。

今回のサンプルは以下のリポジトリに置いてあります。

go-grpc-middleware にロガーなど様々なミドルウェア(= Interceptor)が用意されていますし、本番用途であればそれらメンテナンスや検証の行き届いたパッケージを使うべきだと思いますが、初見のパッケージのオプションを眺めるのも面倒なのでロガー程度ならスクラッチできないかなと思ってやってみたら意外と簡単でした。基本的に入出力さえ辻褄を合わせておけば何をやるのも自由そうです。

動機としてはもう一つ、grpcフレームワーク的なインタフェースに興味があったというのもありました。既製品のミドルウェアを使う前に基本的なインタフェースだけでも押さえておくと、開発の自由度も上がるかもしれないという目論見です。

それでは以下、今回行ったことの簡単な解説です。

Interceptor を自作して grpc.Server に組み込んで実行する

Interceptor を定義する

今回はミドルウェアとしては定番のリクエストのログを出力する Interceptor を題材にしました。Interceptor について調べる前はやっつけでメソッドごとの各ハンドラに log.Printf を突っ込んで回る作業をしていましたが、それらのコードも一掃できました。

Interceptor は func として定義します。Interceptor には種類があり、今回実装したのは grpc.UnaryServerInterceptor という stream でない値を返す際に動作する Interceptor です。

grpc.UnaryServerInterceptorシグネチャに従い、メソッド名・リクエスト・レスポンスを log として表示する Interceptor は以下のようになりました。引数のシグネチャが長いですがミドルウェア的な処理としては必要な情報が並んでいるだけですね。

func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {

    // ハンドラを実行する
    res, err := handler(ctx, req)

    // リクエスト処理のログを表示する(今回ミドルウェアでやりたかったこと ^-^)
    log.Println("My interceptor called!")
    log.Printf("%s: %v -> %v", info.FullMethod, req, res)

    // 実行結果とエラーを返す(返さないとサーバが結果を返さなくなる ^-^);
    return res, err
}

特に難しいことはやっておらず、関数評価時に渡される reqinfo などの材料をもとに処理を行っているだけです。

一点気になったのが Interceptor 中でメソッドのハンドラを評価し、結果を返さなければならないという点です。そこまでミドルウェアというものを経験したこともないのですが、こういう形式が普通なんでしょうか。Node.js の Express とはお作法が微妙に違うなという感触を得ました。

ちなみに resnil で置き換えたらサーバが空レスポンスを返すようになりました。

grpc.Server に設定する

Interceptor を ServerOption type に変換する grpc.UnaryInterceptor を通して Interceptor をサーバに設定します。

grpcServer := grpc.NewServer(
  grpc.UnaryInterceptor(loggingInterceptor),
)

ここもそこまで難解な見た目にはなっていませんね。シンプルです。

実行

サーバを実行してリクエストを送ったところ、サーバのログが以下のように表示されました。

2018/09/13 22:20:39 gRPC server starts on localhost:10000
2018/09/13 22:20:41 My interceptor called!
2018/09/13 22:20:41 /hello.Greeting/Hello: name:"asa-taka"  -> message:"Hello asa-taka, I am gentle-server"

...というこんな感じで grpc の Interceptor は自作するのも結構簡単なんだな、というお話でした。

余談

開発って

  • その分野のベストプラクティス・定番のライブラリを調べる
  • 調べるのが面倒だからとりあえず手前味噌で実装する

のバランスを探り探りで行なっていく作業だと思うのですが、技術検証フェーズでは後者を自由度高く行えると快適に思えることが多いです。ミドルウェアのインタフェースを知ることも、大いにそれに与するものだと思っております。

Saltstack の highstate や low とは何なのか

最近 Saltstack を触っております。

デバッグがてらに state.show_ 系のコマンドを打つことが多いのですが似たようなコマンドが多くて初見ではちんぷんかんぷんなんですよね。

  • state.show_sls
  • state.show_low_sls
  • state.show_highstate
  • state.show_lowstate

この highlow といった用語はあからさまに Saltstack ドメインの言葉遣いなのですが、ではそれが意味するところとは何なのか、今回はそれを押さえてより理解度深く Slatstack を使えるようになろうと思います。

といっても公式のドキュメントの簡単な和訳や要約なので特に大したことはしてません。

State System Layers

State System Layers に挙げられている項目は上から順に Saltstack における State 系の概念のローレイヤーからのスタックになっているのでそれを順に見ていきたいと思います。

Function Call

  • State System における最もローレイヤーな要素
  • Slatstack における State の実行とは個々の State Function の実行と言える
  • pkg.installed などの関数の実行がこれに当たる

Low Chunk

  • YAML(JSON?) で書かれたデータ構造
  • Salt State Compiler のコンパイル結果がこれ(Low Chunk)の集積物になる
  • Function Call が「実行すること」だとすると Low Chunk は 「実行するとその Function Call が実行されるもの」 と言えそう(ややこしい)

Low State

  • Low Chunk が処理順に並べられたリスト
  • 推測混じりで言えば、ここまでは「どの環境のどのホストで」というものを 考慮しない 純粋なデータ構造・実装や処理のことを指していると思われる*1

High Data

  • SLS Files により構成される YAML として表されるデータ構造
  • SLS Files をまとめたもの、という説明にとどまっている
  • この時点では特に「何のためのもの」というところには触れられていない

SLS

  • SLS レイヤーは High Data の論理レイヤーであると(ちょっと意味が読み取れない)
    • 最終的にデータ構造さえ提供されればその生成過程は問わないとある
  • 用語として SLS Files と SLS Formula は区別したいらしい
    • かつては SLS は単体のファイルで構成されており SLS Formula は SLS Files とも呼ばれていた
    • 現在の SLS は Pillar の内容などが適用され動的に生成されるので SLS Formula と呼ぶのが良い
    • この説明の流れだと SLS Files は個々の *.sls ファイルを指すと考えればいいのだろうか

Highstate

  • SLS を読み込んでどの minion に何を実行させるべきかを判断する

Orchestrate

  • これは Salt の更に上位でやってくれとある

まとめ

ここまで読んで来た内容を今度は逆順におさらいしてみると...

  • Highstate とは各 minion ごとの Low State が集まったもの
  • Low State とは Low Chunk のリストで、Low Chunk とは個々の Function Call を行うためのデータ

と読み取れます。つまり当初気になっていた Slatstack 的な high/low の意味合いとしては

  • 細かい個々の State 管理の項目のことが low
  • 環境やグループなど minion ごとの設定を含めた全体の状態管理が high

...と呼ばれているのかな、という雰囲気はつかめたような気がします。

コマンドの解説を再度読んでみる

以上の前提知識をもとに改めて コマンドリファレンス を読んでみます。

  • state.show_highstate
    • salt master から highstate を取得し表示する
  • state.show_low_sls
    • SLS から Low Data 形式に落とし込まれたものを表示する
  • state.show_lowstate
    • 指定された minion に適用される Low Data を表示する
  • state.show_sls
    • master 上の指定された sls file の State Data を表示する
    • State Data ってなんだろう...

よく見たら high/low の違いと state/sls の違いの組み合わせなんですよね。手元に試せる環境がないので何も言えないのですが、まだ違いがよくわからないんですよね... また気が向いたら今度は触りながら確認していきたいです。

*1:わかりづらい例えをすれば、我々のプロジェクトではこの筆記具のことを「鉛筆」と呼びそれはこういう構造・性質を持っています、ということを言っているのみで、それが運用上どう使われているかはこの段階では触れられていない。

TypeScript の絶対パスの import をプロジェクトルートから辿るようにする

何やら TypeScript では絶対パスでプロジェクトルートから import を行えるようだと耳にしました。これで相対パス地獄から逃れることができますね。早速動作確認していきたいと思います。

使用した TypeScript のバージョンは 3.0.3 です。

動作確認

以下のようなディレクトリ構成で動作確認を進めていきます。

.
├── package.json
├── src
│   ├── app-global-lib.ts
│   ├── index.ts
│   └── nested
│       └── nested
│           └── super-nested-lib.ts
└── tsconfig.json

src/app-global-lib.ts

src 直下の src/app-global-lib.ts からは適当な定数を一つ export します。

export const APP_GLOBAL_CONST = 1

src/nested/nested/super-nested-lib.ts

それを何階層かネストしたファイル src/nested/nested/super-nested-lib.ts から import します。src/... のように絶対パスで指定しています。

import { APP_GLOBAL_CONST } from 'src/app-global-lib'

export const SUPER_NESTED_CONST = 2

このままだと Cannot find module のエラーが出るのですが、ここで tsconfig.json

"baseUrl": "./"

コメントアウトを外すと絶対パスがプロジェクトルートからのパスとして解釈され、コンパイルが通るようになります。

src/index.ts

src/index.js では今まで通り相対パスimport します。node_modules のライブラリも問題なく参照できるか確認したかったので適当に uuidimport しています。

import { APP_GLOBAL_CONST } from './app-global-lib'
import { SUPER_NESTED_CONST } from './nested/nested/super-nested-lib'
import uuid from 'uuid'

console.log(APP_GLOBAL_CONST + SUPER_NESTED_CONST, uuid())

これで無事 1 + 2 で 3 と UUID が表示されました。

感想

とても見やすくなりました。通常の Node.js でもできると嬉しいです。

ところで 以前GitHub - piotrwitek/react-redux-typescript-guide: The complete guide to static typing in "React & Redux" apps using TypeScript を読んだ時に気になったのですが

"baseUrl": "./",
"paths": { "@src/*": ["src/*"] },

という設定を tsconfig.json に行うと import @src/app-global-lib という参照の仕方もできるようなのですが、どちらがオススメなのでしょうか。

参考

react-redux-typescript-guide を読んだ感触

Redux + TypeScript の検証作業で疲弊していたところで react-redux-typescript-guide を読んだら思ったより色々まとまっていて、検証のモチベーションがちょっと高まりました、という感想文です。

現在 Redux + TypeScript でアプリを作ろうと色々調べています。 業務でも趣味でも Redux を使った経験はほとんどありません。 認証情報を格納するためのストアとしてシンプルな reducer と数個の action を定義した程度です。

前回のエントリでは typescript-fsatypesafe-actions についてごく簡単に触った感触について書きました。

dispatch に連動して副作用を実現する redux-observableredux-thunk については何度か手元の試作プロジェクトで軽く触った程度です。 軽く触った程度ですが Redux + Typescript の荒波には十分揉まれました。 分かりやすい進捗が出せなくてそろそろ辛いです。

そして react-redux-typescript-guide というガイドを見つけました。

自分でライブラリを探している途中で何度かページ自体は目にしていたのですが、改めて読んでみたところかなり内容がまとまっているようでした。ここまで荒波にまみれてなんとなく掴めてきた理解を整理する上でも、一旦頭を空にして読む価値のありそうなガイドです。写経や読経は世俗の煩悩にまみれた状態でこそ効力を示します。

このガイドでメインで使われているのは typesafe-actionsredux-observable の組み合わせです。

今回は react-redux-typescript-guide の概要をまとめ...るようなことはせず、どの部分を読んでどういう理解になったか、何が得られたかの、ただの感想文を残していこうと思います。

コード類はほとんど引用していないので読みづらいですが済みません。

Type Definitions & Complementary Libraries

まずは補助的に使うライブラリの紹介です。 typesafe-actions はむしろメインで使うライブラリですね。

React Type Cheatsheet

React + TypeScript のベースとなる型情報です。この辺はある程度 React + TypeScript の経験があれば大体把握しているはずですが、それでも新しく気づいたことがあったので、網羅性というものの価値を感じます。

  • React.CSSPRoperties
    • Material-UI でたまに JSS 用に型定義が欲しくなることがありますが React に含まれていたのは初めて知りました

Component Type Pattern

チートシートの続きで、頻出パターンの逆引き一覧になっています。ここも発見が多いです。実装の段階でお世話になりそうです。

Stateful Components

  • Stateful Components って constructor 無しでもいける
  • defaultProps の例はちょっとわかりづらい(というより読みづらい)

Render Props

Apollo なんかで <Query>{({ loading }) => ... }</Query> とかやるやつですね。 そういえば Render Props と HOC の良し悪しってあまり把握してないんですよね...

Higher-Order Components

そして Higher-Order Components の書き方です。色々理解していないことだらけでした。

  • typeclass の中で定義できる
  • Enhanced なコンポーネントに名前をちゃんとつけててえらい
  • そういえば recompose はこのガイドの中では触れられていないのが気になりました
  • Error Boundary の例もあるのは嬉しいです

以前、FlowApollo を利用していたことがあるので、そこまで苦労しないで読めました。

bindActionCreators

Redux とのつなぎの部分で利用する bindActionCreators (初めて知りました)でワンポイントです。

Redux の bindActionCreators を使うときは props 内で () => void を使うとコンパイルエラーになるけれど () => any で代用可能、という注意書きがあり、このガイドの例もそれで実装されているみたいです。

Redux

そして来ました Redux です。 typesafe-actions を使って action を定義していきますが、 詳しくは The Mighty Tutorial を読んでね、と書いてありますね。

そして 前回 気になったところですが、リンク先にも書いてあります。 大事そうなのでここだけ原文で引用します。

WARNING: When using string constants for action type, please be sure to use simple string literals. Don't use string concatenation, template strings or object map because your type will lose the type information, widening to it's supertype string (this is how TypeScript works).

String Literal を型推論に利用しているので template strings や object map を使うと型が string に落ちてしまい action type の型情報が得られなくなる(= ペイロードの型付けが行えなくなる)ということです。 大変そうですが地道に全ての action の名前をつけていくしかなさそうです。

型付けに別の手法を取っている typescript-fsa にはこういう制約はなさそうです。ここで規模のあるアプリを作成した時に、最終的な使い勝手がどう変わるかが、今後の検証でも気になるところです。

Reducers

State の定義には readonly を使うといいよという話。 ReadonlyArrayutility-typesDeepReadonly も便利そうです。

他は TypeScript のスマートな型解析で if ステートメント内の型がその条件により type narrowing されるという話ですね。

Store Configuration

特に目立ったところはなく、以下の微妙な違いに気をつけてさえすればいいと思います。

  • RootAction は全ての Action の Union 型
  • RootStatecombineReducer された(構造化された) State を示す型

いちいち Root とつけるかは好みですが、実際、プロジェクトを構造化した状態で import を大量に行なっていたりすると、実際は何を参照しているのかあやふやになってくるので、特別な名前で参照できると安心感があります。

他に書かれているのは redux-observable の epic を middleware に仕込むところくらいです。

Async Flow

これは The Mighty Tutorial へのリンクで済まされています。これについてはまたそのうち別途書くかもしれません。

Selectors

これは完全に初見の概念だったのですが、Selector とは Store 上のあるデータに別のデータを適用した結果をキャッシュしておき、その結果が依存する State の変更があるまでそれを保持し続けることを行うライブラリの総称のようです。Computing Derived Data - Redux にも項目があります。

Vue.js の computed に近いといえば近いのでしょうか(あれはテンプレート中で参照するためのものなので用途が違うかもしれませんが)。

Tools & Recipes

個人的に一番嬉しかったところかもしれません。よく利用されるツールの設定例が紹介されています。

  • Common Npm Scripts
  • tsconfig.json
    • @src/... でプロジェクトルート下の ./src を参照するやり方があるのを初めて知りました
      • でもどうやらデフォルトで import src/... が動作するみたいですね...
      • この辺はまた検証してみたいです
    • lib で 何を指定したらいいかの答えはここにありました(毎回迷うんですよね...)
  • Vendor Types Augmentation
    • ライブラリの型定義がおかしい場合に quick fix してやり過ごす方法も載っています

感想

...と、一通り読んできましたが、ただ単に読んだだけで特に手を動かして検証を行なったわけではないので、何ができるようになったではありません。それでも多くの試したいことが発見できたので、技術検証のモチベーションを高める効果はあったと思います。もしくは単に鮮度のいいネタを提供してもらえたと言うべきでしょうか。

とりあえず Redux + TypeScript にかき乱された頭の中は整流されたような気がします。 これでまた型システムの闇に立ち向かえそうな気がします。

気になったところ

ガイドを読んでいて、後で調べたいと思ったところです。

  • HOC と Render Props の比較
    • 両者とも同様にDI的なことを行なっているので、手法の差がどう評価されているのか気になります
  • TypeScript の import path の設定
    • よく ../../../api のように ./src 直下近くに置いたライブラリが参照しづらくなる問題が TypeScript では tsconfig.jsonbaseUrl で解決されているみたいですが、その動作について精査してみたいです
    • import @src/... のパターンと import src/... のパターンがあるみたいですが、どちらが推奨されているのかも気になります

Redux + TypeScript 周りの調査

最近 Redux に手を伸ばし始めました。JavaScript で経験を積まずにいきなり TypeScript から始めたせいか無駄に試行錯誤したりハマっている気がしますが、めげずに界隈の状況把握から進めていきたいと思います。

今回は主に typesafe-actionstypescript-fsa を 1日 触った感じを軽く書いていきます。特に網羅的には書いていません。

side-effect 系ライブラリ

今回は深くは触れませんが、前提として Redux の action の dispatch に際しての副作用を提供するライブラリの代表的なところを挙げていきます。

この中では redux-thunkredux-observable を少し使ってみました。redux-observable を利用するには RxJS をある程度触って理解することが必須となります。そして一応書けたコードを見て「これって保守性どうなのだろう」と思わされたので、もう少しこの辺は調査を進めたいところです。

しかし React の勉強をして Redux はそれとはまた別の勉強が必要になり、さらに RxJS の勉強もしないといけないとなると、新しくアサインされた人には辛いプロジェクトになるかもしれないですね(というより保守人員を求めるのであれば Redux ってどうなんでしょうね)。

action 系ライブラリ

action の定義を楽にしたり定型化したりするライブラリですね。FSA(Flax Standard Action) に基づいた設計の action を生成するものをよく見かけます。

という 2 つのライブラリが目につきます。勢いにも知名度にもそれほど違いがないようなので両者で迷いますね。簡単に触った感じの違いをそれぞれ見ていきます。

typescript-fsa の action 定義

const login = actionCreator.async<
  LoginParams,
  LoginInfo,
  ErrorResponse
>('LOGIN')

のように定義します。これで login.started/done/failed という非同期処理用の 3 種類の action が定義されます。ペイロードの type narrowing には isType が用いられ User-Defined Type Guards で実現されているみたいです。

typesafe-actions の action 定義

const login = createAsyncAction(
  'LOGIN_REQUEST',
  'LOGIN_SUCCESS',
  'LOGIN_FAILURE'
)<LoginParams, LoginInfo, ErrorResponse>();

のように定義します。これで login.request/success/failure という非同期処理用の 3 種類の action が定義されます。typescript-fsa との違いとして 3 種類のアクション名を定義しなければならないのですが action の数が増えてくると結構面倒です。あるデータモデルのCRUD操作をしようとすると複数GETも含めて 15 個の名前を定義せねばならず、さらにそれがデータモデル分必要になってくるという。

関数なんかで生成しようとしても、現状の TypeScript には String Literal をマッピングするような操作(プレフィックスをつけた新たな String Literal を定義する等)ができず、関数を通すとただの string 型に落ちてしまうという弊害があります。typesafe-actions の type narrowing は String Literal により実現されているようなので、その恩恵にあずかるには全てのアクション名をきちんと定義する必要がありそうです。

action 定義ライブラリについての所感

パッと見の定義の楽さで言えば typescript-fsa を取りたいところですが、そこからさらに redux-thunk を使うか redux-observable を使うかの選択次第で、実は typesafe-actions が使いやすいかもしれないですし、そうでないかもしれません。

さらにこれがより実際的になってくると fetch 対象のデータモデル数が 7-8 種類程度の分量になってきたり、combineReducers などの階層化が必要になってきたりして、その場合もちゃんと分かりやすく構造化して実装を整理できるのか、そして型定義はどうなるか、そういったこと次第でもまた選択肢が変わってくるかもしれません。

実際に typescript-fsatypesafe-actions を行ったり来たりした感想ですが、そこまで大規模な書き換えは必要にならなかったので、現在の実装が詰み気味になり対向が気になってきたら書き換える、という方針も取れなくはないかもしれません(不安)。主に書き換えたポイントとしては以下の通りです。

  • import するライブラリ
  • 非同期用の action の 3 種類のフィールド名
  • type narrowing の方法

ただ typescript-fsa-reducers というライブラリを使って reducer を書いていると行き来がしづらくなるので方針が固まるまでは if で地道に reducer を書くようにとどめておくのがいいかもしれません。

...という、Redux + TypeScript + Side Effect という交差点では、やはりというか組み合わせが爆発しているのだなという、ため息に似た現状振り返りなのでした。この辺はもう、検証しながら正解スタックを探す覚悟を決めねばやっていけません。

といってもそもそも Redux をそこまでまだ理解していないので、まずはそこから、プラクティスをかき集めていきたいです。

...Apollo が懐かしいです。そう、初めは Apollo が提供している機能を「別に Redux でハンドメイドできるんじゃないか」と思って書き始めたのでした。分厚いレイヤーに嫌気がさして Redux に手を伸ばしたわけですが、自分で書くと想像以上に込み入ったコードになってしまい...フレームワークって偉大ですね、という身も蓋もない着地点が視界の端に移り始めてきたのでした。

React のファイル名から受ける想定利用法

React を触り始めて 1 年くらい経過しましたが、始めの頃はモジュールのファイル名の命名規則がよくわからなくて迷走していたのをふと思い出しました。

例えば「load data」の 2 語からなるファイル名を考えると、以下 の 3 パターンが考えられます。

  • load-data.js (kebab-case)
  • loadData.js (lowerCamelCase)
  • LoadData.js (UpperCamelCase)

ある程度慣れてくると、ファイル名から作者が想定しているそのモジュールの利用法が読み取れるようになります。 今回はその個人的な感覚のまとめになります。

load-data.js から感じ取る利用法

旧来(いつだろう)の JavaScript 的には一番自然に見えるモジュールファイル名ですね。 「各メンバを個々に読み込んで利用するタイプのモジュール」と受け取れます。

import { func1, func2, const1 } from './load-data'

const result1 = func1() + const1
const result2 = func2()

loadData.js から感じ取る利用法

初見で「おや、命名規則が読み取れない...」と思わされたモジュール名です。

これは「default export されている function のメンバが存在するモジュール」と受け取れます。

import loadData from './loadData'

const myData = loadData()

default export しているメンバがあるので、可能ならファイル名と同じ名前で利用してね、というメッセージを感じますね。

LoadData.js から感じ取る利用法

これも上のパターンに似ていて「default export されている class のメンバが存在するモジュール」と受け取れます。

import LoadData from './LoadData'

const loadData = new LoadData()

もしくは React などの JSX であればコンポーネントとして利用できる、という意味も追加されます。 この場合の実装はクラス・関数問わずになりますね。

import LoadData from './LoadData'

function MyComponent() {
  return <LoadData />
}

JSX の場合は new するクラスか、それともコンポーネントか、どちらを指すモジュールなのかは仕様や実装を見なければ判断できませんね。