今まで業務ではPHP, MySQL, 素のJavascriptを触っていた、いわゆるサーバー側のWebエンジニアがオールJavascriptの開発をする上で困ったことをずらずら書いていく。
今回は誰かに見てもらうというより自分のメモを公開するに近いので、全くまとまっていないが、部品だけでも誰かの参考になればいいなっていう感じ。
やりたかったこと
- Express, MonogoDB, React, TypeScriptの開発をしたい
- 実用的な成果物は求めていない。基本的な文法、データの流れを勉強したい
- 認証周りやデプロイはできなくていい(一番臭いところに蓋をしてとりあえず進むっていう戦法)
- Vimで開発したい
ようするにハードルを無くしたかった。
全体像を把握してから細部に取り組んだほうが早いし理解も深いはずだから。きっとね。
環境構築
大枠はこれを参考に。
バックエンドはExpress, TypeScript, MonogoDB、フロントエンドはReact, TypeScriptで開発。
初めはWebpackでts-lintやらbabelやらモリモリ構築しようとしていたが、覚えるものが多すぎてやめた。
ツールの使い方じゃなくて言語、FWを学びたいのが本質だったので、初期構築はシンプルにexpress-generate
とcreate-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でファイルアップロード
- Simple React File Upload
- expressjs/multer: Node.js middleware for handling
multipart/form-data
. - React + fetch APIでFileをUploadする - Qiita
- react-dropzone、Express、Firebaseを使った画像のアップロード - Qiita
POST通信をしたくて実装したけど、別にファイルのアップロードじゃなくても良かったかなと思う。
ExpressのTypeScript化
- TypeScript+Expressの快適な開発環境を作ってみた
- TypeScriptでExpress.js開発するときにやることまとめ (docker/lint/format/tsのまま実行/autoreload) - Qiita
- Should req.files be an array? · Issue #133 · expressjs/multer
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)とフロントエンドの疎通が取れるようになるところまでは一気に進んじゃったほうがいい。
細かく調べるのは後でいい。