20200123のvue.jsに関する記事は12件です。

Vue.js のディレクティブの記述を膨らませないために

概要

本記事では、Vue.jsのディレクティブって便利だけどなんかかさばって可読性が・・という方に
改善する方法をv-bindを例にして紹介していきます。

元のコード

aタグに対して2つのv-bindが設定されています。
2つくらいなら・・と思うかもしれませんがこれが膨張していくと恐ろしいことになります。

index.html
<div id="app">
  <a :href="url" :id="number">リンク</a>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    url: 'https://sample.com',
    number: 23
  }
})

改善

ひとつのオブジェクトにまとめることができます。
画面上の表示はなにも変わりません。

index.html
<div id="app">
  <a v-bind="{href: url, id: number}">リンク</a>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    url: 'https://sample.com',
    number: 23,
  }
})

さらに改善

dataの中にオブジェクトとして持たせておいて、html上ではbindObjectとバインドさせているだけになりました。こちらも画面上の表示はなにも変わりません。
こちらの方がhtmlに記載する量が少ないのでVue.jsを書く上での理想の形です。そして管理もしやすくなりますね。

index.html
<div id="app">
  <a v-bind="bindObject">リンク</a>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    bindObject: {
      url: 'https://sample.com',
      number: 23,
    }
  }
})

最後に

可読性を下げず、チームでも個人でも管理しやすい形でコーディングできるように自分ももっと気をつけていこうと思います。

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

Vue.jsのSPAをFlaskのバックエンドで支えて、Herokuで動かす!

前提

開発環境

  • mac OS X
  • Python 3.7.1
  • pip 19.3.1
  • Pipenv, version 2018.11.26 (インストール:python -m pip install pipenv)
  • Flask
  • Node.js v10.16.0
  • Vue.js (@vue/cli v4.1.2)
  • Herokuのアカウント、あるよ。

ディレクトリ構成の目標

[approute] 
   |-- [server] -- サーバーサイド(Flask app)
   |-- [client] -- クライアントサイド(Vue app)
   `-- サーバー起動にまつわる設定ファイル

サーバーサイドの準備

仮想空間の作成

$ pipenv install flask

→プロジェクト用の仮想空間&[Pipfile][Pipfile.lock]ファイルが作成される

[approute]
   |-- Pipfile
   `-- Pipfile.lock


(pc)Users/xxxxxx/virtualenvs/xxxxxxx

Flask appのシンプルな内容のコードを取り急ぎ置いてみる

HOME URLにやってきたら「Hello world」って返すだけ。

approute/server/main.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    hello = "Hello world"
    return hello

if __name__ == "__main__":
    app.run(debug=True)

仮想空間で実行するscriptを作成

packageは仮想空間にあるので、そちらを実行するようなスクリプトを作成

approute/Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
flask = "*"

[requires]
python_version = "3.7"

[scripts]                          //←追記
start = "python server/main.py"    //←追記

$ pipenv run start で実行できるようになる

Herokuにdeploy

gunicornをインストール

HerokuのPythonサポートはgunicornというWEBサーバーを通して行なっている様です。

https://devcenter.heroku.com/articles/python-gunicorn

ということで $ pipenv install gunicorn

approute/Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
flask = "*"
gunicorn = "*"   //←ここが追記された

[requires]
python_version = "3.7"

[scripts]
start = "python server/main.py"

そして、実行スクリプトを記載。server/main.pyのappを実行するには、↓のように書く

approute/Procfile
web: gunicorn server.main:app --log-file -

一応、ローカルでも$ gunicorn server.main:appで実行確認してみる

Herokuでapp作成

Heroku( https://jp.heroku.com/ )でアカウントを作成して、appも作成。
そしたらdeploy手順が掲載されているので、順次実行していく。

$ heroku login  // Herokuにログイン
$ cd approute //作業ルートに移動
$ git init   //git初期化
$ heroku git:remote -a hogehoge   //Herokuにリモートレポジトリ
$ git add .  
$ git commit -am "make it better"  //gitにコミット
$ git push heroku master  //Herokuにpush
$ heroku open //デプロイしたアプリを開く

Herokuにデプロイしたappから「hello world」言われました。
世界が開けたようです。よかったよかった。

クライアントアプリを作成

Vue UIでプロジェクトを作成

Vue UI、使いやすいので活用しようと思います。

z.png

Y.png

↑今回はアプリルートのgit使っているのでoffります。

これ以降のプリセットとかはとりあえず好きにすればいいと思う。
SPAなので、Routerは必須。

そしたら、しばらくダウンロードとかで待つと。。。
できあがるので、↓の画面から実行スクリプトも実行できますよと。

x.png

SPA

クライアントサイドでSPA作る

Vue UIの「プロジェクト設置>Vue CLI>アセットディレクトリ」からでも、
主導で設定ファイルを作ってでもいいので、staticファイルの書き出し先をstaticに設定。

approute/client/vue.config.js
module.exports = {
  assetsDir: 'static'
}

で、Vue UIでも、termimnalでもいいから、$ npm run buildを実行→appがコンパイルされる

[approute] 
   |-- [client] 
      |--[dist]   //←生成される
         |--[static]   //←jsとか書き出される
         `--index.html   //←rootのHTMLができる
      |--[public]
      |--[src]
      |--vue.config.js
      `--その他設定ファイル

サーバーサイドでクライアントアプリをテンプレートとして設定

結論として、こんな感じに書き換えます。

approute/server/main.py
import os
from flask import Flask, render_template

app = Flask(__name__, static_folder='../client/dist/static', template_folder='../client/dist')

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template('index.html')


if __name__ == "__main__":
    app.run()

ちっと分解。

from flask import Flask, render_template
(略)
    return render_template('index.html')

↑Flaskのrendar_templateを追加しまして、Vueのapp側で書き出したルートファイルである[index.html]をセットします。

app = Flask(__name__, static_folder='../client/dist/static', template_folder='../client/dist')

↑Vueのapp側で書き出したstaticファイルの場所をこのファイルから見た相対パスで書きます。

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):

↑どんなURLパスでやってきても、こちらで受け止めます、な感じ。

pipenv run start → お、世界に色が宿りました。

改めて、Herokuにpush

$ git add .  
$ git commit -am "color from vue"
$ git push heroku master
$ heroku open

してみたら、、
500 Internal Server Error

jinja2.exceptions.TemplateNotFound: index.html

とのことで、ローカルでは見つかっていたVueによって生成されたindex.htmlが見つからない...ってことですかね?
世界が闇に包まれました。

Herokuにdeploy・改

複数のbuildpackに対応する

すでに、pipfileによって、自動的にpythonサーバーが起動する。
そこに、node.jsサーバーもたちあげたい。

https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app

$ heroku buildpacks:add --index 1 heroku/nodejs

$ heroku buildpacks
=== xxx Buildpack URLs
1. heroku/nodejs
2. heroku/python

で、このまま起動しても

remote:  !     The 'heroku/nodejs' buildpack is set on this application, but was
remote:  !     unable to detect a Node.js codebase.
remote:  !         
remote:  !     A Node.js app on Heroku requires a 'package.json' at the root of
remote:  !     the directory structure.

と言われるわけで、package.jsonをルートに持ってこいと言われます。

Vue.jsのアプリのディレクトリ構造を変更する

いったん書き出していた[dist]ディレクトリを削除して、[client]ディレクトリの中身をまるっとルートに移動させる

[approute] 
   ===Flask===
   |--[server] -- main.py 
   |--Pipfile
   |--Procfile
   |--その他設定ファイル
   ===Vue.js===
   |--[public]
   |--[src]
   |--package.js
   |--babel.config.js
   |--vue.config.js
   `--その他設定ファイル

それにともなって、書き換え

approute/server/main.py
app = Flask(__name__, static_folder='../dist/static', template_folder='../dist')

Herokuにpushしてみたら、動いた!世界に光が戻りました。
ということで、このベースの世界から発展開発をしていきたいと思います。

結論

とりあえず、やったことを書いてみましたが。。
あったんだよね、先人の知恵
https://github.com/gtalarico/flask-vuejs-template
https://github.com/oleg-agapov/flask-vue-spa

なんか、ディレクトリ構成がね〜。
vue.config.jsとか設定して、1段掘れないかしら。

参考

FlaskとVue.jsでSPA Webアプリ開発
Heroku 複数ビルドパックの使い方

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

Vue.jsでイベントオブジェクトを取得する

概要

本記事では、Vueのディレクティブである v-on を使ってイベントオブジェクトを取得する方法を紹介します。

イベントオブジェクトとは

イベントオブジェクトは、イベントハンドラーおよびイベントリスナーにおいて実行される関数の引数として受け取ることのできるオブジェクトです。 そのイベントオブジェクトから、発生したイベントに関わる様々な情報(プロパティ)を知ることができ、またそのイベントを制御するメソッドを活用することができます。
詳しくは下記記事を参考にしてください。
https://phpjavascriptroom.com/?t=js&p=event_object

実際のコード

sampleTextというidを持たせたp要素の上にマウスを乗せた時、そのマウスの位置を取得し
その下のresultというidを持たせたp要素に表示させるという流れ。

index.html
<div id="app">
  <p id="sampleText" v-on:mousemove="mousePosition">ここにマウスを載せると下のX、Yの値が変わるよ</p>
  <p id="result">X:{{x}}, Y:{{y}}</p>
</div>
index.js
new Vue({
  el: '#app',
  data: {
    x: 0,
    y: 0
  },
  methods: {
    mousePosition: function(event) {
      this.x = event.clientX;
      this.y = event.clientY;
      // eventの中を見てみると、全てのイベントオブジェクトが入っている
      console.log(event);
    }
  }
})

補足

今回はclientX(Y)を取得したが、eventの中をconsole.logで見ると全ての情報が入っている。

補足2

引数に渡しているeventは好きなものに変えてもちゃんと動く。

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

初心者によるプログラミング学習ログ 218日目

100日チャレンジの218日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

218日目は

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

【Vue.js】条件に応じて表示するリストを切り替えるサンプルコード

はじめに

先日、こちらの記事を書きました。

ここで登場したコードに追記して、条件に応じて表示するリストを切り替える方法について記載します。

環境

OS: macOS Catalina 10.15.1
Vue: 2.6.10
vue-router: 2.6.10
vuetify: 2.1.0

結論

Sample.vue
<template>
  <v-list>
    <v-list-item
      v-for="item in selectedItems"
      :key="item.id"
      @click="triggerClick(item.action)"
    >
      <router-link :to="{ name: item.link }">
        <v-list-item-title>
          {{ item.name }}
        </v-list-item-title>
      </router-link>
    </v-list-item>
  </v-list>
</template>

<script>
  export default {
    data() {
      return {
        selectedPattern: 1, //ここで条件を切り替える
        items: [
          {id: 1, name: '1へのリンク', link: 'Component1', action: '', pattern: 1},
          {id: 2, name: '2へのリンク', link: 'Component2', action: '', pattern: 2},
          {id: 3, name: 'action1を実行', link: '', action: 'action1', pattern: 1},
          {id: 4, name: 'action2を実行', link: '', action: 'action2',  pattern: 2},
        ]
      }
    },
    computed: {
      /* 
       * filterを使って事前にリストを精査しておく
       * patternが1か2かで表示する内容を変更
       */
      selectedItems(){
        return this.items.filter(item => item.pattern === this.selectedPattern)
      }
    },
    methods: {
      triggerClick(action) {
        if (action === 'action1') {
          anyAction1()
        } else if (action === 'action2') {
          anyAction2()
        }
      }
    }
  }
</script>

この例では、selectedを手動で1としていますが、
応用としてVuexで作成したストアからstateを呼び出し、それに応じてリストを切り替える、といったようなことも可能になります。

※ログインしないとログアウトを表示しない、など。

補足:v-ifを使わない理由: 効率悪化

今回のパターンでは、ついv-ifv-forを同時に使えば切り替え出来るのでは?となりがちですが、公式ドキュメントでは非推奨となっています。

v-if と v-for を同時に利用することは 推奨されません。 詳細については スタイルガイド を参照ください。

スタイルガイドによると、

v-ifv-forを同時に使用した場合

再レンダリングするたびにリスト全体を繰り返し処理する必要があります。

computedプロパティ内でfilterを使っていた場合

フィルタリングされたリストは配列に関連する変更があった場合に のみ 再評価されるので、フィルタリングがはるかに効率的になります。

描画効率が悪くなってしまうんですね:sweat_smile:

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

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

Vue 3.0 系 Composition API への移行理由についてまとめてみた

公式リファレンス

はじめに ( Summary )

vue 3.0 の来たる日が近づいてきた(勝手にそう思っているだけ。)ので、公式サイトをほぼそのまま英訳した内容を踏まえて、お勉強をしようと思います、

コンポジションAPIの紹介
コンポーネントロジックの柔軟な合成を可能にする、function ベースのAPIの追加

vue2.0 → 3.0 への動機 ( Motivation )

なぜバージョンを上げ、コーディングの方法が大きく変わったのか、このセクションで詳しく説明がなされています。

vue 2.0 系の終焉

ロジックの再利用とコードの編成

Vueが非常に簡単に手に入り、小規模から中規模のアプリケーションを簡単に構築できることを皆が気に入っています。しかし今日、Vueの採用が拡大するにつれて、多くのユーザーがVueを使用して大規模プロジェクトを構築しています。これは、複数の開発者のチームによって長期間にわたって繰り返され、維持されています。長年にわたり、これらのプロジェクトのいくつかは、Vueの現在のAPIに伴うプログラミングモデルの限界に直面しました。

何が問題なのか?

問題は2つのカテゴリに要約できます。

1つ目

機能が時間とともに成長するにつれて、複雑なコンポーネントのコードを推論するのが難しくなります。これは特に、開発者が自分で書いていないコードを読んでいるときに起こります。根本的な原因は、Vueの既存のAPIがオプションによるコード編成を強制することですが、場合によっては、論理的な懸念によりコードを編成する方が理にかなっています。

コンポーネント間の推論が困難に
コンポーネント階層が増えたことにより、データの受け渡し(prop,emit)が増えました。これは中途参入者のコードのリーディングのコストを高めています。

柔軟性の欠如
data などのオプションがあることにより、強制力が働き、共通的なコーディングの恩恵が得られています。
しかし、その恩恵の制約が諸刃の剣となり、コーディングの柔軟性を失う原因となっています。
(詳しくは RFC の詳細設計で話されています。)

2つ目

複数のコンポーネント間でロジックを抽出および再利用するための、クリーンでコストのかからないメカニズムの欠如。

大規模な設計に対する柔軟でクリーンな設計の崩壊
Options API では、メソッドは methods に、データは data に処理・宣言を行います。
これは、外部への処理の抽出を困難とする理由の1つとなっています。

thisの多様や再代入が、クリーンアーキテクチャの導入を難しくしています。

Composition API はどうなのか?

このRFCで提案されているAPIは、コンポーネントコードを整理する際にユーザーに柔軟性を提供します。オプションによってコードを常に整理するように強制される代わりに、コードは、それぞれが特定の機能を処理する関数として整理できるようになりました。また、APIを使用すると、コンポーネント間、またはコンポーネント外でもロジックを抽出して再利用できます。

ロジックの切り出し
オプションによるコードの強制は残るものの、オプションが中心であるOptions Apiから、機能単位をベースとするComposition Apiになることで、コードが読みやすくなります。
関数の役割が明確になることでロジック抽出が容易になり、クリーンな設計が可能となります。

これらの目標がどのように達成されるかは、詳細設計セクションで示します。

より良い型推論 ( Better Type Inference )

大規模なプロジェクトに取り組んでいる開発者からのもう1つの一般的な機能要求は、TypeScriptサポートの向上です。 Vueの現在のAPIは、TypeScriptとの統合に関していくつかの課題を提起しました。
・・・
Vueの既存のAPIは型推論を念頭に置いて設計されたものではなく、TypeScriptでうまく動作させようとすると、非常に複雑になります。 TypeScriptでVueを使用するほとんどのユーザーは、vue-class-componentを使用しています。
...
これは、構築する上でかなりリスクの高い基盤となります。

クラスベースの頓挫
クラスベースの設計が序盤は提唱されたものの、基盤のリスクから撤廃されてしまいました。クラスベースの設計を可能とするためにデコーレータの依存が大きくなってしまったのが大きな理由です。

比較すると、このRFCで提案されているAPIは、ほとんどの場合プレーンタイプの変数と関数を使用します。提案されたAPIを使用して記述されたコードは、手動のタイプヒントをほとんど必要とせずに、完全なタイプ推論を楽しむことができます。また、提案されたAPIで記述されたコードはTypeScriptとプレーンJavaScriptでほとんど同じに見えるため、TypeScriptを使用していないユーザーでもIDEのサポートを改善するために入力の恩恵を受ける可能性があることを意味します

Composition API
現在提唱されている Composition Api は型推論のサポートを強めています。
このAPIでは、JavaScript と TypeScript でのコードの差異はほとんどありません。つまり型推論のサポートと同様のサポートを JavaScript においても IDE から受けられる可能性を秘めています。

まとめ

  • 散らばっていたロジックや変数をまとめて記述することが可能になるため自由度が高くなる
  • 既存の Options Api を Composition Api に移行させる価値は大いにあり
  • 自由度が高まることで可読性が上がり、実装スピードが向上しそう
  • Vue 2.0 でもライブラリを入れることで Composition Api を利用できる
  • まだ正式リリースではないため、今後のアップデートを含めてポジティブな期待が高まる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】TypeScript基礎編:Vue.extendでシンプルなコードを書こう

前置き

今回はNuxt.jsでTypeScript??
大まかに3つに分けて書いています✍️
・TSのメリット
・TSの書き方3つ
・Vue.extendコード例

TSのメリット

すごく簡単に言うと、
安全な開発がしやすくなります??‍♀️
ここが参考になります!
https://qiita.com/SoraKumo/items/43fba2ad2d10336a665

TSの書き方

TSを入れた場合の書き方は3パターン
・Vue.extend
・vue-class-component
・vue-property-decorator

シンプルで書きやすいのがVue.extendです?

Vue.extendのメリット

簡単⭕️
1番とっつきやすい書き方?

何故なら!
通常と書き方がほとんど変わらないから!
Vue、Nuxtらしさを保ったままTSが使えます。

TSなしの場合と変わるのは3点のみ!!!
・script langをtsに変更
・Vueモジュールをimport/extend
・コンポーネント、ページ自体にクラス名を付与

他は通常と変わらない
これがVue.extendの良さ?

他の書き方は?

@Componentとかになるあれ。
書き方が結構変わりますよね〜!
・vue-class-component
・vue-property-decorator

nuxtの場合はこちら
・nuxt-class-component
https://github.com/nuxt-community/nuxt-class-component
・nuxt-property-decorator
https://github.com/nuxt-community/nuxt-property-decorator

詳しい書き方はこちらが参考になります!
https://qiita.com/potato4d/items/c9c0c8e674f20c85948a

Vue.extend

【基礎構文】
通常と比較しても
シンプルで非常に分かりやすいですね!?

公式はこちら
https://typescript.nuxtjs.org/ja/
https://jp.vuejs.org/v2/guide/typescript.html#基本的な使い方

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 // コンポーネント・ページ自体にクラスを付与
 name: 'Component',
 components: {
 },
 props: {
 },
 data() {
  return {
  }
 },
 computed: {
 },
 mounted () {
 },
 methods: {
 },
 created () {
   console.log('CLICK!!!')// eslint-disable-line
 },
})
</script>

【通常】

index.vue
<script>
export default {
 components: {
 },
 props: {
 },
 data() {
  return {
  }
 },
 computed: {
 },
 mounted () {
 },
 methods: {
 },
 created () {
   console.log('CLICK!!!')// eslint-disable-line
 },
}
</script>

data

Stringの場合は
" "(double quote)ではなく
' ' (single quote)のみ⭕️

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 data () {
   return {
     userName: '',
   }
 },
})
</script>

props

TSなしの場合と変わりません。
・type
・required
・validator
全てそのまま書けます✍️

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 props: {
   status: {
     type: String,
     required: false,
     validator (value) {
       return [
         'default',
       ].includes(value)
     },
   },
 },
})
</script>

methods

こちらも同様
$emitなども通常通り記載可能です。

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 methods: {
   alert () {
     this.$emit('componentAlert')
   },
 },
})
</script>

console.log()

methodsやライフサイクルで
console.log()を使用する場合ESlintにひっかかります。
その場合は// eslint-disable-lineを追記すれば⭕️
https://eslint.org/docs/rules/no-console

index.vue
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
 name: 'Component',
 created () {
   console.log('created!!!')// eslint-disable-line
 },
})
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsのbeforeDestroyed()でイベントリスナーを削除できなかった時の対処法。

発端

偉い人「このページは横画面でしか表示したくない」
蝦「はい」

環境

OS: windows10 64bit
node.js v10.16.3
Nuxt.js v2.10.2

イベントリスナーを追加

蝦「画面の向きが変わるたびにVueファイル内のdata()の値を変えてやればおk」
とりあえずMDNのサンプルをそのままつかう。
Window: orientationchange イベント - Web API | MDN

ebi.js
<script>
//中略
beforeMount() {
    console.log("beforeMount");
    window.addEventListener("orientationchange", this.checkRotate, false);
  }

</script>

しかし

イベントリスナーが消えない。

「後はbeforeDestroy()でイベントリスナーを削除するように書けばおk」

kani.js
<script>
//中略
  beforeDestroy() {
    console.log("beforeDestroy");
    window.removeEventListener("orientationchange", this.checkRotate, false);
  },

</script>

消えない。
コメント 2020-01-23 145126.png
追加はされるが削除はされないので、該当ページに遷移するたびにイベントリスナーが増える。やばい

原因: beforeDestroy()もDestroy()も実行されてない

プリントデバッグしてみると、ページ離脱時にbeforeDestroy()もDestroy()も実行されてない。
再起動してもキャッシュを消しても実行されないという不思議。

解決策: ナビゲーションガードを使う。

どうにもならなくなったらbeforeRouteLeave(to, from, next)で削除すればおk。
ナビゲーションガード | Vue Router
Vue-Routerのナビゲーションガードを使ってみる - Qiita

uni.js
<script>
//中略
 beforeRouteLeave(to, from, next) {
    if (to.name) {
      next();
      window.removeEventListener("orientationchange", this.checkRotate, false);
      return;
    }
    return;
  },

</script>

消えました。
コメント 2020-01-23 151735.png

別のプロジェクトでは再現しなかったので、多分もう使うことはほぼないだろうけど備忘録代わりに書いておく。
原因がわかる人がいたらだれか教えてください。

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

今一度、NuxtJS v2.11.0でTypeScript v3.7.5を適用する方法を探してみた。

環境

  • Vue.js v2.6.11
  • NuxtJS v2.11.0
  • TypeScript v3.7.5

ご親切なお方がいらっしゃいましたら、間違っている箇所に関しては変更依頼を出していただけると幸いです。自身でも気づいた場合には速やかに修正を行います。

NuxtJSでTypeScriptを設定する

https://typescript.nuxtjs.org/guide/setup.html#installation

ドキュメントに従い、packageのインストールと、ビルド時の設定を行う。

npm i -D @nuxt/typescript-build
touch tsconfig.json vue-shim.d.ts
nuxt.config.js
export default {
  buildModules: ['@nuxt/typescript-build']
}
tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}
vue-shim.d.ts
declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

従来のAPIの場合

https://typescript.nuxtjs.org/ja/cookbook/components/#template Options API

最も既存のコードから変更が少ない方法をまず試してみた。Vue.extendを使うだけのやり方である。

export interface Animal {
  name: string
  link: string
}
export default Vue.extend({
  name: 'AnimalComponent',
  props: {
    title: {
      type: String,
      required: true
    },
    content: {
      type: String,
      required: true
    },
    animals: {
      type: Object,
      required: false
    } as PropOptions<Array<Animal>>
  }
})

npm run dev によるエラーは発生しないが、ブラウザ上で実行時に下記のプロパティエラーが発生する

ビルド時に型解決したものが実行時にエラーになるというのがなぜかよくわからない。
何かいい方法が見つかればよかったのだが、ドキュメントにこれ以上の情報がないので、一旦別の方法を探すことにした。

vue.runtime.esm.js?2b0e:619 [Vue warn]: Invalid prop: type check failed for prop "animals". Expected Object, got Array 

クラススタイルのVueコンポーネントの場合

https://typescript.nuxtjs.org/cookbook/components/#template Class API

割と主流の書き方だと思われる。また、TypeScriptとして扱うためのデコレータを使うので、理解しやすい。ただし、その反面次のコンポジションAPIとの思想の差がある。

npm i -S vue-property-decorator
import { Vue, Component, Prop } from 'vue-property-decorator'
export interface Animal {
  name: string
  link: string
}
export default class AnimalComponent extends Vue {
  @prop()
  title: string
  @prop()
  content: string
  @prop()
  animals: Array<Animal>
}

ビルド時にエラーが出ず、実行時にエラーもブラウザ上で出なかったが、実行すると今度はterminal側のconsoleに以下のエラーが出た。

10:22 Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.

これは、まさに今後変更の可能性があるという話で、実験的にデコレータ機能を使いたいならそういう設定をしろという警告である。
今回は、使いたいので下記の設定を末尾に加えようと思う。

tsconfig.json
{
  "experimentalDecorators": true
}

次は、以下のエラーが発生している。これは、プロパティの初期化時のチェックを厳格にしているがゆえに発生するエラー。

Property 'title' has no initializer and is not definitely assigned in the constructor.
Property 'content' has no initializer and is not definitely assigned in the constructor.
Property 'animals' has no initializer and is not definitely assigned in the constructor.

今回は、初期化時のチェックの必要がないので、以下の対応を行う

import { Vue, Component, Prop } from 'vue-property-decorator'
export interface Animal {
  name: string
  link: string
}
export default class AnimalComponent extends Vue {
  @prop()
  title!: string
  @prop()
  content!: string
  @prop()
  animals!: Array<Animal>
}

コンポジションAPIの場合

https://www.vuemastery.com/courses/vue-3-essentials/why-the-composition-api/
とりあえずなにこれ?と思い、なぜコンポジションAPIか?という動画だけ見れたのでスキップ気味にサクッと見た。

既存Vue.jsの課題点

Vue.jsのコンポーネントを作成して、機能追加や変更を加え続けると、そのコンポーネントが肥大化してくるので、内容を把握しにくくなってくること

課題への解決策

  1. なので、そのコンポーネントの中を整頓して意味を付けたブロックごとに分割してMixinする
    • Mixinパターンの解決策の欠点は名前空間の強いルール付が必要なことや、中身を読まないといわゆるプロパティ情報が不透明であること
  2. slotを利用する
    • 個人的にもMixinに比べコンポーネント単位で分割されていたりして、Vue.jsっぽいなぁと思い好んでいるが、slotで分割する分だけコンポーネントが増加するのでパフォーマンスが悪いと言われる

https://vue-composition-api-rfc.netlify.com/#summary
RFCも読んでみると、既存の課題解決方法の欠点を補うためにコンポジションAPIを使ったやり方に変えたいみたいだ。

ということで、3系を念頭にこの戦略を取るのは一つの手ではある。
ただし、今回は2系で主流のやり方をまとめたかったのでここは割愛した。

最後に

下記のように、Vue.jsが3系になる事を考えながら書くというのも一つの手だと思われる
https://stackoverflow.com/questions/58367558/best-practices-for-easy-migration-from-vuejs2-to-vuejs3

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

Vuetify2.xでサウンド再生した

概要

外部でなく、自前(ローカル)にあるファイルをぺろっとVuetifyのプロジェクトで音再生したかった。
そのあたりのメモ。

こまったこと

ぐぐったかんじwebpack.config.jsとかでwavファイルとかmp3ファイルをローディングできるように設定する例があった。
しかしvuetifyの標準プロジェクトだと、設定ファイルとしてplugins/vuetify.jsとvue.config.jsがあるが、webpack用なるファイルはない。

Vue + Nuxtでボードゲームを6時間で作ってみたという記事ではnuxtの設定ファイルで拡張子ルールを追記してる...

sassとかのローディングもおなじような設定したことが昔はあったなと、ググると次の記事をみつけた。
Vuetify v1.3で追加されたvuetify-loaderが便利だった

:thinking: これはimportすれば自動で解決してくれるってこと?

解決編

コンポーネントでimportすれば勝手に解決してくれた。
再生部分については、こちらの記事を参考にした。

main.js
Vue.prototype.$playSound = (path, volume = 1) => {
  var audio = new Audio(path);
  audio.volume = volume
  audio.play();
}
Hoge.vue
<template lang="pug">
  v-btn(@click="playSound") ほげー
(中略)
<script>
    import sndHoge from '../assets/sounds/hoge.wav'
(中略)

        methods: {
            playSound() {
                this.$playSound(sndHoge, 10)
            },
(以下略)

まとめ

うごいてるからヨシ! (buildしたときにデカくなるとか何か影響はでそうだが、未確認)

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

サーバ管理なしでWebサービス公開 -Firebase(Authentication, Hosting, Firestore) + GAEで『LogCrow(ログ情報共有サービス)』開発-

LogCrow_top.png

FirebaseとGoogle App Engineを中心に活用し、Webサービスをローンチしたのでそのアーキテクチャ & サービス概要を紹介します。

LogCrow(ログ情報共有サービス)

背景・動機

  • 私は日頃業務で、システムの保守・運用の効率化・改善について検討しており、いろんな運用者の知見が共有できている状態が作れると運用者が幸せになれるのではないかと考えています。
  • そんな中で、特に保守・運用時にキーポイントとなるログ情報についてフォーカスし、こういうログが出たときには「原因」として何が考えられ、どういった「対処」が必要となるのかといった情報が幅広く共有されると良いのではないかと思っています。
  • Google検索はもちろん優秀で、発生したログ文面をキーワードに検索すればある程度情報にたどり着くことはできますが、公開できない情報やログに特化した検索といったことはできないのでそれなりに検索結果を更に精査していく必要が出てきます。

チャレンジ

  • そこで、以下のような機能要件で手軽に利用できるサービス提供ができないかと思うようになりました。
    • ログのメッセージ内容を軸に、そのログの原因や対処方法を記録して管理できる
    • その記録はPublicに公開するものと非公開なものを管理できる
    • 登録されたログに対して検索できる(その際、非公開のものも効率よく検索できる)
    • 検索結果の中から特に自身に必要なログに対してお気に入り登録することができる
    • API経由で検索をできるようにし、監視ツール等で検知した後にその関連情報を自動連携できる
    • 保守・運用は組織で実施されることを想定し、非公開情報については個人・グループで権限分けできる
  • その他の要件としては、個人で開発することもあり、極力スモールスタートにしたいという要件もあります。
    • 必要としてくれる人がいるかどうかもわからないので極力費用をかかずにスタートを切りたい。
      • 開発工数も最小限に
      • 環境費用も最小限に

LogCrow(ログ情報共有サービス)の2020/1時点のできること

ドキュメントはこちらに公開しています。
初回のリリース時点では今の所以下の機能が実装できています。

  • ログの登録/編集/削除機能
  • ログの検索機能
    • ログの対象ソフトウェア、バージョンによる絞り込み検索機能
  • お気に入り追加機能
  • 公開ログ/非公開ログの管理機能
  • パスワード認証によるユーザ登録機能

LogCrowのアーキテクチャ

上記の要件を満たすため、以下のようなアーキテクチャを採用しています。
(あまり細かく比較検討できているわけではないのでベストな選択かどうかはわからないので参考までに。)

項目 採用製品・SW コメント
backend APIのサービス基盤 Google App Engineスタンダード環境 GoogleのPaaS基盤。全部function化も可能かと思うけど、APIがある程度種類あるので基本的に呼び出して実行されるものはPaaS基盤で動かすことにした。
backend APIの開発言語 Golang Pythonでも良かったけど、型の定義とか厳密にできて実装しやすく管理しやすそうなので。Golang好きなので。
frontendの開発言語 Vue.js + Vuex 仕事ではReact使っているのでその対比のためにVue.jsも使ってみたかった。学習コストも低そうなので。
frontendのデザインフレームワーク Vue Bootstrap 仕事ではMaterial UI使っているので、少し古いかもしれないがBootstrapベースも試してみたかった。
frontendのホスティング先 Firebase Hosting 認証系をFirebaseにしたので、ホスティングもそのままFirebaseで。単純なホスティングだけじゃなくモニタリング系も充実しているので。
バッチ的な処理実行 Cloud Functions for firebase 基本的にはFirebase GAEにあわせて利用。初回ユーザ登録時にバックエンドで初期登録処理とか走らせる用途で利用。
全文検索エンジン Algolia Cloud Firestoreの検索は簡易なことしかできなさそうで、GAEの公式ブログでもおすすめされていたので。
認証基盤 Firebase Authentication 非常に手軽に実装できて無償で使い始められたので。
データ管理基盤 Firestore GAEからもFirebaseからも呼び出しがかんたんなので。
ドキュメント管理、Issue管理 GitHub + GitHub Pages Markdownでドキュメント内容が管理できて、Gitのリポジトリで差分管理できるので。
パフォーマンス監視 Firebase Performance monitoring HostingしているVue.jsの中にSDKを組み込むことで応答速度等簡易に監視ができる。通知とかはなさそうだが、Firebase上のダッシュボードで適度に確認できるので。

各サービス無償枠などあるので、初期費用は特にかからずスタート可能になっています。

図で示すと以下のような感じになっています。

LogCrow全体構成図.png

処理の流れ

基本的にはロジック系は全てバックエンドのGAE側のGo実装のAPIが担っています。
フロントエンドはFirebase Authenticationとのやり取りによる認証処理と、バックエンドのAPIとのやり取りを通してあくまでViewの機能のみを持ちます。

例えば、認証~ログ検索の流れは以下となります。

LogCrow-認証~ログ検索フロー.png

Algoliaの活用によるログの検索機能

Algoliaは非常に優れた全文検索のSaaSです。APIを通してデータ登録することで簡単にGoogle検索のようなデータ検索機能が実現できます。
どのような順序でフィルタをかけるかとか、スペル誤りを考慮した曖昧検索など色々と使い勝手が良いです。FirebaseとGAEは基本的にGoogle内で完結しているのですが、Algoliaは別サービスの利用ということになるので、こちらは別管理となる点は少し手間がかかります。このサービスでは以下のようにログの登録の際、Firestoreへのデータ登録と同時にAlgoliaへもデータをPOSTし、Firestoreへの登録ログデータのID情報と紐付けてAlgoliaでインデックス管理するようなアーキテクチャを採用しています。

Algolia登録.png

今後の計画

まだほんの一部機能のみが実現できたという感じになっています。今後は以下のような追加機能開発も予定しています。
とはいえ、実際に使ってみて、こんな機能があった方が良いなとか、要望をいただければそれを優先的に作りたいと思っています。
ぜひ、要望はメールorGitHubのIssueに追加お願いします。

Issueの管理ページ

  • グループ管理の機能
  • 各ログへのコメント投稿機能
  • Google認証への対応等、他のOAuth認証対応
  • API機能提供
    • 監視ツール等他ツールからの呼び出しによる効果的利用促進
  • ログデータの登録簡略化機能
    • 生ログデータに対するログカテゴライズ機能提供 (こちらについては一部CLIでログクラスタリングできるツールをOSSで開発中。log-cluseter)

などなど

まとめ

このサービスは皆さんからのログがたくさん蓄積されていることでより効果が発揮されるものになっていますが、冒頭でも書いた通り、非公開ログを個人管理するユースケースにも対応しているのでひとまず個人メモとして記録するだけでも活用してみてください。

このサービスを作るに際し、得られた技術的な情報の詳細については別途まとめる予定です。ゼロからWebサービス開発するチュートリアル的な資料とする予定なのでお楽しみに。

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

html2canvasを使ってVue.js のサイトを画像で切り取る

html2canvasを使いたい

今回はVue.jsで作ったhtmlをhtml2canvasで画像にしたいと思います

参考資料

【2019年度版】Windowsでhtmlを画像化する方法(html2canvasの使い方)
【資料2】html2canvasとVue.jsでつくるコラ画像ジェネレータ
【エラー用資料】Vue.jsの初歩的なミス

環境

Windows 10
Visual Studio Code: 1.40.1 (system setup)
Chrome: 76.0.3809.146
Node.js: 12.4.0
V8: 7.6.303.31-electron.0
OS: Windows_NT x64 10.0.18362

index.htmlを作成

基本コード


<!DOCTYPE html>
<html lang="ja" dir="ltr">

<head>
    <meta charset="utf-8">
    <title>"html2canvas" </title>

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <!--Vue.jsの適用headに入れると全部に適応される-->
    <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-alpha.12/dist/html2canvas.min.js"></script>
    <!--html2canvasの適用headに入れると全部に適応される-->


    <style type="text/css">
        /* コラ画像用のCSS */
        #preview {
            overflow: auto;
            width: 100%;
        }

        /*
        #preview_inner {
            background: url(https://3.bp.blogspot.com/-cPqdLavQBXA/UZNyKhdm8RI/AAAAAAAASiM/NQy6g-muUK0/s800/syougatsu2_omijikuji2.png) no-repeat left bottom;
            background-size: 200px auto;
            position: relative;
            padding-bottom: 250px;
            width: 600px;
        }*/

        #baloon {
            position: relative;
            border: 4px solid #ff8888;
            background: #fff;
            border-radius: 8px;
            display: inline-block;
            font-weight: bold;
            font-size: 1.2rem;
            color: #444;
            padding: 10px;
            min-width: 200px;
            white-space: pre-wrap;
        }

        #baloon:before,
        #baloon:after {
            content: '';
            display: block;
            width: 0;
            height: 0;
            border-style: solid;
            border-width: 50px 15px 0 15px;
            border-color: #fff transparent transparent transparent;
            position: absolute;
            left: 20px;
            bottom: -50px;
        }

        #baloon:before {
            left: 16px;
            bottom: -60px;
            border-width: 58px 19px 0 19px;
            border-color: #ff8888 transparent transparent transparent;
        }

    </style>

</head>

<body>

    <div id="preview">
            <!--<div id="preview_inner">-->
                <div id="baloon">
                    <p>{{ message }}</p>
                    </div>

                <!--</div>-->
                <p>
                    <img width="200" src="https://3.bp.blogspot.com/-cPqdLavQBXA/UZNyKhdm8RI/AAAAAAAASiM/NQy6g-muUK0/s800/syougatsu2_omijikuji2.png">
                </p>
            <textarea class="form-control" v-model="message"></textarea>
    </div>

    <div>
    <button class="btn btn-primary btn-block" v-on:click="generate">画像を生成</button>
    </div>

    <script>
        var app = new Vue({
            el: '#preview',
            data: {
                message: ''
            },
            methods: {

            }
        })
    </script>

    <script>
    var generate = html2canvas(document.querySelector("#preview")).then(canvas => {
            document.body.appendChild(canvas)
        });
    </script>

</body>

</html>

HTML
にアクセス。

一部は画像に変換できたものの、
ターミナルとブラウザで検証をして見つかるエラーは消せたのですが
画像を含めて生成はできませんでした。
文字とかは画像化できるのですが、、、

image.png

あと、画像を生成を押す前からずっと出ています。
せっかく作ったボタンの意味!!
ここの原因はたぶんここ

    <script>
    var generate = html2canvas(document.querySelector("#preview")).then(canvas => {
            document.body.appendChild(canvas)
        });
    </script>

見るからにうまく組めていない怪しいコード。
ボタンを押した関数のgenerateで動いて欲しい、かつhtml2canvasを使いたい。
色々試して書いたのですが思いついたものはどれも駄目でした(´;ω;`)

あとはおみくじ画像がうまく画像化されない件。
こちらもstyleに埋め込んでいるから駄目なのかとimgタグでdivの中に持ってきたりしましたがどちらも駄目でした。

【エラー】Uncaught ReferenceError: Vue is not defined

これはそもそもHTMLとVue.jsの仕組みがきちんと分かっていない為に起きたエラーでした。
備忘録的に分かった事を書いておこうと思います。
下記コードの黄色線を引いてる部分を/bodyの直前にもってきていたのが失敗で、
読み込めていませんでした。

2CB828C3-033A-42DC-9D26-949822778374.jpg

完成品

2020-01-23_00h41_03.png

html2canvasとVue.jsは使えたものの目指していた形にはなりませんでした。
アドバイスありましたら随時募集中です。もしくは万が一少しでも参考になりましたら幸いです。
ありがとうございました。

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