20200228のvue.jsに関する記事は20件です。

nuxtでDOMException: Failed to execute 'appendChild' on 'Node'エラー

Failed to execute 'appendChild' on 'Node'

nuxt でこんな感じのエラーが出る
因みにdev では出ない。

DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
    at Object.appendChild

原因

<template> タグ内に <body> が入ってると起こる
色々なエラーにつながるので原因究明がしづらい。注意。

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

Vue.js研修資料

証券営業 → テックエキスパート卒業生 → インターネット広告会社(エンジニア6ヶ月目)
Twitter → https://twitter.com/ar_tokki
 
言語
 python Vuejs AWS
 機械学習と統計の勉強中
 
TECH::EXPERTを卒業後、転職して半年間の振り返り
 https://qiita.com/tokki7127/items/2eb5bbd3b1bb54e33824

Vue.jsチートシート
 https://qiita.com/tokki7127/items/4e0429e0f0ab34930302

この資料について

・2020年の新卒に向けた研修資料である
・目的はVue.jsへの導入
・プロゲートで【HTML・CSS・JavaScript】を一通り終えた後を想定して作った
・この講義の後は下のサイトのイントロコースをやってもらう
https://www.vuemastery.com/ 
イントロコース完了後にVueUIの Vuetifyについて講義を行う(キータで投稿予定)
https://vuetifyjs.com/ja/

Vue.jsとは

JavaScriptの3大フレームワークの1つ
左から順番にAngular・React・Vue.js
スクリーンショット 2020-02-28 21.58.54.png

利用している企業

Angular
 ・YouTube Google Udemy Nike PayPal
React
 ・Facebook Instagram Netflix Dropbox
Vue.js
 ・Xiaomi Alibaba DeNA LINE

情報の比較

Angular React Vue
開発元 Google+コミュニティ Facebook+コミュニティ コミュニティ主体
初期リリース日 2010/10(Angular.js) 2013/07 2013/12
機能 フルスタック ユーザーインターフェイス ユーザーインターフェイス
ルーティング 同梱 React Router Vue Router
適切な開発規模 中〜やや大 小〜中
エコシステム 多い 豊富 小〜中
学習コスト 高い やや低〜中
構文上の特徴 TypeScript JSX 単一コンポーネントファイルシステム

Googleトレンドのトピックの推移

スクリーンショット 2020-02-28 22.19.01.png

Vue.jsのメリット

学習コストが低い

公式ドキュメントがしっかりしている
日本語ドキュメントが多い

軽量なフレームワークである

基本機能に絞ることで簡単に作れる

DOM操作を自動的に行ってくれる

小中規模のWebサイトを作るのに向いている

SPA(Single Page Application)開発につよい

簡単かつシンプルで日本語ドキュメントもある!!→社内システムを作るのにちょうどいい

Vueの勉強の仕方

公式サイト:https://jp.vuejs.org/index.html

スクリーンショット 2020-02-28 22.28.55.png
勉強を始める前になぜ Vue.jsの動画を見ると感覚的に理解できる

有名な本

スクリーンショット 2020-02-28 22.28.14.png
amazon:https://www.amazon.co.jp/s?k=vue&__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&ref=nb_sb_noss_1

オススメのサイト:https://www.vuemastery.com/

スクリーンショット 2020-02-28 22.33.03.png
英語ではあるがイントロコースを終了する頃にはある程度理解できているはずである
https://www.vuemastery.com/courses/intro-to-vue-js/vue-instance

Vueを支えるシステム

コンポーネントシステム:部分ごとに切り分けてコードを作成することで使い回しが可能(ヘッダーとかサイドバーなど)
           リアクティブシステム:状態の変化を自動的に画面に反映する仕組み
レンタリングシステム:仮想DOMによる高速レンタリング
スクリーンショット 2020-02-28 22.34.54.png
この辺はおいおい理解すれば良い

基本のコード(オススメ作業場所:https://jsfiddle.net/)

hello.html
<!DOCTYPE html>
<title>始めてのVue.js</title>
<script src="https://unpkg.com/vue@2.5.17"></script>

<div id="app" ></div>

<script>
    new Vue({
        template: '<p>{{msg}}</p>',
        data: {msg: 'hello world'}
    }).$mount('#app')
</script>

<style>
p{background-color: red;}
</style>

vue.ファイル

app.vue
<template>
  <p class="message">メッセージ:{{ msg }}</p>
</template>

<script>
    export default{
        data(){
            return{msg: 'こんにちわ'}
        }
    }
</script>

<style>
    .message{background-color: red;}
</style>

vue端子を出力したい時は@vue/cli @vue/cli-service-globalが必要になる
$を除いたコマンドを順番にうてばおそらく使えるはず
$ npm install -g @vue/cli @vue/cli-service-global
$ vue–version
$ vueserve –o

用語について

フレームワークという単語自体は英語で「枠組み・構造」を意味しています。

つまり、JavaScriptでWebサービスやアプリを開発するうえで、
サーバーとの連携、ルーティング、データの送受信(CRUD)など、全体的な処理の流れを構造化するわけです。
そのため、フレームワークを使えば足りない部分だけを集中して開発すれば良いので、
とても効率が良いうえ複数人が一緒に作業しやすくなるメリットもあります。

SPAとは単一のページでコンテンツの切り替えを行うWebアプリケーションのことです。

Vue.jsはSPA(Single Page Application)開発に使われていることが非常に多いです。
SPAで開発すると、ページ遷移をする時はJavaScriptを使用してHTMLの一部を差し替えて切り替えます。
従来のWebページではページ遷移の時は全体を切り替える必要があったため、
SPAによって新しいUIの実現やスピードの向上が可能となりました。
パフォーマンスの良さから今は多くの企業でWebサイト・アプリケーションにSPAを採用しており、
SPA開発に使われるVue.jsの人気も高まっています。
DeNAやGMOペパボなど、大手IT企業の開発現場でもVue.jsは採用されています。

DOMとは「Document Object Model」の略

直訳すると、「ドキュメントを物として扱うモデル」になる。
プログラムからHTMLやXMLを自由に操作するための仕組みだ。
例えばブラウザに表示される文字の色を変更したり、大きくしたりと、Webページの見た目をプログラムで処理をしたい場合があるだろう、
しかし何もしていない状態のHTMLファイルではJavaScriptから手を出す事が出来ない。
そこでファイルの特定の部分に目印を付けて「この部分」に「こういう事をしたい」という処理を可能にするための
取り決めがDOMである

公開した目的

エンジニア経験6ヶ月で研修を任せてもらえたので、資料を作成した。
どうせ作った資料だからいろんな人に見てもらいたい
後、スーパーエンジニアにアドバイスもらえたらなんて、、、

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

【Nuxt.js】Vuex基礎編②stateを複数使ってみよう

前置き

今回は前回の基礎編に続き、
stateが複数ある場合の書き方です✍️
基礎編でVuexの基本的な解説はしています!
また基礎編のコードに追記するので
そちらを確認しながらやってみてください?

Vuex基礎編はこちら
https://note.com/aliz/n/n497914c981c8#i8l88

やりたいこと

coun 3.gif

基礎編のカウンターを2つに増やします!
これだけ!!!

NGパターン

 count.gif

まずはNGパターンから。
まずはstateにsubCounterを追加。
mutationsなどにも同様に
subCounterについて追記します✍️

が!
これだと後述したsubCounterに
全てがまとまってしまいます。。。

counter.js
export const state = () => ({
  counter: 0,
  subCounter: 0,
})

export const mutations = {
 setIncrease(state) {
   state.counter++
 },
 setDecrease(state) {
   state.counter--
 },
 setIncrease(state) {
   state.subCounter++
 },
 setDecrease(state) {
   state.subCounter--
 },
}

export const getters = {
 counter: state => {
   return state.counter
 },
 subCounter: state => {
   return state.subCounter
 }
}
Counter.vue
<template>
 <div>
  <div class="container">
    <button @click="$store.commit('counter/setIncrease')">Increase</button>
    <button @click="$store.commit('counter/setDecrease')">Decrease</button>
    {{ counter }}
  </div>
  <div class="container">
    <button @click="$store.commit('counter/setIncrease')">Increase</button>
    <button @click="$store.commit('counter/setDecrease')">Decrease</button>
    {{ subCounter }}
  </div>
 </div>
</template>

<script>
export default {
 computed: {
  counter () {
    return this.$store.getters['counter/counter']
  },
  subCounter () {
    return this.$store.getters['counter/subCounter']
  },
 },
}
</script>
index.vue
<template>
 <div class="container">
  <Counter />
 </div>
</template>

<script>
import Counter from '~/components/Counter.vue';

export default {
 components: {
  Counter,
 },
}
</script>

OKパターン

coun 3.gif

ということで
mutationsを一つに統一しましょう⭕️
第二引数indexを使って
・0の場合はcounterを
・1の場合はsubCounterを
変動させます。

こんかいはその2パターンで良いので
if文のelse ifまであればOKですね??

if文
if (条件式1) {
 条件式1がtrueの時の処理
} else if (条件式2) {
 条件式1がtrueの時の処理
} else {
  上記以外の全ての時の処理
}

追加の引数の書き方は公式参照です✍️
https://vuex.vuejs.org/ja/guide/mutations.html#追加の引数を渡してコミットする

?変更箇所のみ記載します?

counter.js
export const mutations = {
 setIncrease(state, index) {
   if (index === 0) {
     state.counter++
   } else if (index === 1) {
     state.subCounter++
   }
 },
 setDecrease(state, index) {
   if (index === 0) {
     state.counter--
   } else if (index === 1) {
     state.subCounter--
   }
 },
}

Counter.vue
ここで第二引数に0, 1を入れて
counterとsubCounterの区別をしています?

Counter.vue
<template>
 <div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 0)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button>
     {{ counter }}
   </div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 1)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button>
     {{ subCounter }}
   </div>
 </div>
</template>

これで完成です??

完成コード

counter.js
export const state = () => ({
 counter: 0,
 subCounter: 0,
})

export const mutations = {
 setIncrease(state, index) {
   if (index === 0) {
     state.counter++
   } else if (index === 1) {
     state.subCounter++
   }
 },
 setDecrease(state, index) {
   if (index === 0) {
     state.counter--
   } else if (index === 1) {
     state.subCounter--
   }
 },
}

export const getters = {
 counter(state) {
   return state.counter
 },
 subCounter(state) {
   return state.subCounter
 }
}
Counter.vue
<template>
 <div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 0)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button>
     {{ counter }}
   </div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 1)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button>
     {{ subCounter }}
   </div>
 </div>
</template>

<script>
export default {
 computed: {
   counter () {
     return this.$store.getters['counter/counter']
   },
   subCounter () {
     return this.$store.getters['counter/subCounter']
   },
 },
}
</script>
index.vue
<template>
 <div class="container">
   <Counter />
 </div>
</template>

<script>
import Counter from '~/components/Counter.vue';

export default {
 components: {
   Counter,
 },
}
</script>

記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?

https://twitter.com/aLizlab

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

【Nuxt.js】Vuex基礎編②stateを複数使ってみよう

前置き

今回は前回の基礎編に続き、
stateが複数ある場合の書き方です✍️
基礎編でVuexの基本的な解説はしています!
また基礎編のコードに追記するので
そちらを確認しながらやってみてください?

Vuex基礎編はこちら
https://note.com/aliz/n/n497914c981c8#i8l88

やりたいこと

coun 3.gif

基礎編のカウンターを2つに増やします!
これだけ!!!

NGパターン

 count.gif

まずはNGパターンから。
まずはstateにsubCounterを追加。
mutationsなどにも同様に
subCounterについて追記します✍️

が!
これだと後述したsubCounterに
全てがまとまってしまいます。。。

counter.js
export const state = () => ({
  counter: 0,
  subCounter: 0,
})

export const mutations = {
 setIncrease(state) {
   state.counter++
 },
 setDecrease(state) {
   state.counter--
 },
 setIncrease(state) {
   state.subCounter++
 },
 setDecrease(state) {
   state.subCounter--
 },
}

export const getters = {
 counter: state => {
   return state.counter
 },
 subCounter: state => {
   return state.subCounter
 }
}
Counter.vue
<template>
 <div>
  <div class="container">
    <button @click="$store.commit('counter/setIncrease')">Increase</button>
    <button @click="$store.commit('counter/setDecrease')">Decrease</button>
    {{ counter }}
  </div>
  <div class="container">
    <button @click="$store.commit('counter/setIncrease')">Increase</button>
    <button @click="$store.commit('counter/setDecrease')">Decrease</button>
    {{ subCounter }}
  </div>
 </div>
</template>

<script>
export default {
 computed: {
  counter () {
    return this.$store.getters['counter/counter']
  },
  subCounter () {
    return this.$store.getters['counter/subCounter']
  },
 },
}
</script>
index.vue
<template>
 <div class="container">
  <Counter />
 </div>
</template>

<script>
import Counter from '~/components/Counter.vue';

export default {
 components: {
  Counter,
 },
}
</script>

OKパターン

coun 3.gif

ということで
mutationsを一つに統一しましょう⭕️
第二引数indexを使って
・0の場合はcounterを
・1の場合はsubCounterを
変動させます。

こんかいはその2パターンで良いので
if文のelse ifまであればOKですね??

if文
if (条件式1) {
 条件式1がtrueの時の処理
} else if (条件式2) {
 条件式1がtrueの時の処理
} else {
  上記以外の全ての時の処理
}

追加の引数の書き方は公式参照です✍️
https://vuex.vuejs.org/ja/guide/mutations.html#追加の引数を渡してコミットする

?変更箇所のみ記載します?

counter.js
export const mutations = {
 setIncrease(state, index) {
   if (index === 0) {
     state.counter++
   } else if (index === 1) {
     state.subCounter++
   }
 },
 setDecrease(state, index) {
   if (index === 0) {
     state.counter--
   } else if (index === 1) {
     state.subCounter--
   }
 },
}

Counter.vue
ここで第二引数に0, 1を入れて
counterとsubCounterの区別をしています?

Counter.vue
<template>
 <div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 0)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button>
     {{ counter }}
   </div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 1)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button>
     {{ subCounter }}
   </div>
 </div>
</template>

これで完成です??

完成コード

counter.js
export const state = () => ({
 counter: 0,
 subCounter: 0,
})

export const mutations = {
 setIncrease(state, index) {
   if (index === 0) {
     state.counter++
   } else if (index === 1) {
     state.subCounter++
   }
 },
 setDecrease(state, index) {
   if (index === 0) {
     state.counter--
   } else if (index === 1) {
     state.subCounter--
   }
 },
}

export const getters = {
 counter(state) {
   return state.counter
 },
 subCounter(state) {
   return state.subCounter
 }
}
Counter.vue
<template>
 <div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 0)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 0)">Decrease</button>
     {{ counter }}
   </div>
   <div class="container">
     <button @click="$store.commit('counter/setIncrease', 1)">Increase</button>
     <button @click="$store.commit('counter/setDecrease', 1)">Decrease</button>
     {{ subCounter }}
   </div>
 </div>
</template>

<script>
export default {
 computed: {
   counter () {
     return this.$store.getters['counter/counter']
   },
   subCounter () {
     return this.$store.getters['counter/subCounter']
   },
 },
}
</script>
index.vue
<template>
 <div class="container">
   <Counter />
 </div>
</template>

<script>
import Counter from '~/components/Counter.vue';

export default {
 components: {
   Counter,
 },
}
</script>

記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?

https://twitter.com/aLizlab

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

Vue.jsを使用して書いたChrome extensionにおけるi18n対応

Chrome extensionの開発でVue.jsを使用したときの国際化対応方法についてのメモ

基本的なi18nの対応方法

vue-plugin-webextension-i18nをインストールする

% npm install --save-dev vue-plugin-webextension-i18n

Vueのインスタンスにプラグインを追加する

import i18n from 'vue-plugin-webextension-i18n'
Vue.use(i18n);

manifest.jsonに以下を記述する

...
default_locale: "en",
...

リソースファイルを配置する

manifest.jsonと同じ階層に_localesというフォルダを作り、さらにその下にlocaleごとのフォルダを配置する。

% tree -L 1
.
├── _locales
├── assets
├── common
├── config
├── ...
└── manifest.json
% tree ./_locales/ 
./_locales/
├── en
│   └── messages.json
└── ja
    └── messages.json

リソースファイル(messages.json)に文言を追加する

  • en/messages.json
{
  "save": {
    "message": "Save"
  }
}
  • ja/messages.json
{
  "save": {
    "message": "保存"
  }
}

Vueコンポーネント側からリソースファイルを使用する

以下のように記述することで、messages.jsonに足した文言にアクセスできる。

<button>{{ $i18n('save') }}</button>

Tips

Formatsの機能を使う

  • 言語によって語順が異なり、文言の中に変数を埋め込みたい場合は、以下のように記述できる。

messages.json

...
},
"showItems": {
    "message": "Show $NUMBER$ items",
    "placeholders": {
        "number": {
            "content": "$1",
            "example": 2
        }
    }
},
...

Vue component

{{ $i18n('showItems', [itemLength]) }}

※第2引数は配列になっており、[A, B, C]と記述したらmessages.json側では \$1, \$2, $3に渡る

参考

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

Lambda+API Gateway+CloudFrontとVueでOGP画像の自動生成をする

Lambda+API Gateway+CloudFrontとVueを使ってフロントエンドのみでOGP画像の自動生成をしてみたので備忘録。

構成

まずVueでSVGを返すページを用意しておく。
Lambda側はchrome-aws-lambdaでスクリーンショットを撮って、base64で返すようにする。

よくあるLambda@Edgeを使ったダイナミックレンダリングを行いつつ、Edgeで返すMetaタグのog:imagetwitter:imageのURLへのアクセスがあったら、用意しておいたSVGページをLambdaでスクリーンショット撮ってAPI Gateway経由でpngにして返す、というちょっと面倒くさい構成。

バックエンド側でLambdaを起動させてスクリーンショット撮ってS3に保存とかでもよかったのだけど、今回はあくまでもアクセスがあったらOGP画像を返すようにしたかったので、こんな感じの構成にした。

VueでSVG生成

VueでSVGを生成するのはこちらの記事を参考にさせていただいた。
Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう

<template>
  <div class="hello">
    <svg ref="svgCard">
      <text transform="translate(103.29 347.281)" fill="#e51f4e" font-size="29" font-family="HiraginoSans-W5, Hiragino Sans" letter-spacing="-0.002em">
        <tspan x="0" y="26">{{ data.content }}</tspan>
      </text>
    </svg>
  </div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  name: 'Svg',
  data () {
    return {
      data: {}
    }
  },
  beforeMount () {
      this.fetchData(response => {
        this.data = response
      })
  },
  methods: {
    ...mapActions([
        'fetchData',
    ])
  },
}
</script>

svgタグの中にvueのデータを埋め込めるので、APIから持ってきたデータを表示できるようにする。
注意が必要なのが、svgタグではテキストを自動で折り返してくれないので、途中で切って配列にしてv-forで回すとかしないといけない。

あとはこのページをrouterに登録する。

import Svg from '@/views/Svg.vue'

Vue.use(VueRouter)

const routes = [
  ...
  {
    path: '/path/to/svg',
    name: 'Svg',
    component: Svg
  }
]

Lambda+API Gateway

次にスクリーンショットを撮るLambdaを作る。ほんとはserverless frameworkで作りたかったのだけど、serverlessで作ると何故かchromeが動いてくれなかったのと、API Gateway側の設定がイマイチ把握しきれなかったので、今回はコンソールでポチポチした。

Lambda
const chromeLambda = require("chrome-aws-lambda");

const defaultViewport = {
  width: 1200,
  height: 630
};

exports.handler = async event => {

  const browser = await chromeLambda.puppeteer.launch({
    args: chromeLambda.args,
    executablePath: await chromeLambda.executablePath,
    defaultViewport 
  });

  const sleep = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve();
      }, time);
    });
  }

  const page = await browser.newPage();

  const url = "https://" + process.env.DOMAIN + "/path/to/svg";
  await page.goto(url, { waitUntil: "networkidle0" });
  sleep(1000)

  const buffer = await page.screenshot({ encoding: "base64", type: "png" });

  return {
    "statusCode": 200,
    "headers": {"Content-Type": "image/png"},
    "isBase64Encoded": true,
    "body": buffer
  };

};

Lambda側のソースコードはこんな感じ。chrome-aws-lambdaはLambda Layerを使わせてもらった。
https://github.com/shelfio/chrome-aws-lambda-layer#available-regions

API Gateway

API Gateway側は適当なリソースを作って、GETメソッドを用意する。

  • /path/to/svg - GET - 統合リクエストで上で作ったLambdaに繋いで、Lambda プロキシ統合の使用にチェックを入れる
  • HTTP リクエストヘッダーにAcceptを追加する
  • メソッドレスポンスのコンテンツタイプにimage/pngを追加する
  • APIの設定で、バイナリメディアタイプにimage/pngを追加する

APIキーや使用量プランは必要に応じて設定して、ステージにデプロイする。ここでは仮にprodステージにデプロイしたと仮定して進める。

これでcurlコマンドでAPI Gatewayを叩くと画像が返ってくるようになる。

curl -H "Accept: image/png" --output test.png https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/path/to/svg
CloudFront

この状態でブラウザでアクセスすると、Acceptヘッダーがリクエストに含まれないのでjsonが返ってきてしまう。CloudFrontを経由させることで、Acceptヘッダーを付けつつ、一度アクセスのあった画像はキャッシュしてもらえる。

まずDistributionsの作成してOriginを追加する。

Origin Domain Name: xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com
Origin Path: /prod
Origin Custom Headers: Accept: image/png

Behavior側はOrigin or Origin Groupで先ほど追加したOriginを選択する。

CloudFrontのデプロイが完了したら、CloudFrontのURL経由でブラウザで画像が表示されるようになる。

Lambda@EdgeでOGPタグ

Lambda@Edgeを使ったOGPの生成はこのあたりを参考に。
Lambda@EdgeでSPAのOGPを動的に設定する
SSRをやめる。OGP対応はLambda@Edgeでダイナミックレンダリングする。

今回は1個目の記事のような感じで、botからのアクセスだった場合はバックエンドのAPIにアクセスしてタイトルとかを整えつつ、上で用意した画像のURLを含んだOGPタグを生成して返すようにした。

botの種類はこのあたり。

const crawlers = [
  "Googlebot",
  "facebookexternalhit",
  "Twitterbot",
  "bingbot",
  "msnbot",
  "Slackbot",
  "Discordbot"
];

まとめ

とまあ、こんな具合でOGP用の画像を自動生成できた。あとは必要に応じてコールドスタート対策もしておきたいところ。
Serverless Frameworkで行うLambdaのコールドスタート対策

いつもながら先人の知恵や知識に感謝。

参考URL

https://scottbartell.com/2019/03/25/automating-og-images-with-aws-lambda/
https://codissimo.sinumo.tech/2019/12/27/serverless-puppeteer-with-aws-lambda-layers-and-node-js/
https://qiita.com/junara/items/5563ad7ee133ce736ed0
https://qiita.com/kodai-saito/items/9051d2b30a29c7d64f7d

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

Vue.js入門2(私的メモ)

Vue.jsの環境構築

「開発マシンを汚さないから」って理由だけで、ロクに使ったこともないDockerでVue.jsの環境構築をしてみた。
結果、めちゃくちゃ時間かかった。参考サイト通りにやったのに。
CDNで手っ取り早く勉強し始めた方が良かったと後悔。
とはいえ、手を出してしまったからにはDockerの勉強もしたいなー。
超初心者にオススメの教材(Webサイトでも書籍でも動画でも)あったら教えていただけると幸いです。

準備

mkdir フォルダ・ファイルを置くディレクトリ名
cd 上のディレクトリ名

Dockerfileの作成

ベースイメージを指定して、npmコマンドを使えるようにする。

FROM node:10.13-alpine

WORKDIR /app

RUN apk update && \
    npm install && \
    npm install -g npm && \
    npm install -g @vue/cli

CMD ["/bin/ash"]

docker-compose.ymlの作成

ラクチンに複数のコンテナを起動できるよう設定しておく。

version: '3'

services:
      web:
        container_name: test_vue_app
        build: .
        ports:
          - 8080:8080
        volumes:
          - .:/app
        tty: true
        stdin_open: true

サービスをビルド(構築)

上記二つのファイルが存在するディレクトリで。

docker-compose build

コンテナ起動

-dはデタッチモードといって、バックグラウンドでコンテナを実行し続けてくれる。
ターミナルで作業を続けたい場合はつける。

docker-compose up -d

コンテナに入ってプロジェクトを作成する

node, vueが入ったサービス(web)でシェルを起動させる。

docker-compose exec web /bin/ash
/app # 
/app # ls
Dockerfile          docker-compose.yml

プロジェクトを作成する。

/app # vue create プロジェクト名

プロジェクト内に移動して、サーバを起動

プロジェクト内に移動してから。

/app # cd プロジェクト名
/app/プロジェクト名 # npm run serve

確認

指定したポート番号でページにアクセス。
例)http://localhost:8080/

スクリーンショット 2020-02-28 16.00.45.png

ホストのポート番号 : コンテナのポート番号
なので、例えばホスト側のポート番号を7070に指定すれば、http://localhost:7070/ でアクセスできる。

version: '3'

services:
      web:
        container_name: test_vue_app
        build: .
        ports:
          - 7070:8080
        volumes:
          - .:/app
        tty: true
        stdin_open: true

ポート番号を変更する

試してみたが、アクセスできずに断念。
何故や。

参考文献

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

SublimeTextをVue.jsに対応させる手順

はじめに

SublimeTextでVue.jsを扱う際に拡張子(.vue)ファイルをシンタックスハイライトしてくれるプラグイン導入手順です

環境

  • OS:macOS Catalina v10.15.1
  • SublimeText:V3.2.2

前提条件

パッケージコントローラをインストールしていること

※インストールがまだの場合は以下のページを参照
SublimeTextの日本語化手順

Gitリポジトリの追加

コマンドパレットを開く
<ショートカットキー>
 Win/Linux : ctrl+shift+p
 Mac : cmd+shift+p
<メニューから選択>
 メニューバー > Tools > Command Paletteを選択

「Package Control: Add Repository」を入力、選択してEnter
sublimeText#vue1.png

画面下部のURLに
https://github.com/vuejs/vue-syntax-highlight.git」
を入力してEnter
sublimeText#vue2.png

プラグインのインストール

コマンドパレットを開いて「Package Control: Install Package」を入力、選択してEnter
sublimeText#vue3.png

「Vue Syntax Highlight」を入力、選択してEnter
※候補が2つ出てきましたがキャメルケースの名前の方を選択しました
sublimeText#vue4.png

以上でプラグインの導入は完了です

あとはSublimeTextを再起動して拡張子「.vue」ファイルを開くとしっかりとシンタックスハイライトしてくれています
sublimeText#vue5.png

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

Vue.jsで外部HTMLファイルをinclude(っぽく)する

概要

Componentに分けたいけど分けられない。諸事情でHTMLファイルをincludeしたい。
HTMLファイルを直接弄りたいとか言われましても

方法

  • 外部HTMLファイルを非同期で読み込んで展開する

具体例

<template>
  <main>
    <section v-html="external"></section>
  </main>
</template>

<script lang="ts">
import { Vue, Component } from "vue-property-decorator";

@Component
export default class App extends Vue {
  external: string = "";

  public mounted() {
    this.loadExternalHtml();
  }

  public loadExternalHtml() {
    fetch("external.html").then(res => {
      res.text().then(html => {
        this.external = html;
      });
    });
  }
}

includeっぽくはなる。
これでガリガリHTMLを弄ってもおk

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

Vue.jsを使って簡単な計算ツールを作った

はじめに

グランブルファンタジーというゲーム内のイベントである古戦場にて、目的の貢献度を貯めるためにはどれくらいのトリガーが必要か?を計算するツールを作りました。
作り自体は簡単なため、初めてVue.jsを勉強&アウトプットするにはいい題材でした。

実際に動かしたもの
http://ayama.main.jp/battlefieldRun.html

コード
https://github.com/guranytou/battlefieldRuns

一番困った部分

battlefieldRun.html
        <fieldset class="inputFieldset">
          <div id="app">
            <div>
              <span>目標累計貢献度</span>
              <input type="text" v-model="input" id="contributionInput"></input>
            </div>
            <div id="trigerDigest">
              <div><span>肉の消化方法</span></div>
              <div>
                <input type="radio" class="hellRadio" name="hellRadio" id="hell95" value="Hell95" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="hell95">95HELL</label>
                <input type="radio" class="hellRadio" name="hellRadio" id="hell100" value="Hell100" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="hell100">100HELL</label>
                <input type="radio" class="hellRadio" name="hellRadio" id="hell150" value="Hell150" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="hell150">150HELL</label>
                <input type="radio" class="hellRadio" name="hellRadio" id="both" value="both" v-model="trigerDigest"/>
                <label class="hellRadioLabel" for="both">95&100HELL</label>
              </div>
            <div>
              <span>必要なトリガー数:</span><span>{{ output }}</span>
            </div>
          </div>
        </fieldset>
battlefieldRun.js
(function () {
   var triger = new Vue({
     el: '#app',
     data: {
       input: 0,
       trigerDigest: 'false'
     },
     computed: {
       output: function(){
           const Hell95NeedTriger = 10;
           const Hell100NeedTriger = 20;
           const Hell150NeedTriger = 20;
           const ExTriger = 4;
           const Hell95Contribution = 51500 * (Hell95NeedTriger / ExTriger) + 910000;
           const Hell100Contribution = 51500 * (Hell100NeedTriger / ExTriger) + 2680000;
           const Hell150Contribution = 51500 * (Hell150NeedTriger / ExTriger) + 3600000;
           let ans = 0;

           const ThisInput = String(this.input);
           const InputContribution = removeComma(ThisInput);

           function removeComma(value) {
             var num = value.replace(/,/g, "");

             return parseInt(num);
           }

           if(this.trigerDigest === 'Hell95'){
            // 計算式
            ans = Math.floor(InputContribution / Hell95Contribution * Hell95NeedTriger);
            return ans.toLocaleString()
           }else if(this.trigerDigest === 'Hell100'){
            // 計算式
             ans = Math.floor(InputContribution / Hell100Contribution * Hell100NeedTriger);
             return ans.toLocaleString()
           }else if(this.trigerDigest === 'Hell150'){
             ans = Math.floor(InputContribution / Hell150Contribution * Hell150NeedTriger);
             return ans.toLocaleString()
           }else if(this.trigerDigest === 'both'){
            // 計算式
             ans = Math.floor((InputContribution / 2 / Hell95Contribution * Hell95NeedTriger) +  (InputContribution / 2 / Hell100Contribution * Hell100NeedTriger));
             return ans.toLocaleString()
           }else{
             return 0;
            }
         }
     }
   })
}());

振り返り

・カンマをinput/outputにいれるのに地味に困った
→ここは本題ではなかったので、今回はinputに外部のJQueryを利用、outputにはtoLocaleString()を利用した
・computedとmethodsのどちらを利用すればいいのか分からなくて困った
→入れたらすぐに発火するものが作りたかったのでcomputedを採用。またcomputedとmethodsの違いの勉強が出来てよかった
・必要な部分だけさらっと勉強して書き始めたので、まだまだVue.jsへの理解が足りないように感じた
→座学もちゃんと増やす。簡単な題材のアウトプット(FizzBuzz、ToDoリスト作成etc)をする。

・疑問点:Computed内でif文の分岐をしている例文が見当たらなかったが、他の書き方があったりするのだろうか

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

【JavaScript】ElementUIのFormComponentを使用したフォームでリクエスト送信時、二重送信されないように画面をロックさせる

フォーム送信時はユーザーに操作させたくない

フォームの送信ボタンを押したときは二重にリクエストが飛ばないようにボタンを押せなくしたり、そもそも画面の項目に触れてほしくないので画面をロック状態にするようにしたい

以下は過去行ってた二重送信対応策(jQuery)
【JavaScript】フォーム等の送信ボタンに対する二重送信防止対応策 - Qiita

ElementUIのコンポーネントたちを使用して実現させる

今回はElementUI(Vue.jsのコンポーネントライブラリ)のFormコンポーネントloadingコンポーネントを組み合わせて、フォームのリクエスト送信時に画面をロックさせる

サンプル

See the Pen loading form deactivate By ElementUI by sola-msr (@sola-msr) on CodePen.

対応部分

サンプルではv-loading.fullscreen.lockをボタン部分に追加し、送信時にローディング描写をON(true)、送信完了(正常系レスポンス受け取り時)にOFF(false)をセットしています。
上記の対応だけではまだ送信ボタンを押下することが出来る状態ですので、追加の対応としてフォーム全体を非活性化(disabledさせるように、el-form部分にdisabledを追加し、これも送信時と送信完了時にON/OFFさせるようにしています。

<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="120px" class="demo-ruleForm" :disabled="this.disabledForm">
  <el-form-item label="Name" prop="name">
    <el-input type="text" v-model="ruleForm.name" autocomplete="off"></el-input>
  </el-form-item>
  <el-form-item label="Password" prop="pass">
    <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
  </el-form-item>
  <el-form-item label="Confirm" prop="checkPass">
    <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
  </el-form-item>
    <el-form-item label="Activity zone" prop="region">
    <el-select v-model="ruleForm.region" placeholder="Activity zone">
      <el-option label="Zone one" value="shanghai"></el-option>
      <el-option label="Zone two" value="beijing"></el-option>
    </el-select>
  </el-form-item>

  <el-form-item>
    <el-button type="primary" @click="submitForm('ruleForm')" v-loading.fullscreen.lock="fullscreenLoading">Submit</el-button>
    <el-button @click="resetForm('ruleForm')">Reset</el-button>
  </el-form-item>
</el-form>

methodsで変数fullscreenLoadingdisabledFormtrue/falseをセットする関数(openFullScreen()closeFullScreen())を定義し、submitForm関数の中で各々のタイミングで呼び出すようにしてます。

// 略
    methods: {
      openFullScreen() {
         this.fullscreenLoading = true;
         this.disabledForm = true;
      },
      closeFullScreen() {
         this.fullscreenLoading = false;
         this.disabledForm = false;
      },
      submitForm(formName) {        
        this.$refs[formName].validate((valid) => {
          if (valid) {

            // ローディング描写とフォーム非活性化
            this.openFullScreen();

            // イメージしやすいようにあえて3秒間ローディング描写させるようにしてます
            setTimeout(() => {
              alert('submit!');
              // ローディング描写とフォーム非活性化をそれぞれ解除
              this.closeFullScreen();
            }, 3000);

          } else {
            // ここでもcloseFullScreen()を実行したほうがいいかもしれません
            console.log('error submit!!');
            return false;
          }
        });
      },
// 略

おわり

  • ローディング描写中にさらにプログレスバーを表示させる方法を知りたい(切実)

参考URL

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

Vuex開発メモ

Vuexを勉強して、何となく理解できた気がするのでここらでまとめておきます。

Vuexとは?

Vuexは、Vue.jsの状態管理用ライブラリです。
Vue.jsには、propsという、親テンプレートから子テンプレートへ値を渡すことができる機能が備わっていますが、孫要素やそれよりもさらに下の要素が増えていくと、propsを連鎖させることになってしまい、開発効率が落ちてしまったり管理が大変になるというデメリットがあります。

それを解決してくれるのがVuexです。Vuexをプロジェクトに導入することで、それぞれのコンポーネントは同じ値をVuexのストア内で共有することになるので、必要な時にストアから値を取得するだけでその値を使うことができるようになります。

一般的には中規模以上から使われ始めるようですが、ある程度プロジェクトができあがってから導入するのは移行が面倒なので、規模が大きくなる見込みがあるのなら使った方がよさそうです。

Vuexのデータフローについて

Vuexは、大きく分けて3つの要素から成り立っています。Actions、Mutations、Stateです。
Actionsが外部APIとの通信、MutationsがStateの書き換え、Stateがプロジェクトの状態管理を担っています。

Vuexでは、下の図のように、コンポーネントがActionsに問い合わせる、ActionsがMutationsに問い合わせる、MutationsがStateを書き換える、Stateの値をコンポーネントに送るといった、一方向のデータフローを取っています。
Actionsを使用しない場合はコンポーネントからMutationsに直接問い合わせることもできます。
データフロー.png

デモアプリ作成

ここからは、実際にカウンターアプリを作りながら説明していきます。

プロジェクトの作成

コンソールを開いて、下のように打ち込んでください。

vue create counter

Please pick a presetと表示されたら、
Manually select featuresを選択
そのあとにCheck the features needed for your projectと表示されたらvuexをえらんでインストールします。
後はデフォルトのままで大丈夫です。

cd vuexDemo
yarn serve

の後、localhost:8080にアクセスできればプロジェクトの作成は成功です。

コンポーネントの作成

今回は、デフォルトでプロジェクトに用意されているHelloWorld.vueをそのまま使います。

一旦HelloWorld.vue内に書かれている必要のないコードをすべて消してしまいましょう。

以下のような状態にします。

src/assets/components/HelloWorld.vue
<template>
  <div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
}
</script>

<style scoped>
</style>

一旦Vuexを使わずに作ってみましょう。
このような感じになると思います。

src/assets/components/HelloWorld.vue
<template>
  <div>
    <p>{{ count }}</p>
    <button type="button" @click="countup">countup</button>
    <button type="button" @click="countdown">countdown</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      count: 0
    };
  },
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

動作確認.gif

countupボタンを押したら1足されて、countdownボタンを押したら1引かれるという機能を作りました。

以降はVuexを使って同じ機能を作っていきます。

stateの作成

まずは状態を管理するStateを作っていきましょう。
既にstore/index.js内に一部書かれているので、そのまま使います。
デフォルトではこのようになっていると思います。

src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

stateに値を追加しましょう。

src/store/index.js
import Vue from 'vue'
   //省略
  state: {
    count: 0
  },
   //省略

これをHelloWorld.vueから読み込みます。
stateの値を読み込むには、

$store.state.値

で読み込むことができます。

src/components/HelloWorld.vue
<template>
  <div>
    <p>{{ $store.state.count }}</p>   <!--変更-->
    <button type="button" @click="countup">countup</button>
    <button type="button" @click="countdown">countdown</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
//------------------必要なくなるので削除する-------------
  data() {
    return {
      count: 0
    };
  },
//-----------------------ここまで削除-------------------
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

では、実際の表示を見てみましょう。
スクリーンショット.png
ボタンを押しても数字は変わりませんが、storeに記述したcount:0が反映されていると思います。

mutationの作成

ここからは、mutationを作って、stateの変更ができるようにします。

src/store/index.js
//省略
  mutations: {
    countup: function (state) {
      state.count++;
    },
    countdown: function (state) {
      state.count--;
    }
  },
//省略

stateの変更をする場合は、関数の第一引数にstateを渡すのを忘れないようにしてください。
state.値で管理されている値を取得できます。

それでは、mutationで作った関数をコンポーネントから呼び出していきましょう。

$store.commit('関数名')

で呼び出すことができます。

src/components/HelloWorld.vue
<template>
  <div>
    <p>{{ $store.state.count }}</p>
    <button type="button" @click="$store.commit('countup')">countup</button>       <!--変更-->
    <button type="button" @click="$store.commit('countdown')">countdown</button>   <!--変更-->
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
//-------------------必要なくなるので削除する-----------------------
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
//----------------------ここまで削除-------------------------------
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

確認してみましょう。
動作確認.gif
stateが書き換わって、表示されている数字が増えたり減ったりしていることがわかります。

余談ですが、関数の第二引数(payload)に値を渡してあげることで、stateを任意の値に変更することもできます。

actionsの作成

外部APIとの通信をしないのであればactionsを作る必要はありませんが、mutationsの関数の呼び出し方のみ説明しておきます。
actionsの機能についてもっと知りたい方はこちらをご覧ください。

actionsにmutationsの関数を呼び出すための関数を書いていきます。

src/store/index.js
//省略
  actions: {
    countupAction(context) {
      context.commit('countup')
    },
    countdownAction(context) {
      context.commit('countdown')
    }
  },
//省略

これでmutationsのcountupとcountdownを呼び出す準備ができました。
このactionsで定義した関数をコンポーネントから呼び出します。

コンポーネントからactionsの関数を呼び出すには、

$store.dispatch('関数名')

で呼び出すことができます。
ボタンクリック時に呼び出せるよう、HelloWorld.vueを書き換えていきます。

src/components/HelloWorld.vue
<template>
  <div>
    <p>{{ $store.state.count }}</p>
    <button type="button" @click="$store.dispatch('countupAction')">countup</button>      <!--変更-->
    <button type="button" @click="$store.dispatch('countdownAction')">countdown</button>  <!--変更-->
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

確認してみましょう。
dsyub-ctrh1.gif
それぞれの関数を呼び出せていることがわかります。

最後に

Vuexは、初めは理解するのが難しいかもしれませんが、わかるとかなり直感的に操作できます。
規模が大きくなると必須の技術になってくるので、ぜひ使ってみてください。

もしミス等ありましたらコメントまたは編集リクエストでお伝えください。

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

Vuexのデータフローを理解して使ってみる

Vuexを勉強して、何となく理解できた気がするのでここらでまとめておきます。

Vuexとは?

Vuexは、Vue.jsの状態管理用ライブラリです。
Vue.jsには、propsという、親テンプレートから子テンプレートへ値を渡すことができる機能が備わっていますが、孫要素やそれよりもさらに下の要素が増えていくと、propsを連鎖させることになってしまい、開発効率が落ちてしまったり管理が大変になるというデメリットがあります。

それを解決してくれるのがVuexです。Vuexをプロジェクトに導入することで、それぞれのコンポーネントは同じ値をVuexのストア内で共有することになるので、必要な時にストアから値を取得するだけでその値を使うことができるようになります。

一般的には中規模以上から使われ始めるようですが、ある程度プロジェクトができあがってから導入するのは移行が面倒なので、規模が大きくなる見込みがあるのなら使った方がよさそうです。

Vuexのデータフローについて

Vuexは、大きく分けて3つの要素から成り立っています。Actions、Mutations、Stateです。
Actionsが外部APIとの通信、MutationsがStateの書き換え、Stateがプロジェクトの状態管理を担っています。

Vuexでは、下の図のように、コンポーネントがActionsに問い合わせる、ActionsがMutationsに問い合わせる、MutationsがStateを書き換える、Stateの値をコンポーネントに送るといった、一方向のデータフローを取っています。
Actionsを使用しない場合はコンポーネントからMutationsに直接問い合わせることもできます。
データフロー.png

デモアプリ作成

ここからは、実際にカウンターアプリを作りながら説明していきます。

プロジェクトの作成

コンソールを開いて、下のように打ち込んでください。

vue create counter

Please pick a presetと表示されたら、
Manually select featuresを選択
そのあとにCheck the features needed for your projectと表示されたらvuexをえらんでインストールします。
後はデフォルトのままで大丈夫です。

cd vuexDemo
yarn serve

の後、localhost:8080にアクセスできればプロジェクトの作成は成功です。

コンポーネントの作成

今回は、デフォルトでプロジェクトに用意されているHelloWorld.vueをそのまま使います。

一旦HelloWorld.vue内に書かれている必要のないコードをすべて消してしまいましょう。

以下のような状態にします。

src/assets/components/HelloWorld.vue
<template>
  <div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
}
</script>

<style scoped>
</style>

一旦Vuexを使わずに作ってみましょう。
このような感じになると思います。

src/assets/components/HelloWorld.vue
<template>
  <div>
    <p>{{ count }}</p>
    <button type="button" @click="countup">countup</button>
    <button type="button" @click="countdown">countdown</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      count: 0
    };
  },
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

動作確認.gif

countupボタンを押したら1足されて、countdownボタンを押したら1引かれるという機能を作りました。

以降はVuexを使って同じ機能を作っていきます。

stateの作成

まずは状態を管理するStateを作っていきましょう。
既にstore/index.js内に一部書かれているので、そのまま使います。
デフォルトではこのようになっていると思います。

src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

stateに値を追加しましょう。

src/store/index.js
import Vue from 'vue'
   //省略
  state: {
    count: 0
  },
   //省略

これをHelloWorld.vueから読み込みます。
stateの値を読み込むには、

$store.state.値

で読み込むことができます。

src/components/HelloWorld.vue
<template>
  <div>
    <p>{{ $store.state.count }}</p>   <!--変更-->
    <button type="button" @click="countup">countup</button>
    <button type="button" @click="countdown">countdown</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
//------------------必要なくなるので削除する-------------
  data() {
    return {
      count: 0
    };
  },
//-----------------------ここまで削除-------------------
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

では、実際の表示を見てみましょう。
スクリーンショット.png
ボタンを押しても数字は変わりませんが、storeに記述したcount:0が反映されていると思います。

mutationの作成

ここからは、mutationを作って、stateの変更ができるようにします。

src/store/index.js
//省略
  mutations: {
    countup: function (state) {
      state.count++;
    },
    countdown: function (state) {
      state.count--;
    }
  },
//省略

stateの変更をする場合は、関数の第一引数にstateを渡すのを忘れないようにしてください。
state.値で管理されている値を取得できます。

それでは、mutationで作った関数をコンポーネントから呼び出していきましょう。

$store.commit('関数名')

で呼び出すことができます。

src/components/HelloWorld.vue
<template>
  <div>
    <p>{{ $store.state.count }}</p>
    <button type="button" @click="$store.commit('countup')">countup</button>       <!--変更-->
    <button type="button" @click="$store.commit('countdown')">countdown</button>   <!--変更-->
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
//-------------------必要なくなるので削除する-----------------------
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
//----------------------ここまで削除-------------------------------
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

確認してみましょう。
動作確認.gif
stateが書き換わって、表示されている数字が増えたり減ったりしていることがわかります。

余談ですが、関数の第二引数(payload)に値を渡してあげることで、stateを任意の値に変更することもできます。

actionsの作成

外部APIとの通信をしないのであればactionsを作る必要はありませんが、mutationsの関数の呼び出し方のみ説明しておきます。
actionsの機能についてもっと知りたい方はこちらをご覧ください。

actionsにmutationsの関数を呼び出すための関数を書いていきます。

src/store/index.js
//省略
  actions: {
    countupAction(context) {
      context.commit('countup')
    },
    countdownAction(context) {
      context.commit('countdown')
    }
  },
//省略

これでmutationsのcountupとcountdownを呼び出す準備ができました。
このactionsで定義した関数をコンポーネントから呼び出します。

コンポーネントからactionsの関数を呼び出すには、

$store.dispatch('関数名')

で呼び出すことができます。
ボタンクリック時に呼び出せるよう、HelloWorld.vueを書き換えていきます。

src/components/HelloWorld.vue
<template>
  <div>
    <p>{{ $store.state.count }}</p>
    <button type="button" @click="$store.dispatch('countupAction')">countup</button>      <!--変更-->
    <button type="button" @click="$store.dispatch('countdownAction')">countdown</button>  <!--変更-->
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  methods: {
    countup() {
      this.count++;
    },
    countdown() {
      this.count--;
    }
  }
};
</script>

<style scoped>
button {
  margin-left: 20px;
}
</style>

確認してみましょう。
dsyub-ctrh1.gif
それぞれの関数を呼び出せていることがわかります。

最後に

Vuexは、初めは理解するのが難しいかもしれませんが、わかるとかなり直感的に操作できます。
規模が大きくなると必須の技術になってくるので、ぜひ使ってみてください。

もしミス等ありましたらコメントまたは編集リクエストでお伝えください。

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

Vue.js入門1(私的メモ)

Vue.jsはプログレッシブなフレームワーク

プログレッシブなフレームワークってどゆこと。
とりあえず、放置。

フロントエンドの変遷

フロントエンドの担うことがめちゃくちゃ増えてる。

時期 フロントエンドの役割 サーバーの役割 JavaScriptライブラリ・フレームワーク
Webシステム初期 装飾 HTMLの生成 なし
Ajax期 Ajaxを中心としてインタラクション HTMLの生成 + API jQuery, prototype.js etc.
現在 アプリケーションのプレゼンテーション全般 API Vue.js, React, Angular etc.

Vue.jsの特徴

JavaScriptフレームワーク入門者は、とりあえずVue.jsを。

  • 学習コストの低さ
  • コンポーネント志向によるUIの構造化
    シズテム全体をコンポーネント(機能を構成する部品)の集合体とみなす
    保守性の向上や再利用性の高さなどがメリット
  • リアクティブなデータバインディング
    対象のDOM要素とデータを結びつける(バインディング)ことで、効率的に内容を更新

Vue.jsの設計思想

Vue.jsの根底には、プログレッシブフレームワークという考え方がある。
プログレッシブフレームワークとは、アプリケーションの段階的(プログレッシブ)な要求変化に応じて問題解決できる方法を提供する、という思想。
最小のコストでアプリケーション開発を進められ、ユーザーの要望に沿った機能拡張などに適宜対応することができる。
つまり、必要な段階で必要なものを利用する柔軟性をVue.jsは持っている。

Vue.jsを支える技術

  • コンポーネントシステム
    HTML, CSS, JavaScriptの三つを組み合わせた一つのコンポーネント(部品)として切り出せる
  • リアクティブシステム
    状態変化をVue.jsが検知(監視)して、自動的にDOM側に反映できるような仕組み
  • レンダリングシステム
    仮想DOMによるDOMの高速レンダリングを提供している

参考文献

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

nuxt generateで静的サイトをつくる時の開発環境について(ファイル監視など)

はじめに

皆さんこんにちは。
Nuxt.js、とてもいいですよね。煩雑な設定が少なくすぐに実装に取りかかれるので、アウトプット速度がすごく向上します。

今回は、nuxt generateコマンドで静的サイトをビルドする際の開発環境構築で感じた課題と、その解決方法について軽めの記事を書きたいと思います。

ツッコミどころや改善案などあればぜひコメントお願いします。

TL;DR

  • 単一ファイルコンポーネント等を使ったVueでの開発をしつつ静的なHTMLを書き出せるnuxt generateはとても便利で使い所も多いが、開発中のファイル監視と自動更新は自前で実装せねばならず、したとしても毎回generateをすることになるので時間がかかる。
  • 上記を解決するために、開発時は静的ファイルを生成せずnuxtコマンドで開発を進め、静的ファイルを生成したい時のみnuxt generateを叩くという開発方法を検討した。
  • その際、画像ファイル等の参照でハマった

nuxt generateについて

nuxt.jsそのものに比べ、こちらのコマンドは比較的新しい機能ということもあり知名度がやや落ちるかと思ったので、概要について触れておきます。

前述の通りnuxt generateは、nuxt.jsで実装した内容を、静的なHTMLとjsファイルとして書き出して、そのまま静的Webサイトとして使用できるようにしたAPIプロパティです。

これを使うメリットとして、クライアントサイドでの描画がなくなるためSEO上安心ということ、パフォーマンスがやや向上することがあげられます。

また個人的には、多言語サイトを構築する際に、SSRを実装しなくてもjsonから文言を取得して、手軽に各言語のHTMLを書き出せる点に大きなメリットを感じています。(nuxt-i18n使用時)

ディレクトリ構造自体は通常のnuxt開発と同じように、以下のような形で組めば、pagesの中身のvueファイルが全てHTMLで吐き出されるイメージです。

> components
> assets
> static
> pages
package.json
nuxt.config.js

nuxt generateの弱点

nuxt generateは、一括でファイルを生成するという機能である以上、変更箇所に応じて部分的にビルドをするような監視機能は備わっていません。

そこで、自前でsrcファイルの監視をして更新をしなければならないのですが、毎回nuxt generateを叩かなければいけないため、ビルドに多くの時間がかかってしまいます。

npm scriptsやその他ビルドツールであればscssが変更されればそこのみ、jsが変更されればそこのみビルドするといった形で細かく監視ができるので、その点においてはnuxt generateの弱点と言えそうです。

実装

解決案

そこで、開発時には通常のnuxtコマンドを使ってホットリロードサクサクナウヤングな開発をしつつ、実際に静的HTMLとしてテストしたい時のみnuxt generateを使えばいいんじゃないか、と思い当たりました。

そこで最初に書いたpackage.jsonは以下のような形です。

"scripts", {
  "dev": "nuxt",
  "build": "run-s mock:**",
  "build:clean": "rimraf ./dist/**",
  "build:gen": "nuxt generate",
  "build:browse": "browser-sync start --config bs-config.js"
}

npm run devコマンドでは単にnuxtコマンドを実行し、
npm run buildコマンドでは一旦distディレクトリを綺麗にしてからnuxt generateし、それが完了したらbrowser-syncでローカルサーバを立ち上げています。

つまづいたところ

当初assetsフォルダとstaticフォルダの違いがよくわかっておらず、画像等は適当にassetsに突っ込んでいました。

しかし、nuxt開発においてassetsファイルの参照は、

<img src="~assets/images/hoge.png">

のように記述しなければならず、そのままnuxt generateをしても参照ができません。
面倒だけどパス置換のスクリプトを挟むか・・・?とも思いましたが、よくよく調べるとstaticフォルダ内の参照は、/staticを省いて以下のようにかけるようです。(直接は関係ありませんがassetsの中身はwebpackでコンパイルするものを突っ込むようです)

<img src="/images/hoge.png">

しかも、nuxt generateでファイルが書き出されるdistフォルダにはしっかりとimagesフォルダができており、どうやらnuxt generateではstaticに全ての参照ファイルを突っ込むのが正解のようです。

余談

これで開発環境は構築できたのですが、よくみると、distフォルダの中のHTMLが想定通りのものになっていません。

//想定したもの
index.html
hoge.html

//書き出されたもの
index.html
hoge
|__index.html

これは、nuxt.config.jsでgenerateプロパティのsubFoldersをfalseにすると解決できました。

nuxt.config.js
export default {
  //省略
  generate: {
    subFolders: false
  },
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue warn]: `createComponent` has been renamed to `defineComponent`.

概要

バージョンをアップグレードしたら Vue warn が出るようになったのでメモ

対応

-import { createComponent } from '@vue/composition-api'
+import { defineComponent } from '@vue/composition-api'

-export default createComponent({
+export default defineComponent({

警告にある通りリネームしたら警告が消えた

まとめ

  • 警告の通りリネームで解決
  • 親切な警告は助かる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET Core + Vue.jsのプロジェクト作成

自分用のメモ代わりにやったことを書いておきます。
ほぼ参考にさせていただいたページのままですが、そちらのほうではMVCプロジェクトで作成していますが、ここではWebAPIプロジェクトで作成するので若干の違いがあります。

環境

  • Visual Studio 2019
  • .NET Core 3.1.1
  • vue cli 4.2.2

プロジェクト作成

ASP.NET Core WebAPIプロジェクトを作成

とりあえず認証等は何もつけずにAPIのみ

Vue.jsプロジェクト作成

作成したプロジェクトのフォルダで以下のコマンドを実行

vue create client-app

default (babel, eslint)を選択

.NET Coreプロジェクトファイルの編集

csprojファイルを以下のように編集

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="client-app\**" />
    <Content Remove="client-app\**" />
    <None Include="client-app\**" Exclude="client-app\node_modules\**" />
  </ItemGroup>

  <Target Name="ExecNpmInstall" BeforeTargets="Build" Condition="'$(Configuration)' == 'Debug' And !Exists('client-app\node_modules')">
    <Exec WorkingDirectory="client-app\" Command="npm install" />
  </Target>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <Exec WorkingDirectory="client-app" Command="npm install" />
    <Exec WorkingDirectory="client-app" Command="npm run build" />

    <ItemGroup>
      <DistFiles Include="client-app\dist\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

.NET Core側のSPA用Configとデバッグ用コードの追加

SPA用のパッケージが入っていないのでnugetで以下のパッケージをインストール

  • Microsoft.AspNetCore.SpaServices.Extensions

ConfigureServicesにSPA用の記述を追加

Startup.cs
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            // 追加
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = @"client-app/dist";
            });
        }

ConfigureにSPA用の記述を追加

Startup.cs
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            // 追加
            app.UseSpaStaticFiles();
            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "client-app";
                if (env.IsDevelopment())
                {
                    spa.UseProxyToSpaDevelopmentServer(async () =>
                    {
                        var pi = new ProcessStartInfo
                        {
                            FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd" : "npm",
                            Arguments = $"{(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/c npm " : "")}run serve",
                            WorkingDirectory = "client-app",
                            RedirectStandardError = true,
                            RedirectStandardInput = true,
                            RedirectStandardOutput = true,
                            UseShellExecute = false,
                        };
                        var p = Process.Start(pi);
                        var lf = app.ApplicationServices.GetService<ILoggerFactory>();
                        var logger = lf.CreateLogger("npm");
                        var tcs = new TaskCompletionSource<int>();
                        _ = Task.Run(() =>
                        {
                            var line = "";
                            while ((line = p.StandardOutput.ReadLine()) != null)
                            {
                                if (line.Contains("DONE  Compiled successfully in "))
                                {
                                    tcs.SetResult(0);
                                }

                                logger.LogInformation(line);
                            }
                        });
                        _ = Task.Run(() =>
                        {
                            var line = "";
                            while ((line = p.StandardError.ReadLine()) != null)
                            {
                                logger.LogError(line);
                            }
                        });
                        await Task.WhenAny(Task.Delay(20000), tcs.Task);
                        return new Uri("http://localhost:8080");
                    });
                }
            });
        }

一応ここまでで動作はしますが、デフォルトだとデバッグ実行した時にWeatherForecastにアクセスするようになっているためプロジェクトのプロパティで以下の画像のブラウザの起動の右にあるテキストボックスを空にします。
image.png

実行

デバッグ実行して問題がなければVue.jsプロジェクトのほうのページが開きます。
image.png

参考

https://blog.okazuki.jp/entry/2019/06/17/132755

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

Vue.jsを触る際に知っておきたい仮想DOMの話

記事の概要

昨今、様々なプロダクトにおいて使われているVue.js。
人気の理由の1つにリアクティブなブラウザの描画があげられます。

ブラウザの再描画を支えている技術が「仮想DOM」なのですが、Vue.jsからフロントエンドを触り始めた自分にとっては「そもそもDOMって何?」状態だったので、「仮想DOMとその背景」についての解説記事を書きました。

技術的な記事ですが、歴の浅いフロントエンドエンジニアがDOM周りについて最低限の知識を身に付けられる事を目的に大枠の理解を優先しましたので、ご了承ください。

そもそもDOMとは

仮想DOMについて知る前に、そもそもDOM(仮想DOMとの区別の為、今後はリアルDOMと呼びます)とはなんなのかを知りましょう。
リアルDOMとは「Document Object Model」の略で、JavaScript側からHTMLを操作する事の出来る仕組みであり、実際にブラウザで描画されているものです。

リアルDOMを操作する事によって、JavaScriptからHTMLを弄れるため、web上にて見た目の変化が可能になります。

リアルDOMの特徴

リアルDOMには以下の特徴があります。

・HTMLを階層構造として扱う(DOMツリー)
・各要素はノードと呼ばれる

イメージとして下記のHTMLの場合

<html>
 <header>
  <h1>タイトル<h1>
 </header>
 <body>
  <div>
   <p>内容</p>
   <button>送信</button>
 </div>
 </body>
</html>

IMG_2429.HEIC.JPG
リアルDOM上ではこの様な階層構造(DOMツリー)として扱います。またこの1つ1つの要素がノードです。
詳しくはこちらを参考にしてください。
https://kuroeveryday.blogspot.com/2018/11/difference-between-dom-and-node-and-element.html

リアルDOMのみの流れ

IMG_6102.JPG

1 HTMLドキュメント
2 リアルDOM(DOMツリー)
3 webページ
の順番で情報は流れていきます。

webページに変化を加えようとした際は、その都度htmlを解析して、DOMツリーを再構築します。
その為、webページへの変化が多いと、再描画まで時間がかかってしまいます。

仮想DOMとは

その名の通り、仮のDOMです。メモリ空間などで擬似的に作ったものです。
今までブラウザで持っていたDOMツリーをJavaScriptのオブジェクトとして扱い、差分のみをリアルDOMに与えてくれる役割を持ちます。

仮想DOMを用いた流れ

IMG_6103.JPG

仮想DOMを用いる事により、情報の順番は
1 HTMLドキュメント
2 仮想DOM
3 リアルDOM
4 webページ

といった形になります。

仮想DOM内部の仕組み

1 DOMツリーを新旧で2つ用意している
2 HTMLドキュメントが書き変わる
3 仮想DOMを再構築し、新旧DOMでの差分を検出する
4 差分のみをリアルDOMに反映する

仮想DOMによる恩恵

描画のスピードです!笑
仮想DOMの存在によって、リアルDOMでは差分のみの読み込みで描画ができる為、ブラウザの再描画のスピードが格段に早くなり、リアクティブな変化にも耐えうるようになりました。

まとめ

Vue.jsでのリアクティブな変化は仮想DOMの働きによって実現されていました。
普段の業務でフレームワークを使う際に仮想DOMなどの様に、裏側の原理について必ずしも理解しておく必要はないと思いますが、現段階でなぜここまでVue.jsが広まっているのか?を知る一旦にはなりそうです。

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

vue.jsでmomentを使ってみた

Vue.js(Vue CLI)moment.jsを使ってみたときのメモです。

やっておくこと

基本的なところはスッとばします。

$ vue create vue-moment-sample
$ yarn add moment

サンプルソース

今回はサンプルなので、コンポーネントではなくApp.vueにて試しています。

.src/App.vue
<template>
  <div id="app">
    <h1>MOMENT SAMPLE</h1>
    <h2>現在日</h2>
    <p>{{ getTime | moment }}</p>
    <h2>生まれた日</h2>
    <p>{{ "1990-10-23 00:00:00" | moment }}</p>
    <h2>生まれてから</h2>
    <p>{{ elapsedDate(getTime , "1990-10-23") }}</p>
  </div>
</template>

<script>
import moment from "moment" //利用するコンポーネントでインポート

export default {
  name: 'App',
  computed: {
    getTime() {
      return moment()
    }
  },
  methods: {
    elapsedDate(a, b) {
      return moment(a).diff(moment(b), 'days', false)
    }
  },
  filters: {
    moment(date_str) {
      moment.locale('ja')
      return moment(date_str).format('YYYY-MM-DD')
    }
  }
}
</script>

フォーマットについて

  • moment().format('YYYY/MM/DD')というような形でフォーマットを指定する。

日本語でよく使いそうなフォーマットを整理してみた。

日時 フォーマット 備考
2020/2/28 YYYY/M/D
2020-02-28(金) YYYY-MM-DD(dd) moment.locale('ja')が必要
2月28日 金曜日 MMMDo dddd moment.locale('ja')が必要
2/28 14:25 M/D HH:mm 0時を24時として扱いたい場合はHHの代わりにkkを用いる
2/28 午後 14:25 M/D(dd) a hh:mm 英語表記の場合は、aでam/pm、AでAM/PM

わかったことメモ

  • filtersとして定義してフォーマット(YYYY-MM-DDなど)を整えるのに使える
  • 時間の加算・減算などの処理のときも、活躍しそう
  • 日本語表記など特定語表記を使いたい場合はmoment.locale()を使う

調べてることメモ

いったんググっても出てこなかったところは、少しずつ使いながら調べていく。

  • main.jsなどで一度moment.locale('ja')しておけば良いのか?
  • vue-momentvue-cli-plugin-momentなどはどう使う?

参考

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

vue.jsでmomentを使いこなしたい

Vue.js(Vue CLI)moment.jsを使ってみたときのメモです。

必要なこと

基本的なところはスッとばします。

$ vue create vue-moment-sample
$ yarn add moment

ソースコード

.src/App.vue
<template>
  <div id="app">
    <h1>MOMENT SAMPLE</h1>
    <h2>現在日</h2>
    <p>{{ getTime | moment }}</p>
    <h2>生まれた日</h2>
    <p>{{ "1990-10-23 00:00:00" | moment }}</p>
    <h2>生まれてから</h2>
    <p>{{ elapsedDate(getTime , "1990-10-23") }}</p>
  </div>
</template>

<script>
import moment from "moment"

export default {
  name: 'App',
  computed: {
    getTime() {
      return moment()
    }
  },
  methods: {
    elapsedDate(a, b) {
      return moment(a).diff(moment(b), 'days', false)
    }
  },
  filters: {
    moment(date_str) {
      moment.locale('ja')
      return moment(date_str).format('YYYY-MM-DD')
    }
  }
}
</script>

わかったことメモ

  • filtersとして定義してフォーマット(YYYY-MM-DDなど)を整えるのに使える
  • 時間の加算・減算などの処理のときも、活躍しそう
  • 日本語表記など特定語表記を使いたい場合はmoment.locale()を使う

調べてることメモ

いったんググっても出てこなかったところは、少しずつ使いながら調べていく。

  • main.jsなどで一度moment.locale('ja')しておけば良いのか?
  • vue-momentvue-cli-plugin-momentなどはどう使う?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む