前回で作ったAPIのフロントエンドアプリケーションを作ろうと思います。
どういうアプリ?
サンプルとして作ったAPIが住所録的だったので、住所録を作りました。
以下の機能があります。
- 登録(確認付き)
- 更新(確認付き)
- 削除(確認ダイアログ付き)
- 一覧表示
特徴
取り立てて特徴はありませんが、以下を追加してみました。
- ほぼ全部TypeScript
Nuxt.jsはあまりTypeScriptにやさしくないので使ったことがなかったのですが、
今回はTypeScriptを採用してみました。 - vee-validateによる親子コンポーネント間のバリデーション
子コンポーネントのバリデーション結果を親コンポーネントで判定してみました。 - デコレータ(vuex-module-decorators)を使用しない
なんとなくデコレータが好きじゃないので、使わずに書いてみました。
環境
- nodejs: v12.16.3
- yarn: 1.12.3
- nuxt.js: 2.14.7
準備
以下で作り始めました。
$ npx create-nuxt-app
ソース(ディレクトリ)構成
ソースはここにあります。
以下が結果のソースです。
|-- components | |-- container | `-- present | |-- addr_form.vue :登録フォーム | |-- addr_form_readonly.vue :登録フォーム(確認用) | |-- addr_id_form.vue :更新フォーム | |-- addr_id_readonly_form.vue :更新フォーム(確認用) | `-- confirm_dialog.vue :削除確認用ダイヤログ |-- layouts | |-- default.vue |-- lib | |-- addr.ts :addrの型を設定 | `-- api.ts :axios API呼び出し用 |-- nuxt.config.js |-- package.json |-- pages | |-- addr_add.vue :登録画面 | |-- addr_confirm.vue :登録画面(確認) | |-- addr_edit_confirm.vue :更新画面(確認) | |-- addr_edit_save.vue :更新画面(保存) | |-- addr_save.vue :登録画面(保存) | |-- addrs.vue :一覧画面 | |-- editaddr | | `-- _id.vue :更新画面 | `-- index.vue |-- plugins | |-- axios.js :$axios用プラグイン | `-- vee-validate.js :フォームバリデーション設定 |-- store | `-- index.ts :addrs(APIで保持している住所の配列) |-- tsconfig.json |-- types | |-- index.d.ts :TypeScriptの型指定 | `-- vue-shim.ts `-- yarn.lock
コンポーネントの種類
コンポーネントを以下のように分類してみました。よくある分類だと思います。
- components/container <– データをもつコンポーネント。
- components/present <– prop経由でしか持たない。callbackは親コンポーネント側を使用する。
と言っても、今回はcontainerはありませんでした。
起動方法
今回は、FastAPIとUI側で別のURLにしてみました。
FastAPI側の起動は以下の通りです。
$ pipenv run uvicorn app.run:app --reload --host 0.0.0.0 --port 18001
UI側の起動は以下の通りです。192.168.132.128:18000はテスト環境での値なので、
適宜変更してください。
$ cd mvc $ yarn install $ yarn run dev ? Listening on: http://192.168.132.128:18000/ No issues found.
CORS回避(API側)
CORSを回避するため、API側でoriginが違う場合の対処をしてみました。
FastAPI側で以下の対処を追加すれば、CORSを回避できます。
originsはUI側からどのようにアクセスしに来るかによります。
+from fastapi.middleware.cors import CORSMiddleware import os from starlette.requests import Request import sys @@ -12,11 +13,24 @@ sys.path.append(ROOT_DIR) from app.urls import router as app_router from app.settings.settings import Settings +origins = [ + "http://192.168.132.128:18000", + "http://192.168.0.12:18000", +] settings = Settings() app = FastAPI(openapi_url='/openapi.json') app.add_middleware(DBSessionMiddleware, db_url=settings.DATABASE_URI) +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +
vee-validateによる親子コンポーネント間のバリデーション
思ったよりは簡単で、子側は以下のように、v-slotとrulesを設定し
<validation-provider v-slot="{ errors }" rules="required|ipaddr" name="IPアドレス" > <v-text-field v-model="addrObj.ipaddr" label="IPアドレス" :error-messages="errors" required /> </validation-provider>
親側には以下を設定し、submitのタイミングでvalidationを呼び出せばいいだけでした。
<validation-observer ref="validationObserver" tag="div"> <AddrForm :addrObj=addrObj /> </validation-observer> methods: { async submit(addrObj) { const isValid = await this.$refs.validationObserver.validate(); if (!isValid) return false; this.$router.push({ name: 'addr_confirm', params: { addrObj: addrObj }}); } },
開発する際にはまったこと
むしろはまったことしかありません。。多すぎて覚えてない。。
前半は、そもそも何も動かなくて、後半はnuxt.config.jsやtsconfig.jsonに設定をする必要があったことが多い気がします。
まだ分からない点
いまだに実装上分かってないことがあります。
FastAPIで戻り値がおかしい
以下のようにDBのupdate後の値をAPIで戻すべくModelを返却しているのですが、なぜか辞書にしないとUI側で値が取れなかったりします。ちゃんと理由を調べてない。。
def asdict(self): result = OrderedDict() for key in self.__mapper__.c.keys(): if getattr(self, key) is not None: result[key] = str(getattr(self, key)) else: result[key] = getattr(self, key) return result def update_addr_query(db: Session, addr: schemas.AddrUpdateEntity): db_addr = db.query(models.Addr).filter(models.Addr.id==addr.id).first() db_addr.name = addr.name db_addr.addr = addr.addr db_addr.ipaddr = addr.ipaddr db_addr.update_time = datetime.datetime.now() db.commit() # 辞書に変更しないと、modelsがそのまま反映されてしまうため asdict(db_addr) return db_addr
/addrsをリロード(Ctrl+R)すると画面が崩れる
リロードが終わると正常なんですが、、よく分かってません。
終わりに
思った通り苦労しました。作ったつもりでいきなり動かない。動かないけど、どこをどう調べていいかよく分からない。
なのでしょうがなく、これのように小さく作って、段々と大きくしていくことにしてます。。
参考
https://qiita.com/shindex/items/a90217b9e4c03c5b5215
- Nuxt.jsのTypeScript環境
https://qiita.com/ikedaHi/items/1001594a386815c0ce85
- ボダンでの遷移、コンポーネント間通信、syncのやりかた。
https://qiita.com/after666/items/7e94d61fed89406ae59a
- actionsの引数について
https://qiita.com/tshcstrm/items/aeb9cc7da014fd2b8304
- TypeScriptでのpropsの書き方。
- 親子間通信
- emit
- 親子間のイベント伝達
https://note.com/aliz/n/ne1d2fc4fb1ef
- nuxt.jsでのvue-router
https://typescript.nuxtjs.org/cookbook/store.html#class-based
- classベースの書き方(今回はVanilla的に記述)