Express, MonogoDB, React, TypeScriptの開発を始めるときに困ったこと、参考になったサイト

f:id:rasukarusan:20200323173536p:plain:w400

今まで業務ではPHP, MySQL, 素のJavascriptを触っていた、いわゆるサーバー側のWebエンジニアがオールJavascriptの開発をする上で困ったことをずらずら書いていく。
今回は誰かに見てもらうというより自分のメモを公開するに近いので、全くまとまっていないが、部品だけでも誰かの参考になればいいなっていう感じ。

やりたかったこと

  • Express, MonogoDB, React, TypeScriptの開発をしたい
  • 実用的な成果物は求めていない。基本的な文法、データの流れを勉強したい
  • 認証周りやデプロイはできなくていい(一番臭いところに蓋をしてとりあえず進むっていう戦法)
  • Vimで開発したい

ようするにハードルを無くしたかった。
全体像を把握してから細部に取り組んだほうが早いし理解も深いはずだから。きっとね。

環境構築

大枠はこれを参考に。

バックエンドはExpress, TypeScript, MonogoDB、フロントエンドはReact, TypeScriptで開発。
初めはWebpackでts-lintやらbabelやらモリモリ構築しようとしていたが、覚えるものが多すぎてやめた。
ツールの使い方じゃなくて言語、FWを学びたいのが本質だったので、初期構築はシンプルにexpress-generatecreate-react-appに任せた。中身を知るのは後でいい。

VimのPlugin

" Typscriptシンタックスハイライト
repo = 'leafgarland/typescript-vim'

" TypscriptReactシンタックスハイライト
repo = 'peitalin/vim-jsx-typescript'

" JavaScriptハイライト
repo = 'pangloss/vim-javascript'

" JS/TSインデント
repo = 'jason0x43/vim-js-indent'

"coc - 入力補完/定義ジャンプ
repo = 'neoclide/coc.nvim'

正直JS/TSの開発するならvs-codeでいいんじゃねえかなと思いつつ、とりあえず構築した。
最近のvs-codeを使ったことないから便利な機能を知らないので比較しようがないが、とりあえずVimでも困ることは今の所ない。

  • Vim-fzfのシンタックスハイライトが効かない

vim-fzfでtsのファイルを開くとfiletypeがtypescriptreactになってしまう。ハイライトが付くのはtypescript.tsx。 filetypeがtypescriptreactになってしまう原因はQuramy/tsuquyomiプラグインだった。 プラグインをコメントアウトしたらちゃんと付くようになった(init.vimでfiletypeをセットできるようになった)

mongoDBはコレクションが無いとDBが見えない

初歩的だったが、知らなかった。

expressとmongodbの疎通ができない

mongoDBが起動する前に疎通をしようとして失敗していた。コンテナは立ち上がっているが中のDBの起動を待ちきれていなかった。
docker-compose up直後は下記のエラーが出てしまっていた。

api_1      | (node:19) UnhandledPromiseRejectionWarning: MongoNetworkError: failed to connect to server [mongodb:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 172.18.0.2:27017]
api_1      |     at Pool.<anonymous> (/api/node_modules/mongodb/lib/core/topologies/server.js:438:11)
...
api_1      |   [Symbol(mongoErrorContextSymbol)]: {} }

wait-for-itで解決。docker-copose.ymlに以下を記載。

    api:
        command: bash -c "/usr/wait-for-it.sh --timeout=0 mongodb:27017 && npm run watch"

wait-for-itは下記を頂いた。
- wait-for-it/wait-for-it.sh at master · vishnubob/wait-for-it

expressがソースを変えても自動更新されない

最初はnodemonを使って解決していたが、ExpressをTypescriptで書いていくうちにtsc-watchでやるのが楽だということを発見した。

Reactでファイルアップロード

POST通信をしたくて実装したけど、別にファイルのアップロードじゃなくても良かったかなと思う。

ExpressのTypeScript化

ExpressでのMVC

MongoDB(NoSQL)の設計

正直DB設計ははまだあまり手を付けていないが、MySQLとNoSQLの違いを把握するのに参考になった。
未だに外部キーの脳みそなので困惑中。

Node.jsでUnitTest

mochaとjestで悩んだがjestの方が楽そうなのでjestにした

・jest-mongodbはtypescriptでは使えないっぽい
・importとかでコケる、本体ソースのmodelとかにimportが書かれても落ちる
・babelを使えばいけるっぽい?

下記がめちゃくちゃ参考になった。

DB操作をasync, awwaitで書くのはなぜか

MongoDBを環境変数で切り替える

dotenvを使って下記のような.envファイルを用意。

__MONGO_HOST__=mongodb://localhost:27017
__DB_NAME_DEV__=development
__DB_NAME_TEST__=tests

ただこのやり方だと環境別のif文を書かないといけなくてちょっとキモい。

if (process.env.NODE_ENV === 'test') {
  dbName = process.env.__DB_NAME_TEST__;
}

.envファイルじゃなくてconfig.jsみたいなファイルに書き込んでそれを読む込むっていう形のほうがいいかなあ。

TypescriptReactでfetchの結果を型付けできない

こんなことがしたかった

// Error: Objects are not valid as a React child (found: object with keys {_id, name, age}). If you meant to render a collection of children, use an array instead.

interface IOrder {
  _id: string,
  name: string,
  age: number
}

function List() {
  // 
  const [apiResponse, setApiResponse] = useState<IOrder[]>([]);

  const callAPI = () => {
  fetch('http://localhost:9000/orders')
    .then(res => res.json())
    .then(res => {
      setApiResponse(res);
    })

  return(
    <div> {apiResponse} </div>
  );
}

response.json()の型はany。というかObjectで返ってきているため、そのままだとrenderで怒られる。 JSON.stringfy(response)とやればできるば、forEachとかが使えない。

どうやらFetchを使いたい場合は自分で型付けを実装しないといけないっぽい。「Typescript react api client」で検索したらいっぱい出てきた。

だからこそaxiosライブラリが存在するみたい。型付けを楽にできるっぽい。
結果axiosを使ったら超絶楽に実装できた。

React Router

基本これだけみればいける

/users/:idみたいなことをしたいときはこれを参考にする
- react-routerに入門する - Qiita

ReactのCSSフレームワーク

色々あって迷ったけど「Best of css framework 2019」で調べて、導入楽そう&書くの楽そう&おしゃれの直感で選んだ。

Evergreen-uiの他に自作のCSSを当てるときにstyled-componentを使っていた。

EvergreenがTypescriptに対応していない?

こんなエラーがでる。

 2769: No overload matches this call.  Overload 1 of 2, '(props: Readonly<SideSheetProps>): SideSheet', gave the...  Type '"top"' is not assignable to type 'Pick<PositionTypes, "top" | "bottom" | "left" | "right"> | undefined'.

どうやらevergreenがTypescriptに対応していないので下記のようにする

いや、対応しているっぽい?

これでいけた。initialStateでセットし、その値を使えばいけた

<Component initialState={{ isShown: false , position: Position.LEFT}}>
    <SideSheet
      position={state.position}

ReactRouterのLinkにCSSを付けたい

これでOK

const NavItem = styled(Link)`
  padding: 6px 8px 6px 16px;
`
<NavItem to="/">Home</NavItem>

Typescriptの連想配列の型の書き方がわからない

filterで返ってくるやつも型で縛りたい。Interfaceやジェネリックで縛る方法を模索。

paths = [
{name:'hoge', value: 1}
{name:'foo', value: 2}
]
paths.filter(path => path.name === 'hoge').value

みたいなことをしたい

TypeScriptのインターフェースの初期化

typescript interface initialisation

const [selectedOrder, setSelectedOrder] = useState<IOrder>({} as IOrder);

Inputタグにはnameをちゃんと指定する

こんなエラー

Type '{ refKey?: string | undefined; }' has no properties in common with type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.

TypeScriptだとinputタグにnameをちゃんと指定しないといけないっぽい。
react-dropzoneの型が指定されていないからかと思った。が、違った。

ダミーデータの準備

日本の郵便番号や氏名とかも対応しててとってもいいライブラリ。 - faker.js - generate massive amounts of fake data in node.js and the browser

終わり

とにかく開発環境を構築するのが面倒すぎる。 知らないものをインストールしたくないから調べてから導入するってのは当然の感覚として持っておいてもいいが、ことJSに関しては一回無視してもいいレベル。 初期に調べていたら開発できるまでに余裕で日が暮れてしまう。
調べていると「あれ、俺って何がしたかったんだっけ」と陥ってしまうので、バックエンド(&DB)とフロントエンドの疎通が取れるようになるところまでは一気に進んじゃったほうがいい。
細かく調べるのは後でいい。