20200324のvue.jsに関する記事は11件です。

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.xVue.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 について記事を書いていこうと思います。
是非、引き続き追っていただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

社内利用ツールを2020年らしいプロセスで作ってみる(Vue.js + Electron + SQLite3) Day2

ここまでの流れ

とあるSIerにおいて、社内システムで案件、要員、収益などの登録、参照は出来るものの、計画立案時にアドホックに変えながら計数を見るような機能がなくて困っているため、休暇中にツールを作ってみる試みです。

  1. ツール作成の目的と動作環境を検討する -> Day1
  2. アーキテクチャをいったん検討する -> Day1
  3. ユースケースを洗い出す -> Day2
  4. 画面イメージのスケッチを作成する -> Day2
  5. ドメインモデルを検討する(用語の定義、振る舞いの定義)
  6. ユースケース、画面イメージのワイヤフレーム、ドメインモデルを何周か見直す
  7. 当初決めた目的が果たせるか利用者目線を検証する(コンセプトの検証)
  8. 単純なCRUDアプリでアーキテクチャを検証する(実現方式の検証)
  9. コアになるユースケース1本だけプロトタイプ作成(MVP)
  10. 本格的に準備して開発を始める(テスト駆動のイテレーション開始)

【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 でローカルに保存したものが以下です。いい感じですね。

useCase.png

masterブランチの更新

まず、忘れないうちに先ほどの dummy.txt は削除しておきます。(ターミナル操作)

初版のユースケース図が完成して作成したissueがクローズ可能になったので、masterに対してプルリクします。
プルリクのコメント欄に "close #1" という文言をつけて、マージ時に自動でissueが閉じるようにしておきます。

プルリク内容を確認する画面で、"Files changed" タブの書類マーク "Display the rich diff" ボタンを押すと、噂の図形としてdiffが見えるモードになりました。わーい。

スクリーンショット 2020-03-24 17.38.22.png

でも今回は新規作成だけなので、この強力さが活きるは変更のときですね。

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というのがセットアップ簡単そうなので使うかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【環境構築】dockerでvue.js+Typescript+vuetify+express+Sequelizeの環境構築

dockerファイル作成

ディレクトリを作成

console
mkdir node
console
cd node
vim Dockerfile

node.jsを準備

dockerファイルを作成し、そこから各種プログラムを実行できるようにする。
ここではexpress,suquelize-cliの実行環境の構築ができるよう記載。

Dockerfile
FROM node:12.13
RUN npm install -g express-generator sequelize-cli

FROM nodeでノードのベースイメージ
RUNコマンドでnpm installを実行しexpressとsepulize-cliをインストールするコマンド

docker イメージを作成

これによりDockerfileが実行される。

console
docker build node/. -t serverapp:latest

-tオプションを付けていることで、名前(serverapp)とタグ名(latest)を指定している。
スクリーンショット 2020-02-29 15.02.42.png

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をインストールしていきます。
コンテナにロングインする。

console
docker exec -it serverapp /bin/bash

docker exec -it <コンテナ名>/bin/bash コンテナにログイン
-it コンテナを継続的に動かすために必要

root
cd /node
express .

destination is not empty, continue?(空ファイルじゃないけど大丈夫?)と聞かれますが、中にはDockerfaileがあるだけなので[y]で続行する。
スクリーンショット 2020-02-29 16.21.32.png

sequelizeなどの準備

ここで色々必要になるものの準備を行います。

root
npm install --save sequelize sqlite3 cors nodemon
npm install
それぞれ簡単解説

ここでは詳しい説明はしませんが、別記事をそれぞれ作成しようと思います。

名称 説明
sequelize データベースを管理するツール
sqlite3 簡易版データベース
cors セキュリティ上のルール
nodemon 自動でサーバーを再起動してくれるツール

これでローカルフォルダのnode/にファイルが作成できたはず。

sequelizeをセットアップ

root
sequelize init

スクリーンショット 2020-02-29 16.54.25.png

ターミナルに戻る

root
exit

node/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ファイルを作成。

console
mkdir node/data

もう一度dockerコンテナにログイン

console
docker exec -it serverapp /bin/bash

Unable to resolve sequelize package in
sequleize model:createコマンドを使いデータベースに雛形を作成
sequelize model:createコマンドとは?

root
sequelize model:create --name goal --underscored --attributes goalname:string

Unable to resolve sequelize package inのエラーが出る場合はこちら

マイグレートする

そもそもマイグレートとは、アプリケーションで使うデータベースの定義を自動的に作成・管理する機能です。

root
sequelize db:migrate

マイグレートが完了したら一度dockerを停止する。

terminal
docker stop serverapp

vueの準備

ディレクトリを作成

console
mkdir vue
console
vim vue/Dockerfile

作成したfrontapp内のDockerfileに以下の記述をする。
ここではvue/cliの実行環境の構築ができるよう記載。

Dockerfile
FROM node:12.13
RUN npm install -g @vue/cli

Dockerfileを元にコンテナイメージを作成。起動し、ローカルのフォルダをマウント。

console
docker build vue/. -t frontapp:latest

スクリーンショット 2020-03-08 05.19.39.png

console
docker run -itd --rm --name frontapp -v $PWD/vue:/vue frontapp:latest

docker run コマンドが正常に動いているか確認。

console
docker ps

https://www.dropbox.com/s/d767il7jbm3yprk/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%202020-03-08%2005.21.25.png?dl=0

console
docker exec -it frontapp /bin/bash

コンテナにログイン後以下を実行する。

root
cd /vue
vue create frontapp

スクリーンショット 2020-03-08 05.21.33.png

以下のように標準のyarnか高速のnpmどちらかで実行
npmで実行したいため[y]を選択する。

root
Your connection to the default yarn registry seems to be slow.
Use https://registry.npm.taobao.org for faster installation?

以下のメッセージに従いvueをインストールしていく。
今回はマニュアルでtypescriptなどもインストールしていく。

スクリーンショット 2020-03-08 05.33.47.png
スクリーンショット 2020-03-08 05.34.35.png

これでvueのインストールは完了です。

vuetifyの環境

frontappに移動し、vuetifyのプラグインを追加する。
インストールはデフォルトで行った。
インストール終了後はexitで一度コンテナからログアウトする

console
root@701c15dfea18:/# cd vue/frontapp
root@701c15dfea18:/vue/frontapp# vue add vuetify
exit

docker-compose.ymlファイルを準備する。

Node.jsとVue.jsそれぞれのコンテナを起動する際、composeファイルがあると起動/終了が楽なので、
docker-compose.ymlを下記のように記入。

docker-compose.yml
version: "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 down

localhost:8080でアクセス
スクリーンショット 2020-02-24 23.31.55.png
localhost:3000でアクセス
スクリーンショット 2020-02-24 23.34.35.png

参考記事

とてもお世話になりました。ありがとうございました:bow:
Vue.js + Express + Sequelize + DockerでCRUD搭載のTodoリストを作ってみる
【環境構築】Docker + Rails6 + Vue.js + Vuetifyの環境構築手順
GitHub PagesにDocker+Vue.js+Vuetifyでページを公開

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでjszipとfilesaver.jsを使ってzipフォルダを作成し、ダウンロードしてみる。

初めに

今回はjavascriptのライブラリの一つ、jszipfilesaver.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を勉強して記事を書こうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
import Vue from 'vue/dist/vue.esm';

const app = new Vue({
  el: '#app',
  data: {
    message: "Can you say hello?"
  }
});

スクリーンショット 2020-03-24 14.26.30.png

このように、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.js
import 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.js
import 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

https://qiita.com/toyoken/items/8a7e1d64fe8230301619

https://github.com/vuetifyjs/vuetify/issues/4068

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel-MixでVue + Vuetify + TypeScript

概要

あんまりイケてないのだが、Laravel-Mixで
Vue + Vuetify + TypeScriptをやってみたので備忘録がてらに。

環境

  • Laravel 6.x
  • Node.js 12
  • macOS

Vueのセットアップ

$ php artisan preset vue

Vuetifyのインストール

$ npm install -D vuetify

Vuetifyの設定

app.js
import 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.json
tsconfig.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.ts
import 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.ts
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

webpack.mix.jsの編集

webpack.mix.js
mix.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

でコンパイルが通れば完了。

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
import 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>

呼び出しに成功しているか確認します
2020-03-24 (5).png
ちゃんと表示されています!

まとめ

自分はTwitterアイコンの呼び出しをを省略記法で記述していたためうまく表示されませんでした。Solidアイコンのみ省略記法で呼び出すことができ、そのほかのRegularやBrandsアイコンについてはきちんと記述する必要があるようです。
以上、VueでFontAwesomeのアイコンを表示する方法でした。読みにくい点や異なる点などございましたら、ご指摘お願いします。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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」を選択しています。

gcp.jpg

Back-end側

Bask-end開発側のディレクトリ構造

Back-end側のディレクトリ構造は最低限以下の通りにしておけば良いと思います。必要に応じてファイルを増やしてもいいですが、後述するdockerfileに処理を追加する必要があることに留意すること

/root
  |-app.py
  |-requirements.txt
  |-cloudbuild.yaml
  '-dockerfile

Source Repositryの設定

以下の画像にあるように、「リポジトリを作成」があるので、それをクリックして作成されます。また、3点のマークを押下し、「Manage SSH Keys」を選択し、「SSH認証カギを登録」を選択すると、SSH認証カギが生成されます。これはリポジトリにプッシュする際に使用します。

SSH.jpg

Cloud Buildの設定

「Cloud Build」を有効にしていれば、「Cloud Build」タブ->「トリガー」タブを選択すると、トリガーを作成できます。設定は以下のような感じにします。

  • イベント:ブランチにpushする
  • ソース:作成したリポジトリ
  • ブランチ:特に設定する必要はないですが、ブランチごとに「本番用」/「テスト」用といったように出来る
  • ビルド構成:/cloudbuild.yaml

「cloudbuild.yaml」は以下のような感じとします。substitutionsを変更することで、設定を変えらえるようにしています。

  • _PLATFORM:Cloud runのサービスタイプの設定
  • _REGION:ロケーション設定
  • _SERVICE_NAME:サービス名
  • _AUTHENTICATION:アクセス権限設定
cloudbuild.yaml
steps:
  - 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についての記事も書いていますが、何となく知りたい場合は以下の記事を参照してください。

開発環境

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" //ロケーション設定
   }
 } ]
}

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
var 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は今後フロントエンドでスタンダードなフレームワークになるのかなぁと思っているのだけど、特にこだわりが無いのであれば、導入を検討しても良いんじゃないかなぁと思う。(むやみに新しい技術に飛びつくのは節操ないけど)。ある程度モダンな技術を採用しておくと採用の面でもプラスに働く可能性は高いですし、在籍しているエンジニアのスキルの向上にもなりそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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"と書かれた部分がページのトップにくるよう位置を指定できるのです。

なかなかのツワモノです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む