- 投稿日:2020-03-24T23:05:59+09:00
Vue Composition API の reactive と ref についてまとめてみた
はじめに
composition API で変更検知が可能な値を生成するには、
ref, reactive
のどちらかを使う必要があります。
computedもあるけど、今回は省略。そこで 2 つのリアクティブなオブジェクトと、それらをサポートするメソッドについて
まとめてみました。ref & Ref
【基本】 呼び出し方
変更検知が可能な リアクティブなオブジェクト を作成します。
refオブジェクトは内部に単一プロパティーを持ち、.valueで取得できます。変更されないことが確定している値以外は、基本的にリアクティブにしておくことを推めます。
ref(1)// 型は Ref<T>, 定義するときは ref<T>(value) const point: Ref<number> = ref<number>(1);リアクティブにする値はプリミティブ以外でも可能です。
ref(2)// オブジェクトやメソッドをリアクティブにすることも可能 const date: Ref<Date> = ref<Date>(new Date());レンダリングコンテキスト( TypeScript で定義した ref オブジェクト ) は、
テンプレートに表示するときに自動的にラップが解除されて.valueが参照されます。ref(3)<template> <!-- ref オブジェクトは変数名のまま利用可能 --> <div>{{ point }}</div> </template>【注意】 ref はここが躓きやすい 1
下記の例を見ていただければ気づくかと思うのですが、 pointB は
Ref<string>であるにも関わらず、
pointA はそれをRef<number>として認識してしまい、代入してしまっています。本来であれば型推論で警告が出るはずと思いましたが、出ません。
ref(4)const pointB: Ref<string> = ref<string>('1 point'); /* * 本来であれば * 型を変更するなら : Ref<Ref<number>> * 値を変更するなら : pointB → pointB.value * に書き換えなければいけない */ const pointA: Ref<number> = ref<number>(pointB); // NaN console.log(Number(pointA.value));もちろん、 Number で数値変換すると
NaNになります。
これでは型推論を採用している意味がなくなってしまいます。ref の型定義は以下のようになっています。
ref(5)// HasDefined は <S, unknown> の両者を比較することで、型が存在するかの確認を行っています function ref<S, T = unknown, R = HasDefined<S> extends true ? S : RefValue<T>>(raw: T): Ref<R>;定義を見るからに、
ジェネリクスで渡した型は返り値の型( R )として定義はされるけど引数の型( T )は基本なんでも良いよ
となっていました。
ですので、これらを踏まえて次のようにコーディングするのが正しいです。
ref(6)const pointB: Ref<string> = ref<string>('1 point'); // このとき型推論によって警告が出る const pointA: Ref<number> = ref<number>(pointB.value);このことから、型推論を効かせた正しいコーディングを行うためには、
ref に渡す値はリアクティブでない値にする
→リアクティブである値をさらにリアクティブにすることは厳禁に注意する必要があります。
そもそも行わないコーディングかと思いますが、コーディング上はできてしまうので気をつけましょう。【注意】 ref はここが躓きやすい 2
後述する
reactiveと ref を混ぜて記述するときに起こる問題です。
それは、reactive オブジェクト A の中に ref オブジェクト B があるとき です。このとき A のプロパティーとして B へのアクセスまたは変換を行おうとすると、自動的にラップが解除されます。
ただし、配列などの コレクションタイプ についてはラップ解除の対象外となっています。ref(7)リファレンスのコード抜粋const count = ref(0) const state = reactive({ count }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1【応用】 ユニオンも使えるよ!
応用と書いてますが、 TypeScript を使い慣れている方であれば取り上げるものではないかもしれません。
ref(8)// string も number も OK const point: Ref<number | string> = ref('1 point'); point.value = 100;reactive
【基本】 呼び出し方
ref と同じくリアクティブなオブジェクト を作成します。
こちらはver 2.xのVue.observable()と同等になります。引数にはオブジェクトを与えます。
プロパティーへのアクセスは ref とは違い.valueは必要ありません。reactive(1)const obj = reactive({ count: 0 }) // 0 console.log(obj.count)【提案】 ref vs reactive
公式の RFC でもあったように、用途に適した使い分けが求められる かと思います。
例として上がっている座標のプロパティー( x, y )は、 reactive が適していると思います。このように値の集合が 1 つの意味を持つときは
reactiveを採用すべきです。
一方で、集合を成さない値などについてはrefを使っていくべきです。つまり、
ref の引数としてオブジェクトを渡そうと考えたとき、それは reactive で定義すべき値
と考えれば良いかと思います。ですが、2 つの使い分けをすぐには完璧にはできないと思いますので
初めのとっかかりとして ref で記述し、継続的なリファクタリングで reactive にするかどうか
判断していくことで上手な使い分けができるようになっていくのかなと思います。ref, reactive をサポートするメソッド
【基本】 isRef
宣言した値の型が Ref であるかどうかを判別します。
複数のファイルを跨いだ変数など、型情報が理解しづらい値に対して有効です。型推論が可能である TypeScript では利用頻度は高くないかと思います。
そもそも、isRef を使い始めると言うことは 闇が深いコード であることを表している気もします...。isRef(1)// ref かどうかわからない変数の値を取得したいときに便利 const unwrapped = isRef(foo) ? foo.value : foo【基本】 toRefs
reactive オブジェクト A をプレーンなオブジェクト B に変換します。
このとき、A のプロパティーであった値は ref に変換されます。toRefs(1)const state = reactive<{ foo: number, bar: number }>({ foo: 1, bar: 2 }) // { foo: Ref<number>, bar: Ref<number> } const stateAsRefs = toRefs(state);このとき注意しなければいけないことがあります。
それは
toRefsによって生成されたオブジェクトのプロパティーが、
元のオブジェクトのプロパティーを シャローコピー していることです。toRefs(2)state.foo++ console.log(stateAsRefs.foo) // 2 stateAsRefs.foo.value++ console.log(state.foo) // 3利用するシーンとしては、テンプレートコンテキストに渡す際の
return { toRef(reactive(...)) }
などが考えられます。そうすることで、テンプレートでは ref と変わらない感覚で利用することが可能です。
一方で、意味合いのあるリアクティブオブジェクトを分解するということは、プロダクトの理解を深める という観点からはお勧めできないかと思いました。
終わりに
今回は、リアクティブな値である
ref,reactiveについてまとめました。
今後も随時、vue Composition APIについて記事を書いていこうと思います。
是非、引き続き追っていただけると嬉しいです。
- 投稿日:2020-03-24T20:08:40+09:00
社内利用ツールを2020年らしいプロセスで作ってみる(Vue.js + Electron + SQLite3) Day2
ここまでの流れ
とあるSIerにおいて、社内システムで案件、要員、収益などの登録、参照は出来るものの、計画立案時にアドホックに変えながら計数を見るような機能がなくて困っているため、休暇中にツールを作ってみる試みです。
ツール作成の目的と動作環境を検討する-> Day1アーキテクチャをいったん検討する-> Day1- ユースケースを洗い出す -> Day2
- 画面イメージのスケッチを作成する -> Day2
- ドメインモデルを検討する(用語の定義、振る舞いの定義)
- ユースケース、画面イメージのワイヤフレーム、ドメインモデルを何周か見直す
- 当初決めた目的が果たせるか利用者目線を検証する(コンセプトの検証)
- 単純なCRUDアプリでアーキテクチャを検証する(実現方式の検証)
- コアになるユースケース1本だけプロトタイプ作成(MVP)
- 本格的に準備して開発を始める(テスト駆動のイテレーション開始)
【Qiita】社内利用ツールを2020年らしいプロセスで作ってみる(Vue.js + Electron + SQLite3) Day1
開発日記(Day2)
GitHubにリポジトリを作成
draw.ioに入る前に、管理先となるGitHubリポジトリを作成して、ユースケース作るというissueおよびbranchを作成しました。
今回の活動は自分の周囲の人にも見せてフィードバックもらいたいので、Publicでやると決めていました。
ライセンスは Day1 で下調べしていた通り、GPLを選択しました。./uml/ 配下に配置したいので、ダサい方法ではありますが、ターミナルで uml/dummy.txt を作成して一度リモートのfeatureブランチにpushしました。
ユースケースを洗い出す
draw.ioを使って、まずは大雑把に出来ることを書きます。
保存先は先ほど作成したGitHubリポジトリ、featureブランチ、umlフォルダを選択し、拡張子は svg にしておきます。キリがいいところまで書いたら上部の「Unsaved changes. Click here ti save.」を押してGitHubにコミットします。
File > Export As > PNG でローカルに保存したものが以下です。いい感じですね。masterブランチの更新
まず、忘れないうちに先ほどの dummy.txt は削除しておきます。(ターミナル操作)
初版のユースケース図が完成して作成したissueがクローズ可能になったので、masterに対してプルリクします。
プルリクのコメント欄に "close #1" という文言をつけて、マージ時に自動でissueが閉じるようにしておきます。プルリク内容を確認する画面で、"Files changed" タブの書類マーク "Display the rich diff" ボタンを押すと、噂の図形としてdiffが見えるモードになりました。わーい。
でも今回は新規作成だけなので、この強力さが活きるは変更のときですね。
GitHub ActionsによるCIを勧められますが、本格開発の開始時にします。
さくっと承認してissueのクローズとmasterブランチへのユースケース図配置を確認しました。画面イメージをスケッチ
メニューの検討
"Webサイト デザイン 見本" というそのままのキーワードでググって、いくつかサイトを見て回って情報インプットしました。
上部に大項目を横に並べて、カーソルオンで小項目のリンクが出てくる形式がよく見るので操作も万人に分かりやすそうです。
気に入らなくなれば後で変えてもいいですし、考えさせない原則でいったんこれを採用することにしましょう。メニュー以外の登録/参照
メニュー以外は単純な CRUD 操作や、表形式での参照が中心なので、ToDoリストアプリや、tableの見せ方などのデザインを参考にします。
要員登録や案件登録のCRUD系でいうと、一覧表示の表を起点として、新規登録ボタンが表と独立して存在、編集や削除用のボタンは表内に列として設ける、というスタイルが分かりやすそうです。
新規登録や編集が押下されたら登録内容を入力する画面に遷移するイメージです。複数行編集は編集内容からしてなさそうですし、複数行削除も必要なシーンはとても限られていそうなので、チェックボックスは削ります。
Day3にむけて
Day2はここまでです。今日は昼食の後に盛大にお昼寝してしまいました。。
画面イメージのスケッチを書いていると早くもユースケースの不足に気付きましたが、いい傾向ですね。
明日は今日の頭の中に見え隠れしていたドメインモデルのクラス図を作ってみて、ユースケースと画面イメージの見直しサイクル(ワイヤフレーム化含む)へ進めていこうと思います。開発環境
Day2では実装まで行かないので、これは後でDay3かDay4に移動することになりそうです。
クライアントPCスペック
- MacBookAir 2012年モデル
- macOS Catalina 10.15.3
- Intel Core i5 1.8GHz
- DDR3 4GB 1600MHz
- Intel HD Graphics 4000 1536MB
- SSD 128GB
各種ソフトウェアバージョン
- Chrome
- VisualStudioCode
- homebrew
- node
- npm
- electron
- vue-cli
- node-sqlite3
vue-electronというのがセットアップ簡単そうなので使うかもしれません。
- 投稿日:2020-03-24T19:55:51+09:00
vue.js 練習
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .error{ color:red; } .flip-list-move{ transition:transform .5s; } </style> </head> <body> <div id="app1"> {{message}} <img v-bind:src="img_src"> </div> <div id="app2"> <p v-if="error"> みえますか? </p> </div> <div id="app3"> <p v-bind:class="error_class"> みえますか? </p> </div> <div id="app4"> <p>{{now}}</p> <button v-on:click="time">現在時刻を表示する</button> </div> <div id="app5"> <button v-on:click="shuffle">シャッフル</button> <transition-group name="flip-list" tag="ul"> <li v-for="pref in prefs" v-bind:key="pref.name"> {{pref.name}} </li> </transition-group> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script> <script> var app1 = new Vue({ el:"#app1", data:{ message:"あいうえお", img_src:'test.jpg' } }); var app2 = new Vue({ el:"#app2", data:{ error:true } }); var app3 = new Vue({ el:"#app3", data:{ error_class:"error" } }); var app4 = new Vue({ el:"#app4", data:{ now:"00:00:00" }, methods:{ time:function(e){ var date = new Date(); this.now = date.getDate() + "日" + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); } } }); var app5 = new Vue({ el:"#app5", data:{ prefs:[ {name:'北海道'}, {name:'群馬'}, {name:'東京'}, {name:'福岡'}, {name:'沖縄'} ] }, methods:{ shuffle:function(){ this.prefs = _.shuffle(this.prefs); } } }); </script> </body> </html>
- 投稿日:2020-03-24T18:08:24+09:00
【環境構築】dockerでvue.js+Typescript+vuetify+express+Sequelizeの環境構築
dockerファイル作成
ディレクトリを作成
consolemkdir nodeconsolecd node vim Dockerfilenode.jsを準備
dockerファイルを作成し、そこから各種プログラムを実行できるようにする。
ここではexpress,suquelize-cliの実行環境の構築ができるよう記載。DockerfileFROM node:12.13 RUN npm install -g express-generator sequelize-cliFROM nodeでノードのベースイメージ
RUNコマンドでnpm installを実行しexpressとsepulize-cliをインストールするコマンドdocker イメージを作成
これによりDockerfileが実行される。
consoledocker build node/. -t serverapp:latest-tオプションを付けていることで、名前(serverapp)とタグ名(latest)を指定している。
docker run -itd --rm --name serverapp -v $PWD/node:/node serverapp:latestオプションの説明
-itd コンテナを継続的に動かすために必要
--rm コンテナ終了時自動的に削除。
--name serverappというコンテナ名前で作成
-v ホスト側のディレクトリ:コンテナ側のマウントポイント
今回の場合は$PWDで現在いるディレクトリの/nodeがホスト側、/node serverapp:latestがマウントポイントとなる。express,sequelizeをインストールする
ドッカーコンテナにログインしexpressをインストールしていきます。
コンテナにロングインする。consoledocker exec -it serverapp /bin/bashdocker exec -it <コンテナ名>/bin/bash コンテナにログイン
-it コンテナを継続的に動かすために必要rootcd /node express .destination is not empty, continue?(空ファイルじゃないけど大丈夫?)と聞かれますが、中にはDockerfaileがあるだけなので[y]で続行する。
sequelizeなどの準備
ここで色々必要になるものの準備を行います。
rootnpm install --save sequelize sqlite3 cors nodemon npm installそれぞれ簡単解説
ここでは詳しい説明はしませんが、別記事をそれぞれ作成しようと思います。
名称 説明 sequelize データベースを管理するツール sqlite3 簡易版データベース cors セキュリティ上のルール nodemon 自動でサーバーを再起動してくれるツール これでローカルフォルダのnode/にファイルが作成できたはず。
sequelizeをセットアップ
rootsequelize initターミナルに戻る
rootexitnode/config/config.jsonの記載を変更する。
変更点
database mysql → sqliteへの変更
storage "./data/development.sqlite3"の記載をそれぞれに追加config.config.json{ "development": { "username": "root", "password": null, "database": "database_development", "host": "127.0.0.1", "dialect": "sqlite", "storage": "./data/development.sqlite3", "operatorsAliases": false }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "dialect": "sqlite", "storage": "./data/test.sqlite3", "operatorsAliases": false }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "sqlite", "storage": "./data/production.sqlite3", "operatorsAliases": false } }nodeファイルの中にconfig.jsonで指定したdataファイルを作成。
consolemkdir node/dataもう一度dockerコンテナにログイン
consoledocker exec -it serverapp /bin/bashUnable to resolve sequelize package in
sequleize model:createコマンドを使いデータベースに雛形を作成
sequelize model:createコマンドとは?rootsequelize model:create --name goal --underscored --attributes goalname:stringUnable to resolve sequelize package inのエラーが出る場合はこちら
マイグレートする
そもそもマイグレートとは、アプリケーションで使うデータベースの定義を自動的に作成・管理する機能です。
rootsequelize db:migrateマイグレートが完了したら一度dockerを停止する。
terminaldocker stop serverappvueの準備
ディレクトリを作成
consolemkdir vueconsolevim vue/Dockerfile作成したfrontapp内のDockerfileに以下の記述をする。
ここではvue/cliの実行環境の構築ができるよう記載。DockerfileFROM node:12.13 RUN npm install -g @vue/cliDockerfileを元にコンテナイメージを作成。起動し、ローカルのフォルダをマウント。
consoledocker build vue/. -t frontapp:latestconsoledocker run -itd --rm --name frontapp -v $PWD/vue:/vue frontapp:latestdocker run コマンドが正常に動いているか確認。
consoledocker psconsoledocker exec -it frontapp /bin/bashコンテナにログイン後以下を実行する。
rootcd /vue vue create frontapp以下のように標準のyarnか高速のnpmどちらかで実行
npmで実行したいため[y]を選択する。rootYour connection to the default yarn registry seems to be slow. Use https://registry.npm.taobao.org for faster installation?以下のメッセージに従いvueをインストールしていく。
今回はマニュアルでtypescriptなどもインストールしていく。これでvueのインストールは完了です。
vuetifyの環境
frontappに移動し、vuetifyのプラグインを追加する。
インストールはデフォルトで行った。
インストール終了後はexitで一度コンテナからログアウトするconsoleroot@701c15dfea18:/# cd vue/frontapp root@701c15dfea18:/vue/frontapp# vue add vuetify exitdocker-compose.ymlファイルを準備する。
Node.jsとVue.jsそれぞれのコンテナを起動する際、composeファイルがあると起動/終了が楽なので、
docker-compose.ymlを下記のように記入。docker-compose.ymlversion: "3" services: node: build: node/. volumes: - ./node:/node working_dir: /node command: ["npm", "start"] ports: - "3000:3000" vue: build: vue/. volumes: - ./vue:/vue working_dir: /vue/frontapp command: ["npm", "run", "serve"] ports: - "8080:8080"一度バックグランドで実行しそれぞれ表示を確認する。
docker-compose up -d # コンテナ終了は docker-compose downlocalhost:8080でアクセス
localhost:3000でアクセス
参考記事
とてもお世話になりました。ありがとうございました
Vue.js + Express + Sequelize + DockerでCRUD搭載のTodoリストを作ってみる
【環境構築】Docker + Rails6 + Vue.js + Vuetifyの環境構築手順
GitHub PagesにDocker+Vue.js+Vuetifyでページを公開
- 投稿日:2020-03-24T16:58:47+09:00
Vue.jsでjszipとfilesaver.jsを使ってzipフォルダを作成し、ダウンロードしてみる。
初めに
今回はjavascriptのライブラリの一つ、jszipとfilesaver.jsというライブラリの記事を書こうと思います。
jszipというものはzipとして圧縮するためのライブラリで,filesaver.jsはファイルをダウンロードするためのライブラリです。
必要最低限(zipフォルダにファイルやフォルダ又はフォルダの中にファイルを入れる)のやり方を書きます。必要な技術、ライブラリ
- vue.cli(vue.js)
- jszip
- filesaver.js
やり方
必要最低限をやるために必要なメソッドは、file() folder() generateAsync() saveAs() の4つ。
file()の第一引数はファイル名(string型),第二引数は内容(string型,array型etc)です。
folder()の第一引数はファイル名(string型)。
generateAsync()はzipフォルダを作成するためのメソッドで、第一引数には作成するzipのタイプを指定します。
saveAs()はfilesaver.jsのメソッドです。第一引数にはダウンロードするzipフォルダ、第二引数はzip名。practice.vue<template> <div> <a v-on:click="make">ダウンロード</a> </div> </template> <script> import jszip from 'jszip' import saveAs from 'filesaver' export default{ methods:{ make(){ let zip = new jszip(); //インスタンス作成 zip.folder('icon').file('test.txt','hello world') //iconフォルダを作り、その中にtest.txtファイルを作っている。 zip.file('practice.js','hello') // iconフォルダと同じ階層にjsファイルを作っている。 zip.generateAsync({type:'blob'}) //blobタイプのzipを作成。 .then(function(blob){ //zipデータを受け取る。 saveAs(blob,"hello.zip") //第一引数は受け取ったzipデータ,第二引数はzipの名前(.zipは無くてもいい) }) } } </script>これはjszipのページのexampleに載っているコードを少し書き直した物ですが、これだけでzipフォルダを作る事ができます。
終わりに
また時間があったらjszipとfilesaver.jsを勉強して記事を書こうと思います。
- 投稿日:2020-03-24T15:47:18+09:00
Vue.js Vuetify axios 導入と[Vuetify] Multiple instances of Vue detected のエラーについて
はじめに
現在、Vue on Railsでポートフォリオを開発中のゆーた@onoblogです。
エラーに遭遇し、解決までに、複数の記事を参考にしないと解決できなく時間がかかったため、まとめます。
使用環境
- macOS mojave
- vue on rails
- vue/cli 4.1.2
- webpack 4.2.2
- yarn 1.21.1
- Docker
状況
CDN無しで、vueとvuetifyとaxiosを導入時にエラーが発生。
Vueのみの導入
index.jsimport Vue from 'vue/dist/vue.esm'; const app = new Vue({ el: '#app', data: { message: "Can you say hello?" } });このように、
import Vue from 'vue/dist/vue.esm';を読み込むとインスタンスを2つ読み込むエラーが起きてしまいます。原因は、こちらの通りです。
解決方法
こちらを追加です。
webpack/environment.js. . . environment.config.merge({ resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } } })index.js- import Vue from 'vue/dist/vue.esm'; //削除 + import Vue from 'vue'; //追加 const app = new Vue({ el: '#app', data: { message: "Can you say hello?" } });そして、上記、2点をした後コンパイルをします。
- Dockerの場合 docker-compose run app bin/webpack - 通常の場合 bin/webpack
vuetifyの導入
情報がバラバラだったため結構試した結果、この形でうまくいきました!
index.jsimport Vue from 'vue/dist/vue.esm'; + import Vuetify from "vuetify"; + import "vuetify/dist/vuetify.min.css"; + Vue.use(Vuetify); const app = new Vue({ el: '#app', data: { message: "Can you say hello?" } });
axiosの導入
index.jsimport Vue from 'vue/dist/vue.esm'; + import axios from 'axios'; import Vuetify from "vuetify"; import "vuetify/dist/vuetify.min.css"; Vue.use(Vuetify); const app = new Vue({ el: '#app', data: { message: "Can you say hello?" } });最後に
index.html.haml#app .row {{ message}} .row %v-btn Vuetifyのボタン = javascript_pack_tag 'packs/index'参考
https://qiita.com/t1732/items/51da6fc82bd48707a6c6
https://github.com/SeregPie/VuetifyImageInput/issues/3
- 投稿日:2020-03-24T13:59:55+09:00
Laravel-MixでVue + Vuetify + TypeScript
概要
あんまりイケてないのだが、Laravel-Mixで
Vue + Vuetify + TypeScriptをやってみたので備忘録がてらに。環境
- Laravel 6.x
- Node.js 12
- macOS
Vueのセットアップ
$ php artisan preset vueVuetifyのインストール
$ npm install -D vuetifyVuetifyの設定
app.jsimport Vue from "vue"; import Vuetify from "vuetify"; import App from "./components/ExampleComponent"; Vue.use(Vuetify); new Vue({ el: "#app", components: App, vuetify: new Vuetify() });$ npm run devで一旦コンパイルが通るか確認。
TypeScriptのセットアップ
$ npm install -D ts-loader typescript vue-property-decorator $ touch tsconfig.jsontsconfig.json{ "compilerOptions": { "outDir": "./public/", "sourceMap": true, "strict": true, "noImplicitReturns": true, "noImplicitAny": true, "module": "es2015", "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "node", "target": "es5", "lib": [ "es2016", "dom" ] }, "include": [ "./resources/ts/**/*" ] }app.jsをapp.tsに
app.tsimport Vue from "vue"; import Vuetify from "vuetify"; import "vuetify/dist/vuetify.min.css"; import App from "./components/ExampleComponent.vue"; Vue.use(Vuetify); new Vue({ el: "#app", render: h => h(App), vuetify: new Vuetify() });resources/ts/types/index.d.tsの作成
index.d.tsdeclare module "*.vue" { import Vue from "vue"; export default Vue; }webpack.mix.jsの編集
webpack.mix.jsmix.ts("resources/ts/app.ts", "public/js"); mix.webpackConfig({ resolve: { extensions: [".js", ".jsx", ".vue", ".ts", ".tsx"], alias: { vue$: "vue/dist/vue.esm.js" } }, module: { rules: [ { test: /\.tsx?$/, loader: "ts-loader", options: { appendTsSuffixTo: [/\.vue$/] }, exclude: /node_modules/ } ] } });TypeScriptのコンパイル
$ npm run devでコンパイルが通れば完了。
参考記事
- 投稿日:2020-03-24T11:45:24+09:00
VueでFontAwesomeのアイコンを表示する方法(Vue.js)
はじめに
Vue.jsを用いてポートフォリオサイトを制作するのに、Vue-cliを利用したのですが、コンポーネントでFontAwesomeの呼び出し方法がわからず苦戦しました。
そこでの経験を踏まえて、Twitterアイコン等を表示させるために使うFontAwesomeのVueでの導入方法と記述方法について書いていきます。FontAwesomeを導入する
ルートディレクトリに移動する
> cd my-projectインストールする
> npm install --save @fortawesome/fontawesome-svg-core > npm install --save @fortawesome/free-solid-svg-icons > npm install --save @fortawesome/vue-fontawesomeさらに多くのアイコン(twitterアイコンなど)を使いたい場合はこちらも追加でインストールする
> npm install --save @fortawesome/free-brands-svg-icons > npm install --save @fortawesome/free-regular-svg-iconsインストール完了!
コーディング方法について
main.jsに以下のようにコードを追加するsrc/main.jsimport Vue from 'vue' import App from './App.vue' //ここから import { library } from '@fortawesome/fontawesome-svg-core' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { fas } from '@fortawesome/free-solid-svg-icons' //追加でインストールした場合は下の2行も import { far } from '@fortawesome/free-regular-svg-icons' import { fab } from '@fortawesome/free-brands-svg-icons' library.add(fas, far, fab) Vue.component('font-awesome-icon', FontAwesomeIcon) //ここまで Vue.config.productionTip = false new Vue({ render: h => h(App) }).$mount('#app')あとは表示したい場所でアイコンを呼び出すだけです
アイコンを呼び出す
今回はAboutページでアイコンを呼び出してみます。
src/views/About.vue<template> <div class="about"> <h1>This is an about page</h1> <!--Solidアイコン--> <font-awesome-icon :icon="['fas', 'envelope']"/> <!--Solidアイコンの省略記法--> <font-awesome-icon icon="envelope" /> <!--Regularアイコン--> <font-awesome-icon :icon="['far', 'envelope']"/> <!--Brandsアイコン--> <font-awesome-icon :icon="['fab', 'twitter']"/> </div> </template>呼び出しに成功しているか確認します
ちゃんと表示されています!まとめ
自分はTwitterアイコンの呼び出しをを省略記法で記述していたためうまく表示されませんでした。Solidアイコンのみ省略記法で呼び出すことができ、そのほかのRegularやBrandsアイコンについてはきちんと記述する必要があるようです。
以上、VueでFontAwesomeのアイコンを表示する方法でした。読みにくい点や異なる点などございましたら、ご指摘お願いします。参考
Font awesome を Vue.js で使ってみよう
https://qiita.com/kurararara/items/d76776a7dc2d763a068bVue CLIでFont AwesomeのTwitterアイコンを追加する方法
https://zarigani-design-office.com/blog/vue-cli-twitter/
- 投稿日:2020-03-24T11:29:40+09:00
Cloud run+Firebaseの開発について【Webアプリ開発】
概要
私はGCPを積極的に利用していますが、安く簡単にサービスを作ろうと思うと、Cloud run+Firebaseが良いのかなと思い、まとめました。メンテナンス性についても、Cloud runとCloud Functionsを比べると、Cloud runの方が良いと思います。
開発環境一覧
- Source Repositry
- Cloud build
- Cloud run
- Firebase(hosting)
はじめに
始めに上記の環境においてはサービスを有効状態にしてください。
また、gcloudでは最低限必要になIAMは以下の通り。
- Firebase 管理者
- Cloud run 管理者
今回はCloud buildを使用するため、IAMにおける「~~@cloudbuild.gserviceaccount.com」に以下の権限を付与する。
- Cloud Run 管理者
- Cloud Run サービス エージェント
- ストレージ オブジェクト管理者
構成
構成は以下を想定しています。Back-end開発者はリポジトリにプッシュすればいいし、Front-end開発者はBack-endを気にせず、APIサーバとしてCloud runを利用することを念頭に置いています。
ここで、Firestoreが構成に入っていますが、本記事では説明しません(別の記事で書くやもしれません)。
また、単純にFirestoreをベースにして処理を考えるならば「Cloud Functions」でも良いのですが、自作ライブラリの利用やソースコードのメンテナンス性を考えると、「Cloud Functions」であると不都合だったため、「Cloud run」を選択しています。Back-end側
Bask-end開発側のディレクトリ構造
Back-end側のディレクトリ構造は最低限以下の通りにしておけば良いと思います。必要に応じてファイルを増やしてもいいですが、後述するdockerfileに処理を追加する必要があることに留意すること。
/root |-app.py |-requirements.txt |-cloudbuild.yaml '-dockerfileSource Repositryの設定
以下の画像にあるように、「リポジトリを作成」があるので、それをクリックして作成されます。また、3点のマークを押下し、「Manage SSH Keys」を選択し、「SSH認証カギを登録」を選択すると、SSH認証カギが生成されます。これはリポジトリにプッシュする際に使用します。
Cloud Buildの設定
「Cloud Build」を有効にしていれば、「Cloud Build」タブ->「トリガー」タブを選択すると、トリガーを作成できます。設定は以下のような感じにします。
- イベント:ブランチにpushする
- ソース:作成したリポジトリ
- ブランチ:特に設定する必要はないですが、ブランチごとに「本番用」/「テスト」用といったように出来る
- ビルド構成:/cloudbuild.yaml
「cloudbuild.yaml」は以下のような感じとします。substitutionsを変更することで、設定を変えらえるようにしています。
- _PLATFORM:Cloud runのサービスタイプの設定
- _REGION:ロケーション設定
- _SERVICE_NAME:サービス名
- _AUTHENTICATION:アクセス権限設定
cloudbuild.yamlsteps: - name: 'gcr.io/cloud-builders/docker' id: 'build-docker-image' args: ['build', '-t', 'gcr.io/$PROJECT_ID/${_SERVICE_NAME}', '.'] - name: 'gcr.io/cloud-builders/docker' id: 'push-docker-image' args: ['push', 'gcr.io/$PROJECT_ID/${_SERVICE_NAME}'] - name: 'gcr.io/cloud-builders/gcloud' id: 'deploy-cloud-run' args: ['beta', 'run', 'deploy', '${_SERVICE_NAME}', '--image', 'gcr.io/$PROJECT_ID/${_SERVICE_NAME}', '--platform=${_PLATFORM}', '--region', '${_REGION}'] - name: 'gcr.io/cloud-builders/gcloud' id: 'apply-member-role-cloud-run' args: ['beta', 'run', 'services', 'add-iam-policy-binding', '${_SERVICE_NAME}', '--region', '${_REGION}', '--platform=${_PLATFORM}', '--member', '${_AUTHENTICATION}', '--role', 'roles/run.invoker'] substitutions: _PLATFORM: managed # full manage _REGION: asia-northeast1 # tokyo _SERVICE_NAME: <_SERVICE_NAME> # service name _AUTHENTICATION: allUsers # Google IAMドキュメント 概要を参照 images: - gcr.io/$PROJECT_ID/${_SERVICE_NAME}Cloud run
Cloud runにデプロイするイメージファイルの内容は以下のようなdockerfileにて生成します。内容は適宜変更します。
dockerfile# Use the official Python image. # https://hub.docker.com/_/python FROM python:3.7 # Copy local code to the container image. ENV APP_HOME /app WORKDIR $APP_HOME # Install production dependencies. COPY requirements.txt ./ COPY app.py ./ RUN pip install --no-cache-dir -r requirements.txt # Service must listen to $PORT environment variable. # This default value facilitates local development. ENV PORT 8080 # Run the web service on container startup. Here we use the gunicorn # webserver, with one worker process and 8 threads. # For environments with multiple CPU cores, increase the number of workers # to be equal to the cores available. CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 app:appここまでやっておくと、あとはリポジトリにプッシュすると勝手に「Cloud run」にディプロイされます。Build状態においては、Cloud consoleにて確認できます。
Front-end側
Firebase(hosting)
こちらはFront-end側の環境ですが、ディレクトリ構造については特に記述しません。基本的にはVue cliにて自動的にファイルが生成されます。一応Vueについての記事も書いていますが、何となく知りたい場合は以下の記事を参照してください。
- 出来る限り何も考えずにVue.jsを扱う:https://qiita.com/StrayDog/items/eceb96f14e5647428942
開発環境
Javascirptフレームワークは以下の通り。
- Vue.js
- Bootstrap-vue
ReactやAngular.jsも調べてみましたが、個人的にはVue.jsが一番とっつきやすかったです。
あまりデザインに拘りがない場合はBootstrapでCSSの記述を少なくした方が良いですね。
FirebaseからCloud runコンテナにリクエストを送信
「firebase.json」の「hosting」に「rewrites」を追加する。リクエストは「projectID.web.app/」「projectID.firebaseapp.com/」「カスタムドメイン/」から可能となる(例えば、get)。projectIDはGCPのプロジェクトホーム画面から確認できる。
firebase.json"hosting": { // ... // Add the "rewrites" attribute within "hosting" "rewrites": [ { "source": "**", //クライアント側の全てのリクエストを許可 "run": { "serviceId": "<service name>", //サービス名(Cloud runのコンテナ名) "region": "us-central1" //ロケーション設定 } } ] }参考
- Cloud Run を使用した動的コンテンツの配信とマイクロサービスのホスティング:https://firebase.google.com/docs/hosting/cloud-run?hl=ja#direct_requests_to_container
- ホスティング動作を構成する:https://firebase.google.com/docs/hosting/full-config#direct_requests_to_a_cloud_run_container
- Google IAMドキュメント 概要:https://cloud.google.com/iam/docs/overview?hl=ja#concepts_related_identity
- 超簡単。GCPのサーバーレス環境Cloud RunにGoアプリを自動デプロイ!:https://www.apps-gcp.com/deploy-go-app-to-cloud-run-by-cloud-build/
- 投稿日:2020-03-24T01:07:02+09:00
Vue.jsによるバリデーションのサンプルを作ってみた
Vue.jsを使うメリット
導入のしやすさ
Vue.jsが流行っているのは段階的に導入を進めていきやすいからというのが挙げられると思います。また実装の規模によってCDNで導入したりnpmコマンドでVue CLIを入れてがっつりVueを使用して構築することも可能です。
大量のイベントの記述を一掃できる
Vue.jsで実装を行うとjQueryで大量に記述するonclickなどのイベントの記述を一掃することができます。
HTMLに$(.js-selector)のようにクラスの先頭にjs-って付けてJSだけで使用されるクラスををたくさん記述する必要がなくなるのでHTMLの見通しも良くなります。またDOMの構造が変化してもプログラムが動かなくなることが基本的にありません。バリデーションのサンプルを実装した
See the Pen wRmvOO by YusukeIkeda (@YusukeIkeda) on CodePen.
Vue.jsでバリデーションを実装する場合、プラグインを導入する方法もありますが、まずはプラグインを使わず実装を出来た方が理解度が変わると思うので、今回はプラグインを使わないで実装をしてみました。
HTML・CSSについて
CSSに
[v-cloak] {display: none;}という記述がありますが、これはチラツキ防止です。
Vue.jsが完全に読み込まれる前に、一瞬だけマスタッシュ構文 {{ checkName }} が見えてしまうのを防ぐ為、Vue.jsが完全に読み込むまでdisplay:none;で見えなくしてくれます。HTML<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Vue.jsによるサンプルバリデーション</title> <style> [v-cloak] { display: none; } table td{ width: 200px; } table input[type="text"]{ width: 100%; } </style> </head> <body> <div id="app"> <div v-cloak> <!--エラーメッセージ--> <p v-show="!this.name.flag">{{checkName}}</p> <p v-show="!this.tel.flag">{{checkTel}}</p> <p v-show="!this.email.flag">{{checkEmail}}</p> </div> <!--入力項目--> <form method="post" action=""> <table> <tr> <th>名前:</th><td><input type="text" v-model="name.text"></td> </tr> <tr> <th>電話番号:</th><td><input type="text" v-model="tel.text"></td> </tr> <tr> <th>メール:</th><td><input type="text" v-model="email.text"></td> </tr> </table> </form> </div> <script src="https://unpkg.com/vue"></script> <script src="script.js"></script> </body> </html>JavaScriptについて
【実装における課題】
if(checkval == 0)というif文で文字列の長さをチェックして、未入力かどうかの判定をしているけど、このif文が初回アクセス時も動いてしまう。その為画面にアクセスしたらいきなりエラーメッセージが表示される状態。対処療法として初期化の文字列に半角スペースを入れて、エラーメッセージを表示させないようにしたけど、これは根本的な解決策じゃない…。良い解決策をご存じの方、コメントで教えていただけたら幸いです…!
script.jsvar app = new Vue({ el: '#app', data: { name:{ text: ' ', max:'名前は10文字以内で入力してください', require:'名前は必須です', flag : true, }, tel:{ text: ' ', max:'電話番号は10文字以内で入力してください', require:'電話番号は必須です', flag : true, }, email:{ text: ' ', max:'メールアドレスは100文字以内で入力してください', require:'メールアドレスは必須です', flag : true, }, }, computed: { checkName: function() { let checkval = this.name.text.length; if(checkval == 0) { //未入力チェック this.name.flag = false; return this.name.require; } else if(checkval > 10) { //文字の制限チェック this.name.flag = false; return this.name.max; } this.name.flag = true; }, checkTel: function() { let checkval = this.tel.text.length; if(checkval == 0) { //未入力チェック this.tel.flag = false; return this.name.require; } else if(checkval > 10) { //文字の制限チェック this.tel.flag = false; return this.tel.max; } this.tel.flag = true; }, checkEmail: function() { let checkval = this.email.text.length; if(checkval == 0) { //未入力チェック this.tel.flag = false; return this.name.require; } else if(checkval > 100) { //文字の制限チェック this.email.flag = false; return this.email.max; } this.email.flag = true; } } });まとめ
今回のサンプルはとりあえず動くけど、実装方法は絶対に良くないのでもっと理解を深める必要があるなぁと思います。
Vue.jsは今後フロントエンドでスタンダードなフレームワークになるのかなぁと思っているのだけど、特にこだわりが無いのであれば、導入を検討しても良いんじゃないかなぁと思う。(むやみに新しい技術に飛びつくのは節操ないけど)。ある程度モダンな技術を採用しておくと採用の面でもプラスに働く可能性は高いですし、在籍しているエンジニアのスキルの向上にもなりそうです。
- 投稿日:2020-03-24T00:18:20+09:00
vue cliのscroll behavior: アンカーリンクをつくる方法
こんにちは。備忘録です。
ページ内にたくさんの記事があるとします。1ページに表示されている記事数は5つ。ところが一番下にある「もっと読む」というボタンをクリックすると、新たに5記事が表示される。そんな動きをしたいとします。
ところがアンカーリンクを設定しなければ、「もっと読む」をクリックしたときにページのトップに連れて行かれてしまいます。UX的には、「もっと読む」をクリックしたときは、トップに飛ばされないまま、続く5つの記事が見たいです。
こんなとき、vue.jsではscroll behaviorという機能を使って、トップに連れて行かれないよう調整することができます。
(下に来る例が上にくる例と違うので紛らわしいのですが、、、)
Step 1. RouterのJSファイルをいじる。
const router = new Router({ mode: "history", scrollBehavior(to, from, savedPosition) { //ここでscrollを調整する。ここにはto, from, savedPositionの三つの引数が入ります。 if (savedPosition) { return savedPosition; //戻る(?)もしくは進む(?)ボタンを操作したときは、デフォルトのpositionでロードしてください。 } else { //そのほかの場合はこのpositionを効かせてちょうだい。 const position = {}; if (to.hash) { //hashがあれば…… position.selector = to.hash; //positionはhashが指定したところだよ。 if (to.hash === "#animal") { //hashがanimalというidを指定しているのであれば position.offset = { y: 200 }; //offsetを効かせてください。※ここはoffsetを効かせていますが、なんでも指定できます。 } if (document.querySelector(to.hash)) { return position;//hashがあれば、そのpositionを教えてください。 } return false; //そもそもhashがなければ、デフォルトの操作をしてください。 } } }, routes: [ { path: "/", name: "Home", component: Home, props: true } });Step 2. Scroll behavior を効かせたい instance/componentをいじる。
<section class="zoo-information"> <h1>Our Zoo</h1> <p>{{zoo.description}}</p> </section> <section class="animals"> <h2>I think {{ animal.name }} is the best</h2> <div class="cards" id="animal"> <div v-for="animal in animals" :key="animal.slug" class="card"> <router-link :to="{ name: 'animalDetails', params: { animalSlug: animal.slug}, hash: '#animal' }" > //ここにクリックしたとき、トップに行かず、#animalのところで位置をキープしたい <img :src="require(`@/assets/${animal.image}`)" :alt="animal.name" /> <span class="card__text">{{ animal.name }}</span> </router-link> </div> </div> <router-view :key="$route.path" /> </section>何も設定しなければ、
router-linkのリンクをクリックしたとき、.zoo-informationがトップにくるようページが表示されるでしょう。でも、せっかくrouter-linkをクリックしているので、UX的にもrouter-linkをクリックしたときはrouter-linkの中身がページのトップにくるよう表示したいです。そこで
to.hashをrouter.jsで指定しておくと、router-linkをクリックしたときにid="animal"と書かれた部分がページのトップにくるよう位置を指定できるのです。なかなかのツワモノです。














