1か月の開発の記録

ここ1か月開発していたお絵描きアプリの開発が落ち着きました。React + Typescriptに初めて手を出したこともあり、学んだことなどを備忘録として記録しながら振り返ります。

制作物

手書きで描いた画像をサーバーに送信するデモを作成しました。目標は"Next.js + Koa.js + Typescriptでcanvasを扱うWebアプリを作成する"です。Next.jsはSPAのSSRのためではなく、フロントエンドにReactを使うWebアプリ開発の手段として使う方法を模索しました。ここに登場した技術をうまく噛み合わせられると、フロントエンドはReactのJSXで記述しつつ、バックエンドはasync/awaitで非同期処理を簡単に書きながら、Typescriptでコードの可読性と保守性を向上させられるはずです。

今回はフロントエンド開発について(苦しみながら)多くのことを学びました。Typescriptを使った経験がほとんどなかったこと、ReactによるSPAの開発経験も1回だけ、そしてTypescript + React(Next.js)の組み合わせは初めてです。サーバーサイドは以前Koa.jsを使ったAPIサービスを開発した経験が活きて、今のところ何とかなっています。

以下、作業時の記録です。読む必要はありませんが、個人的に将来振り返ると面白いと思うのでまとめました(そして途中から校正が面倒になりました。許して。)

6月10日

Next.jsでサーバーサイドのスクリプトを記述する際にエラーに遭遇した。

Cannot invoke an expression whose type lacks a call signature. Type 'typeof import("d:/xxx/node_modules/next/types/index")' has no compatible call signatures.ts(2349)
import next from 'next';
const app = next({ dev: true});

6月12日

Next.jsがTypescriptに対応したばかりで型情報やドキュメントに不備があるのが原因だった。8.1.1-canary版を導入した。また、Node.jsのスクリプトは次のように記述すると動いた。

import next from 'next-server';
const app = next({
  quiet: false
});

またサーバーサイドで使用するtsconfig.jsoncompilerOptions.modulecommonjsにした。Next.jsのフロントエンドとバックエンドでTypescriptのコンパイル設定が異なるので、そのあたりをうまくハンドリングする必要があった。設定項目はとても多く、なんとかエラーが無くなるように設定することに成功したが、正直よくわからないし覚える気も起きなかった。

6月13日

生のHTMLは味気なくてモチベーションが湧かないので、CSSのカスタマイズ方法を調査した。

@zeit/next-sassnode-sassをインストールし、next.config.jsに設定を書くと、tsxファイルでscssファイルをインポートできるようになる。どういう原理なのかまでは学習しなかった。こんな、いつ動かなくなるかわからないビルドチェーンを常用しているフロントエンジニアの皆さんは大変だなという気持ちになった。

6月17日

Reactのアプリ開発に着手するも、Typescriptのコンパイラに怒られ始める。nullかもしれない変数はエラーチェックしないといけないなど、コンパイラが賢くて驚いた。あとTypescriptでReactをどうやって書くのかの情報が少ない現状を知った。でもJavascript辛いし。

6月18日

Next.jsのサーバーサイドのコードを実装した。ブラウザから画像をPOSTするにはCORSの設定が必要だったのでkoa-corsというミドルウェアを導入した。また、大きなデータをやり取りする場合はapplication/x-www-form-urlencodedよりもmultipart/form-dataがいいと考え、その追加の設定を行った。初期デモはあっさり動いた。

6月23日

フロントエンドが全然開発できていないので、もう少し頑張った。具体的に言うと、Canvas機能強化のためにKonva.jsを導入した。最初はfabric.jsに手を出したが、Konva.jsのほうが自由が高い気がする。

6月24日

次のエラーがメモされていた。多分React x Typescript絡みのエラーなのだが、詳細を忘れてしまった。

Type 'Element' is missing the following properties from type 'ReactChildren': map, forEach, count, only, toArrayts(2739)
ToolButton.tsx(8, 3): The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & Props'

記憶が正しければ、TypescriptでReactのFunctionalComponentを記述する方法を間違えていたのだと思う。いろいろ調査してエラーを修正できた。

6月25日

内部状態を持ち、2回押せるボタンを作成した。Material-UIが提供するボタンを継承したコンポーネントを作成し、プロパティを親に渡す実装にした。いいのかどうかわからないが、Typescriptの型チェックはパスできた。教育上良い状態ではないが、今は気にしないことにする。

6月29日

canvasのundo/redo機能の実装を試みた。プログラミングを始めたころから何度かツール作りに挑戦していたが、undo/redoの実装を実装したことがなく、今回はOSSの実装を参考にしながら作業に取り組んだ。

参考にしたライブラリはcanvasのプロパティをフックしていて、関数や可変長の引数を変数に保存していた。これだからJavascriptはなんでもありでずるい。CやC++に活かせる気がしない。

Typescript初心者としては、関数のフックを実現する方法に苦労した。

例えば次のコードはブラウザで動作しない。

class Context2D extends CanvasRenderingContext2D {
    constructor() {
        super() // Illegal Constructor
    }
}

解決方法は、まず以下のインターフェイスを定義する。

interface Context2D extends CanvasRenderingContext2D {
    // additional properties
}

その後、オリジナルのコンテキストをContext2D型にキャストしたうえでプロパティに値を代入すればよい。

そのほかにはprototypeチェーンの振る舞いを調べたり、Typescriptのkeyofを覚えたり、スタンドアロンとして使うにはwebpackが必要だったので導入したり。Reactとは別ベクトルでTypescriptの難しさを実感した数日間だった。

7月7日

Typescriptにポーティングする際に言語仕様を誤って理解しており、undo機能が不自然な挙動をしていた。が、なんだかんだバグ潰しを続けて、現在は正常に動いている。まともに動かすまで2週間かかった(平日は作業少なめ)。undo機能の実装は大変だったナ。

あと作りこんでいるうちにパフォーマンスの問題が出てきて、canvasの取り扱いの難しさを実感した。解決策がパッと思い浮かばないので作業を一区切りすることにした。

おわり。アウトプットは大変だ。しばらくは読書したい。