お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。
投稿日:
ライブラリ::Next.js開発::設計

Next.jsのディレクトリ構成を、かれこれ3年くらい考えているのだが、ローカルのメモ帳に溜め込んでいても腐るだけなので一旦雑に吐き出してみる。

やりたいこと

大きくは基本の開発ルールを定めて、チームで混乱が生じないようにしたい。

  • コードファイルの配置ルールを決め円滑な開発ができるようにする
  • ディレクトリごとにスコープを作り、ある機能で使うコードは一か所にまとめる(コロケーション)
  • ロジック・状態・ビューを分離し、コードの肥大化を抑制することで、見通しをよくする
    • 大まかなルールとしてはロジックはcontroller.ts, util.tsに置き、状態はstate.tsに、ビューはview.tsxに配置する
  • 共通部品と共通部品でないものを明確に分ける
  • 命名規則を単純化して命名で悩んだり属人化することを防ぐ

大きなこと

基本的なデザインパターンはMVCをベースにしていて、controller.tsにはイベントハンドラを、state.tsには画面の状態を、view.tsxにはTSXを持つという設計。controller, state, viewの定義は以下の通り。

  • controller.ts
    • viewに差し込むイベントハンドラ群
    • useEffectはここに書く
  • state.ts
    • 画面で利用するstateを実装する
    • 中にはusePageState()という関数を一つだけ作り、これで画面の全状態を管理する
      • この関数の引数は状態の初期値のみを渡す
    • この中にTSXやuseEffectは書かない
  • view.tsx
    • 基本的にTSXだけが書かれている
    • 表示非表示を切り替えるために以下のようなboolean分岐はあってよいが、ネストするなどで複雑になる場合は別コンポーネントに切り出す
      • props.isHoge ? <Hoge /> : < />

ただ、これだとstateはModelではなくVewModelになるので、MVVCみたいな構成になってしまうので、一般的なデザインパターンから外れてしまうのがやや懸念だ。但しModelがないことで、Modelを修正したら参照している全画面に影響が出てデグレしたみたいなのは回避できると思っている。MVCというよりClean Archtectureとかレイヤーアーキテクチャの方が近いかもしれない。

ディレクトリ・ファイル構成例

└─src
    ├─adaptors
    │  ├─HogeRequest
    │  │  ├─index.ts
    │  ...
    ├─components
    │  ├─Fields
    │  │  ├─Checkbox
    │  │  │  ├─controller.spec.ts
    │  │  │  ├─controller.ts
    │  │  │  ├─view.spec.ts
    │  │  │  └─view.ts
    │  │  ...
    │  ├─Layouts
    │  │  ├─HogeLayout
    │  │  │  └─index.tsx
    │  │  ...
    │  └─Pages
    │      ├─Dashboard
    │      │  ├─ui-parts
    │      │  │   ├─HogeSection
    │      │  │   │  ├─controller.spec.ts
    │      │  │   │  ├─controller.ts
    │      │  │   │  ├─view.spec.ts
    │      │  │   │  └─view.ts
    │      │  │   ...
    │      │  ├─controller.spec.ts
    │      │  ├─controller.ts
    │      │  ├─state.spec.ts
    │      │  ├─state.ts
    │      │  ├─view.spec.ts
    │      │  ├─view.ts
    │      │  ├─usecase.spec.ts
    │      │  └─usecase.ts
    │      ...
    ├─hooks
    │  ├─ValueState
    │  │  ├─index.spec.ts
    │  │  └─index.ts
    │  ...
    ├─pages
    │  ├─Dashboard
    │  │ ├─index.page.ts
    │  │ ├─server.spec.ts
    │  │ └─server.ts
    │  ...
    ├─resources
    │  ├─RoutingConfig.ts
    │  ...
    └─utils
       ├─HttpClient
       │  ├─index.spec.ts
       │  └─index.ts
       ...

各ディレクトリ・ファイルの役割

src/adaptors/**/*

API通信などのリクエストを投げる処理だけを置く場所。投げる処理以外は一切書かない。ここではパースや例外処理を行わなず、必要な処理がある場合は、呼び出し元に委任する。

これは呼び出し元によってハンドリングが変わるケースがあり、機能AとBでは最初同じハンドリングだったが、のちに機能Aだけハンドリングが変わりアダプタ側に手を入れるというケースを防ぐためだ。要するに開放閉鎖の原則を守るためである。

src/components/**/*

Pageコンポーネント以外の全てのUIコンポーネントと、それに付随する処理を配置する場所。MVC一式がここに入る。

Modelをここに入れることに関しては悩みがあるが、MVVMのViewModelとして考える場合はそこまで違和感がないように思う。

src/components/Fields/**/*

<input type="text" />みたいなフォーム系の入力コンポーネントの部品置き場。

ここに配置されるコンポーネントは原則として状態を持たず、状態は親コンポーネントからprops経由で渡される。このため、viewとcontrollerのみがある。

src/components/Layouts/**/*

ページ全体のレイアウト置き場。大外のレイアウトを入れる想定、そんなに数は生まれないと思う。

例えば以下のコードで~Layoutとなっているのがここに入る。基本的にはViewしかない想定だが、必要ならControllerなどを置いてもよい。Stateが存在することはない(props経由で差し込まれることはある)

<CommonLayout>
  <FullWideViewLayout>
    <ヘッダーコンポーネントとか />
  </FullWideViewLayout>
  <HalfWideViewLayout>
    <ボディコンポーネントとか />
  </HalfWideViewLayout>
  <FullWideViewLayout>
    <フッターコンポーネントとか />
  </FullWideViewLayout>
</CommonLayout>

src/components/Pages/**/*

ページコンポーネントの実体置き場。この設計ではNext.jsのページコンポーネントはこのディレクトリにあるUsecaseを参照するための存在なので、ページ本体の実装はここに置く。

src/components/Pages/Hoge/ui-parts/**/*

このページコンポーネント内で使う細かいUI部品置き場。src/components/Fields/**/*などにある共通UIコンポーネントや、巣のTSXの組み合わせ。stateは持たず、controllerとviewのみを持つ。

src/components/Pages/Hoge/*.{ts, tsx}

このページコンポーネントの本体。

  • controller
    • viewに差し込むイベントハンドラ群
    • useEffectはここに書く
  • state
    • 画面で利用するstateを実装する
    • 中にはusePageState()という関数を一つだけ作り、これで画面の全状態を管理する
      • この関数の引数は状態の初期値のみを渡す
    • この中にTSXやuseEffectは書かない
  • view
    • controllerとstateを受け取るpropsを持つ
    • 基本的にTSXだけが書かれている
    • 表示非表示を切り替えるために以下のようなboolean分岐はあってよいが、ネストするなどで複雑になる場合はui-parts側に実装することが望ましい
      • props.isHoge ? <Hoge /> : < />
  • usecase

    • getServerSidePropsの結果を受け取るpropsを持つ
    • controller, state, viewの橋渡しをする場所
    • この階層でcontrollerにstateを差し込みラップした関数を作り、viewに差し込む

      ```tsx
      import { sendHoge } from './controller';

      const ps = usePageState();
      const onClickHoge = (ev: EventT) => { // これは中でHTTPリクエストを行っており、ローディングの状態を切り替えている
      sendHoge(ps.hoge, ev.target.value);
      }

      return ;
      ```

viewやcontrollerレベルで切り替わる場合は、切り替え先をcontroller, state, view, usecaseの単位で子にして、親側でラップする

src/hooks/**/*

共通的な状態操作用のHooks置き場。

src/pages/**/*

Next.jsのページコンポーネント置き場。*.page.tsxにはusecaseコンポーネントの呼び出しのみを記述する。SSRする場合はserver.tsに関数を実装し、*.page.tsx側から参照して使う。

src/resources/**/*

定数置き場。

src/utils/**/*

共通処理置き場。全体的に利用する共通処理のみ配置する。局所的に使うものは置かない。

一定のドメインの範囲で利用するものをどこに置くかは考え切れていない。

投稿日:
ジャンル::雑記

七月から体調を崩していて、更に先々週から大きく崩していたけど、この週末は徐々に調子が良くなってきたのもあり、何か美味しいものを食べようと思い、やはりこの季節はカニだろうというのが浮かんだので、近所のかに道楽に行ってみた。

思い立ったが吉日ということで、かに道楽のサイトを開き予約状況を確認して、予約してから向かった。これは高い店なので突撃は危険そうに感じたのと、ググった感じ、カニの季節は予約しないと入れないという情報を見たからだ。結論から言うと、この日の少人数席は空いていて予約は不要だった。

大阪を代表する看板の一つ「かに道楽」の文字が光る、かに道楽三宮店。カニのオブジェもいい。これを見るだけでワクワクするというものだが、明らかに高いのが解っていたので今までは見ても素通りするだけだった。しかし神戸に住んでいて、かに道楽に行ったことがないというのももったいない話な気がするので、今回これてよかった。いやまぁ、家から歩いてこれる距離にあるので、店の前に来るだけならいつでも来れるのだが。

kanid1.jpeg

少人数席は掘りごだつ式の座敷席だった。このタイプの席は半数程度しか埋まっておらず、そのまま突撃しても問題なさそうだったし、受付以降は店員も無予約客として接してきたので、恐らく予約客はそう多くないのだろう。来る店員ごとに「予約しています」「注文も予約しています」という感じで説明する羽目になったので、一人で行く場合は予約しないほうが楽かもしれない。Web予約メニューより店舗にある献立表の奴のほうが見やすく、その場の気分で選びやすいというのもある。

kanid2.jpeg

そこそこ高そうな店という雰囲気を醸し出す机の上、ホジホジ棒(R)と書かれた竹製使い捨てのカニほじ棒が面白い。箸置きもカニの形をしていながらも二列配置できる工夫?があって面白かった。

kanid3.jpeg
kanid4.jpeg

前菜として出てきた、カニの軽食たち。ゆでガニ、白和え、ポン酢の三点セットだ。小皿料理が好きなので、こういうのが胸が高鳴る。

kanid5.jpeg

かに刺し。紅たでや山葵がいい感じの小道具に収まっていて風流だ。

kanid6.jpeg

炊きあがるのに30分かかるといわれ机の下に置かれる釜飯。

kanid7.jpeg

かわいらしい容器に入った茶碗蒸し。

kanid8.jpeg

天ぷら。さつまいもとカニとエリンギ。和食の中にエリンギがいるのはどうなのかと思いながら食べていた。

kanid9.jpeg

焼きガニ。とびきりぷりぷりで、全料理の中でこいつだけ格の違いを感じた。

kanid10.jpeg

澄まし。

kanid11.jpeg

炊きあがった釜飯とお茶漬けセット。

kanid12.jpeg

カニほぐし身を、おこげ入りの釜めしに入れて頂く。釜めしには下味がついており、これはかなり良かった。

kanid13.jpeg
kanid14.jpeg
kanid15.jpeg

デザートのアイスには流石にカニは入っていなかった。手についたカニの臭いを消すためのレモンという不思議なものがついてきたので、試しにレモンで指を拭ってみたところ、確かにカニの臭いが消えた。手洗い用のレモンとは、なんとも贅沢な逸品だ。

kanid16.jpeg

受付前にカニの生簀があったので、恐らく出てくるカニの一部は冷凍ではなく生なのではないかと思う。全部ここからとっているとしたらちょっと夢があるが、どうなんだろうか。

20241102_202459359.JPG

行ってみた感想としては近くにあって気軽にカニが食べれる店としてはいいなと思った。例えばこれが香住まで行ってカニを食べるとなると、どうしても大変だ。例えば、はまかぜで香住に行ってカニを食べるのも悪くない体験なのだが、私が住んでいる三宮から行くとなると距離が距離なので時間がかかるし、運賃も高い。往復で1.3万程度する。その代わりに優雅な鉄道旅と、壮大な日本海を眺めながら安い値段でカニを始めとした、日本海の恵みを食べることができる。食事代だけでいえば香住で食べたほうが圧倒的に安い。かに道楽と同じ値段で倍以上食べれたと思う。単なる思い出補正かもしれないが、カニ自体も、かに道楽より美味しかった記憶がある。参考までに今回頼んだのは凪咲(なぎさ)というコースメニューで、6,000円ほどだった。

香住は見るところも多く観光で行くことを考えれば悪くない選択肢だ。しかし、手軽さでは歩いて行けるかに道楽に軍配が上がる。本気でカニを楽しむなら香住。手軽に楽しむなら、かに道楽という使い分けが良いだろう。他にもかに道楽の方が高級感があり、待ち時間が少ないのもある。香住にもそういう店はあるかもしれないが、前に行ったKAN-ICHIだと食堂風の店舗で、高級というよりはカジュアル寄りだった。しかも二時間くらい並ぶ必要があり、なかなかしんどい。前に行ったときは、いつ呼ばれるかわからないので観光もできず、日本海を眺めるか、土産を物色するか程度しかできないのが手痛いかった。良くも悪くもかに道楽は日常の中にあるので、手軽さが魅力だといえるだろう。

あと確か近日中に兵庫県日本海側のカニが入った気がするので、地物を楽しみたいなら来週末辺りから行くのがよさそうだ。流石に高いので今年はもう行かないが…w

投稿日:
ジャンル::雑記文芸::映画

もともと映画館というのはテレビがなかった時代に興行を見る場だったと見聞きしたことがある。テレビが普及し、そしてインターネットが普及した結果、テレビが衰退したといわれ、インターネット配信も増えてきた今では映画館の価値というのは相対的に低下していると考えている。そんな中、個人的に映画館で映画を見る理由は何かというのを書いていく。

まず一つ目は大きなスクリーンと迫力のある音響だ。これは家では中々味わえない体験で、これを目的に映画館に来ている人も少なくないことだろう。またこの部分は映画館や、シネコンのスクリーンによっても違うので、映画館やスクリーン巡業をするオタクがいるほどのものにもなっている。なぜか全国津々浦々の映画館やスクリーンを知り尽くしたオタクとかいる。

次に映画館独特の雰囲気を体験できるところだ。これは映画館によってだいぶ変わってくるが、シネコンだと独特の匂いや、ひんやりした空気感、静寂みたいなのがあって、私はこれが好きだ。幕間や上映終了後のこの雰囲気を楽しみに行くのも一つだ。特に上映終了後の空気を吸いながら家に帰るのは至高の体験の一つだ。他にも上映中のポップコーンの香りや、観客のざわめき・笑い声、コンビニの袋をくしゃくしゃする・後ろから蹴られるなどの不快感も混みで、その回にしかない、一期一会な上映体験を得るのも侘び寂がある。経験した中で過去最悪だったのは神戸国際松竹でARIA The CREPUSCOLOを鑑賞していた時に後ろの列でおじさんが床に転がって寝ていたことだ。しかも大きないびきをかいて寝ていた。こんな光景は中々忘れることができないし、無駄に頭に焼き付くので面白い。いや、観ているときはただただ不快でしかないのだが、家でネット配信を見ていたらこんな体験はできない。

他にもオタクと映画館について話し合ったり、遠方のオタクを地元の映画館に呼び込んだりというのもするのだが、普段映画館で映画を見ているからこそできる活動の一つだ。こういうのも楽しい。つまり、私は映画館で映画を見ることそのものより、そこに付随してくる要素を楽しんでいるのだろう。特にトラペジウムについてはXで多くのオタクたちと大いに過熱し盛り上がったので、あれはよかった。正直トラペジウムという作品自体は私の中でそこまでの存在ではないが、それで盛り上がった熱気というのは格別だった。まるで往年のインターネット、そう、インターネットがまだ黎明期だったころ、今ほど細分化しておらず、2chやはてなみたいなのがネットの中心にあって、似たようなパソコンオタクたちが集まっていたころに、ネット全体が熱を帯びて何か一つに向かっていたような、あのころの体験を呼び起こしてくれるような作品だった。

鑑賞外の要素であれば、アニメ映画に限ってだが、誰によっていつ公開されたどの作品を観たかを記録しているし、映画館で何かを見たときは、いつどこで何を見たかも記録している。下図はその記録内容だ。最初は映画館で鑑賞したアニメ映画作品だけを記録していたのだが、いつ何処で観たかの情報が欲しくなったので、観た作品の記録と、観た場所の記録を分けている。これをクロス集計すれば封切日に見た割合を出すことや、私が映画を観ることになった2019年以前の作品をいつ観たかというのも出せる。単体でもどこのスタジオの作品を多く見ているかや、どこの映画館によく行っているか、よく行く映画館の変遷など、様々な要素を見ることができる。

そんなことに何の意味があるのかといわれると分からないが、集計が楽しいのでやっている。なお、本記事の執筆時点では下図が上手く拡大できないため全体を見る場合、上手いことして拡大してほしい。

過去に映画館で鑑賞したアニメ映画作品の記録
過去に映画館で映画作品を鑑賞した記録

結論としては、私は映画作品を見るためだけではなく、映画作品を見ることによって得られる副次的な要素を楽しむために、映画館に行っているのだと思う。

投稿日:
ジャンル::買い物

ドコモでスマホを買う時のプランとして、「いつでもカエドキプログラム+」というのがいつの間にか出てきていた。これは「いつでもカエドキプログラム」の亜種で、「smartあんしん補償」を契約することで13~22ヶ月の間に端末を返却するとお得に買えるという内容だ。

「smartあんしん補償」は有償の端末補償プランだが、「いつでもカエドキプログラム+」と抱き合わせにすると料金が相殺され安くなる謎のトリックがある。何故かは知らないが、携帯キャリア特有の気の遠くなる複雑な事情があるのだろう。

この「いつでもカエドキプログラム+」は「いつでもカエドキプログラム」より一見安く見えるが22ヶ月目以降に返却する場合はプラスなしのほうが、プラスありに存在する頭金の分安くなるらしい。計算が面倒で検証していないがドコモのサポートがそう言っていた。なので2年より早く返す場合は「いつでもカエドキプログラム+」、2年くらいで返す場合は「いつでもカエドキプログラム」が有利だろう。

一括で買って端末を中古で売るというのも一つだが、これで上記を上回れるケースは結構難しいと思うので、今のところはドコモ側の買取プランが有利だろう。

ドコモがなぜこのようなプランを提供できるのかは不明だが、一説によると買い取った端末を再販しているらしい(どこに?)ので、それで成り立っているようだ。

これまでGalaxy S22 Ultraを使っていたのだがSamsungの姿勢に耐えられなくなったので5-6年ぶりくらいにXperiaに戻ってきた。

まずは開封の儀。最近のスマホはコストカットなのかパッケージが簡素化されいる傾向があるが、Xperia 1 VIは紙箱ではなく不織布的な箱で少しだけリッチだった。

e1a3eaa048b44c54.jpg
fb1ba51cdfff57e1.jpg

最近のスマホには珍しくSDカードトレーがあり、器具がなくてもトレーを外せるように爪をひっかける部分がついている。写真を撮り忘れたのだがイヤホンジャックもある。家では有線イヤホンを使っている私にはありがたい。今まではUSB-C変換で使っていたが、動画を長時間見るときに充電に支障があり困っていた(充電するときだけ無線ヘッドセットに切り替えて回避していた)のでありがたい。

aafcce5a0c9c550c.jpg

カメラ性能は以前より微妙になったが、今のところ大きな不満はない。悪くなった部分としては白飛びするようになったことや、100倍までできたズームが7倍くらいまでに縮んだことがある。

Xperia 1 VI Galaxy S22 Ultra
1014e9ec7264d997.jpeg
eb7459f3c92e89a5.jpg

その代わり写真の自動補正が弱まり、変な色になることがなくなった。例えば以下は白色のバスを撮影したものだがGalaxyでは青味が出ている。

Xperia 1 VI Galaxy S22 Ultra
8570104df10af349.jpeg
43926aa7d5733b81.jpg

この程度ならまだいいのだが、Galaxyの自動補正は強力でバスや電車の行き先LEDを撮影したときに補正後にLED表示が消え真っ黒になることがあり、結構困っていた。何せ撮影した瞬間は出ているものが補正表示の後に真っ黒になるので、かなり悲しい。しかも自動補正のオプションをすべてOFFにしても補正されるので救いがなかった。Xperiaではこれが起きなくなることを期待している。

他にもXperia 1 VIはマクロモードで接写が得意とかいう話を見るがGalaxy S22 Ultraで3倍ズームにして撮影したほうが映りはいいと思う。

正直全体的にみるとカメラ性能は落ちていると思うが、白飛びを除けば目で見た通りのものが映し出されているようにも思えるので今のところ大きな不満はない。

次に操作性だが、これは大きく改善しており、One UIがなくなったおかげでだいぶ操作しやすくなった。

例えばGalaxyではEdgeで文字を選択し翻訳するのに、サブメニューを開いたうえで上側の翻訳を押す必要があった。間違えて下を押すと外れなので機能しない。

f2b9ef30fa9fb209.jpg

上側はGoogle翻訳で、下側はSamsung翻訳?なのだが、Samsung翻訳を有効にすると一言語当たり500MBのストレージを食うという凶悪さがあり、使いたくなかった。正直これは恐らくストレージを圧迫してクラウド課金に誘導するためのSamsungの施策なのではないかと疑いたくなるほどの酷さ。しかもオプトアウトできない。この機能はある時期を境に追加されたが、言語パックを入れていないと翻訳ウィンドウが出るだけのお邪魔機能になる。

この翻訳面の挙動はXperiaでは大きく改善し、メインメニューに翻訳が分かりやすく出てきて使いやすくなった。

Screenshot_20241008-112254_1.png

GalaxyにはSamsungインターネットなど、独自のエコシステムが多く、大半はオプトアウトできるのだが、以前からオプトアウトさせないように小細工を仕掛けてくる傾向があり、あまり好きでなかった。Xperiaにすることでそう言った呪縛から解き放たれたのは快適だ。

そして端末本体だが、以前より僅かに小さくなり、少し軽くなったのも嬉しいポイントだ。

- Xperia 1 VI Galaxy S22 Ultra
74mm 78mm
高さ 162mm 163mm
厚み 8.2mm 8.9mm
重さ 192g 229g

Spigenのケースをつけた状態での比較写真、左がXperia 1 VI、右がGalaxy S22 Ultra。こうしてみると一回り小さく見える。

nRLjhosJ.jpg

但し価格は大幅に上がり、22万ほどになってしまった。Galaxy S22 Ultraの価格は覚えていないが、ここまではしなかったと思うし、あちらは法的に大丈夫なのかと思うほどにキックバックが充実していたので実質価格はそこまでしなかった。今回のS24 Ultraもその辺りはいいらしく、ドコモショップでXperiaより安くてスペックのいいGalaxyを買うべきと言われたほどだ。あとからクレームを言われるのを防止するためか、Xperiaを買う理由を結構聞かれたし、本当にいいんですね?と念押しまでされた。

Sペンがなくなったが、元々使っていなかったので個人的には問題ない。一つだけ惜しいのはSamsungのギャラリーアプリが使えないことだ。あれは画像加工と整理に中々便利なアプリだったが、apkを移植してもXperiaでは動かないので、ギャラリーアプリには以前使っていたシンプルギャラリーを使うことにした。

端末スペックとしてはSnapdragon 8 Gen 3が乗っているので何ら問題ない。エントリーモデルも触ったが、やはり弱いSoCだと画面がカクついて厳しい。みんなよくあれ使ってるなと思う。とはいえ、フラグシップはクソ高いので悩ましいポイントだ。今やエントリーモデルの価格もかつてのフラグシップ並みに迫ってきており、なかなか厳しいと感じた。