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

webAPI入門したと言いたい

はじめに

webAPIとは何それ美味しいの?って感じだったので、今回少し触ってみました。

やりたいこと

このサイトのAPIを利用してみる。
googleのAPIやqiitaのAPIを試してみようとしたが、ドキュメントが読めず挫折しました。また今度挑戦します。

やったこと

  1. JsFiddlevue.jsの環境を用意する。サンプル
  2. axiosメソッドを使用して、API通信をする。
  3. htmlに取得したデータを表示する。
  4. 以上!

参考

完成版

苦戦したこと

  • axiosに指定するURLがどれか分からなかった。image.png

GETの横に/apiと書いてあるので、既存のURLの末尾に追加するとjsonデータが取得できた。

  • APIで取得したデータから各プロパティを抽出する方法が最初分からなかった。
  • cssの設定で、画像を中央に文字を重ねることができなかった。

最後までできなかったこと

  • csstransform: translate (-50%, -50%);が機能しない。
  • 一番やりたかったこと。取得した画像と文字を同時に表示させる方法。(是非教えて頂きたい・・・)

最後に

この記事を書くまで、qiitaにJsFiddleを埋め込めれると思っていたが、できなかった。CodePenなら対応しているみたい。
今後は、CodePenを利用していこうとおもう。

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

Nuxt.js + Vue.js + Vuex によるカウンターのサンプルコード

概要

  • Nuxt.js + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する

今回の環境

  • Node.js 13.12.0
  • Nuxt.js 2.12.1

Nuxt.js + Vuex によるカウンターのサンプルコード

ソースコード一覧

├── package.json
├── pages
│   └── counter.vue
└── store
    └── counter.js

package.json

{
  "name": "my-app",
  "dependencies": {
    "nuxt": "2.12.1"
  }
}

pages/counter.vue

<template>
  <div>
    <p>{{ count }}</p>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script>
export default {
  computed: {
    count () {
      console.log('Call the computed count')
      return this.$store.state.counter.count
    }
  },
  methods: {
    // 「+」ボタンクリック時に呼ばれる
    increment () {
      console.log('Call the methods increment')
      this.$store.commit('counter/increment')
    },
    // 「-」ボタンクリック時に呼ばれる
    decrement () {
      console.log('Call the methods decrement')
      this.$store.commit('counter/decrement')
    }
  }
}
</script>

store/counter.js

// カウンターの値を管理するストア (Vuex.Store)

export const state = () => ({
  count: 0
})

export const mutations = {
  increment (state) {
    console.log('Call the mutations increment')
    state.count++
  },
  decrement (state) {
    console.log('Call the mutations decrement')
    state.count--
  }
}

Node.js サーバを起動

package.json に記述したライブラリをインストール。

$ npm install

Node.js サーバを起動。

$ ./node_modules/nuxt/bin/nuxt.js

   ╭─────────────────────────────────────────────╮
   │                                             │
   │   Nuxt.js v2.12.1                           │
   │   Running in development mode (universal)   │
   │                                             │
   │   Listening on: http://localhost:3000/      │
   │                                             │
   ╰─────────────────────────────────────────────╯

ℹ Preparing project for development
ℹ Initial build may take a while
✔ Builder initialized
✔ Nuxt files generated

✔ Client
  Compiled successfully in 5.69s

✔ Server
  Compiled successfully in 5.30s

ℹ Waiting for file changes
ℹ Memory usage: 121 MB (RSS: 200 MB)
ℹ Listening on: http://localhost:3000/

Web ブラウザで http://localhost:3000/counter にアクセスすると「+」「-」ボタンと数値が増減するカウンターが表示される。

参考資料

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

Vue.js + Vuex によるカウンターのサンプルコード

概要

  • Vue.js + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する

今回の環境

  • Vue.js 2.6.1
  • Vuex 3.1.3

Vuex とは

Vuex とは何か? | Vuex

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。

ステート | Vuex

Vuex は 単一ステートツリー (single state tree) を使います。つまり、この単一なオブジェクトはアプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します。これは、通常、アプリケーションごとに1つしかストアは持たないことを意味します。

Vuex 入門 | Vuex

Vuex アプリケーションの中心にあるものはストアです。"ストア" は、基本的にアプリケーションの 状態(state) を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。

  1. Vuex ストアはリアクティブです。Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。

  2. ストアの状態を直接変更することはできません。明示的にミューテーションをコミットすることによってのみ、ストアの状態を変更します。これによって、全ての状態の変更について追跡可能な記録を残すことが保証され、ツールでのアプリケーションの動作の理解を助けます。

Vue.js + Vuex によるカウンターのサンプルコード

以下の HTML を Web ブラウザで読み込むと「+」「-」ボタンと数値が増減するカウンターが表示される。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue.js + Vuex ぽちぽちカウンターサンプル</title>
</head>
<body>

<div id="app">
  <p>{{ count }}</p>
  <p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script src="https://unpkg.com/vuex@3.1.3/dist/vuex.js"></script>

<script>

// カウンターの値を管理するストア
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    // increment: state => state.count++
    increment() {
      console.log('Call the mutations increment')
      this.state.count++
    },
    // decrement: state => state.count--
    decrement() {
      console.log('Call the mutations decrement')
      this.state.count--
    }
  }
})

new Vue({
  el: '#app',
  computed: {
    count () {
      return store.state.count
    }
  },
  methods: {
    // 「+」ボタンクリック時に呼ばれる
    increment() {
      console.log('Call the methods increment')
      store.commit('increment')
    },
    // 「-」ボタンクリック時に呼ばれる
    decrement() {
      console.log('Call the methods decrement')
      store.commit('decrement')
    }
  }
})
</script>

</body>
</html>

参考資料

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

イベントハンドリング

イベントハンドリングとは

ボタンをクリックしたり、マウスオーバーやスクロールなどユーザーによる一連の挙動を「イベント」と呼ぶ。様々なイベントに対応して発動させる処理のことを「イベントハンドラ」と呼び、イベントハンドラをイベントと紐づけることを「ハンドル」という。

vueにおいてのイベントハンドラ

vueでイベントによってイベントハンドラを実行させるにはv-onディレクティブを用いる。

基本構文

<button v-on:イベント名="処理(関数)">add</button>
<button v-on:click="counter += 1">add</button>

省略記法

<div @イベント名="処理(関数)"></div>
sample.html
<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>上のボタンがクリックされた回数{{ counter }} 回</p>
</div>
sample.js
var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})

ボタンをクリックすることでイベントハンドラが実行され、counterの数字が+1されていく。

インラインメソッドハンドラ

ここではv-on属性の値に直接javascriptの式を記述している。このような記述法をインラインメソッドハンドラという。

メソッドイベントハンドラ

イベントハンドラの仕組みはより複雑になっていくので式ではなく、メソッドの名前に置き換えることで、すっきりとしたコードが記述できる。そのような記述方法をメソッドイベントハンドラという。

sample.html
<div id="app">
  <!-- クリックすることでshowMessageメソッドが呼び出される -->
  <button v-on:click="showMessage">ボタン</button>
 <!-- 「こんにちわ」と表示される -->
  <p>{{ msg }}</p>
</div>
sample.js
new Vue({
  el: '#app',
  data: function () {
    return {
      msg: ""
    };
  },
  methods: {
    // msgに「こんにちわ」が代入される
    showMessage: function () {
      this.msg = 'こんにちわ'
    }
  }
});
イベント一覧
記述 イベントハンドラ実行タイミング
@click クリック時
@focus フォーカス時
@blur フォーカスが外れた時
@change input変更時
@dblclick ダブルクリック時
@submit サブミット時
@keyup キーアップ時
@keydown キーダウン時
@mouseover カーソルを当てた時(hoverと同じ)
@mouseout カーソルを外した時
@mouseenter カーソルを当てた時(子要素に反応させない)
@mouseleave カーソルを外した時(子要素に反応させない)
@mousedown マウスでクリックした時
@mouseup クリックしたマウスを上げた時
@mousemove マウス動かしてる時
@touchstart スマホとかでタッチした時
@touchmove タッチ状態から動かした時
@touchend 指を離した時

イベント修飾子

v-onディレクティブのイベントに対する制御をより細かく指定することのできる機能の総称をイベント修飾子という。通常のHTMLでは同じイベントをハンドルしたDOMがネストしている場合、バブリングといって子要素から外側(親要素方向)に向かって順にイベントが連鎖していく。イベント修飾子を付与することでそのバブリングを制御することができる。

基本構文.html
<button v-on:click.イベント修飾子="alert">add</button>

<!-- 修飾子は繋げることもできる -->
<button v-on:click.stop.prevent="alert">add</button>
イベント修飾子一覧
記述       機能     
.stop イベントの連鎖を止める
.prevent イベントの規定の動作をキャンセル
.capture 子要素のイベント実行時に親要素のイベントを先に実行
.self 子要素から親要素のイベントが発生しなくなる
.once イベントを一回だけ実行
.passive .preventの影響を無視

キー修飾子

特定のキーコードのキー入力時に、特定のイベントハンドラを呼び出す。

基本構文.html
<input type="text" v-on:keydown.キー修飾子="alert">

<!-- 複数つけた場合は「OR」を意味する -->
<input type="text" v-on:keydown.up.down.left.right="alert">
キー修飾子一覧
記述       機能     
.enter enterが押されたとき
.tab tabが押されたとき  
.delete deleteが押されたとき
.esc escが押されたとき  
.space spaceが押されたとき
.up 矢印の上キーが押されたとき
.down 矢印の下キーが押されたとき
.left 矢印の左キーが押されたとき
.right 矢印の右キーが押されたとき

システム修飾子

対応するキーが押されている場合にハンドラが呼び出される。

基本構文.html
<!-- shiftキーを押しながらクリックしたとき -->
<button v-on:click.shift="doDelete">削除ボタン</button>
システム修飾子一覧
記述       機能     
.ctrl ctrlが押されている場合
.alt altが押されている場合  
.shift shiftが押されている場合
.meta metaが押されている場合  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueのディレクトリの責務について考える(2)

Vueのディレクトリの責務について考える - Qiitaの続きです。

ロジックを追加する場所

Vueで業務ロジックを追加する場合、以下の4通りの構成が思い浮かびました。

  • SFCのメソッド内に直接書く
  • Vuexを使う
  • vuex-ormを使う
  • モデルを作成する

SFCのメソッド内に直接書く

前回まではこのように書いていましたが、コンポーネント間で同じロジックが書かれたりすることにより保守が大変になります。それを避けるためにutil関数とかを作ったりしても用途不明の関数が大量生産され残念なことに。

(小規模なアプリならこの書き方で問題ないです。)

Vuexで頑張る

複雑な業務ロジックがない場合はVuexでいいかもしれません。(APIを取得してフィルターをかけるだけのような)
ただVuexに依存することになるので、他のFWに変更するとなった場合のコストが大きいです。

vuex-ormを使う

https://github.com/vuex-orm/vuex-orm

バックエンドのORMのような使用心地、メソッドチェーンでクエリーが記述できるのですごく使いやすいです。(Laravelのormにそっくり)
Vuexの上にさらにライブラリを使うのでもうVueと添い遂げる覚悟が必要そうですね。

モデルを作成する

この方法が現状では一番いいと思っています。Vuexと併せて使うことも可能です。AngularやReactや将来出る新しいFWに変更する場合もスムーズに移行できると思います。
以下の資料が分かりやすいので、ぜひ読んでみてください。

Vue.jsで考えるMVVM

モデルの例

QiitaItemモデルの作成

models/QiitaItem.ts
export default class QiitaItem {
  constructor(
    public body: string,
    public created_at: string,
    public title: string,
    public url: string,
  ) {}

  shortTitle(): string {
    return this.title.substr(0, 20) + '...';
  }
}

(TypeScriptのクラスではコンストラクターの引数にアクセス修飾子を付与することで、プロパティの生成と初期化を行なってくれる。)

Repositoryからモデルを返す

repositories/qiitaRepository.ts
import axios from 'axios';
import config from '@/config';
import QiitaItem from '@/models/QiitaItem'

const baseURL = 'https://qiita.com/api/v2/';

const Repository = axios.create({
  baseURL,
  headers: {
    Authorization: 'Bearer ' + config.qiita.token,
  },
});

export default {
  async getItemsBy(userId: number): Promise<QiitaItem[]> {
    let qiitaItems = [];
    const { data } = await Repository.get(`users/${userId}/items`);
    foreach (qiitaItem of data) {
      qiitaItems.push(new QiitaItem(qiitaItem));
    }
    return qiitaItems;
  },
};

コンポーネントからはRespositoryの関数を実行するとModelが返ってくる。(配列のモデル)

Xxx.vue
import qiitaRepository from '@/repositories/qiitaRepository'
...
const qiitaItems = qiitaRepository.getItemsBy(123);
qiitaItems[0].shortTitle();

ここまでのディレクトリ構成

├── components // ステートフルコンポーネント、ステートレスコンポーネント
├── containers // コンテナコンポーネント
├── repositories // API通信
├── models // モデル達
├── views // ページと1:1
├── App.vue // トップページのコンポーネント
├── main.js // プラグインの使用など
├── router.js // ルーティング
└── store.js // Vuex

シンプルな業務ロジックであれば、repositoriesとmodelsで完結するのですが、複数のドメインが絡み合うロジックについてはservicesに定義します。(次回に続く?)

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

Vue.js+Vuetify.jsを使ったWEBアプリケーション構築 ~画面遷移(Vue Router)の設定~

はじめに

今回は以前投稿した記事「Vue.js+Vuetify.jsを使ったWEBアプリケーション構築」のサンプルアプリケーションを基に画面遷移の設定について説明したいと思います。

画面遷移の設定

画面遷移はVue Router使って設定します。
Vue Routerとは・・・

https://router.vuejs.org/ja/

Vue Router は Vue.js 公式ルータです。これは Vue.js のコアと深く深く統合されており、Vue.js でシングルページアプリケーションを構築します。機能は次の通りです:
・ネストされたルート/ビューマッピング
・モジュール式、コンポーネントベースのルータ構造
・ルートパラメータ、クエリ、ワイルドカード
・Vue.js の transition 機能による、transition エフェクトの表示
・細かいナビゲーションコントロール
・自動で付与される active CSS クラス
・HTML5 history モードまたは hash モードと IE9 の互換性
・カスタマイズ可能なスクロール動作

Vue Routerの機能の内、サンプルアプリケーションで利用しているのは次の機能です。

  • モジュール式、コンポーネントベースのルータ構造
  • ルートパラメータ、クエリ、ワイルドカード
  • 細かいナビゲーションコントロール
  • HTML5 history モードまたは hash モードと IE9 の互換性

サンプルアプリケーションでの画面遷移の設定は以下のようになります。
ソースコードにコメントで説明します。

src/router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from './store';
import * as aTypes from '@/store/action-types';
import NotFound from './components/base/NotFound';
import Ichiran from './components/screen/Ichiran';
import Shosai from './components/screen/Shosai';
import Toroku from './components/screen/Toroku';
import TorokuKanryo from './components/screen/TorokuKanryo';
import Transition from './utilities/Transition';

// VueでVue Routerを利用する宣言です。
Vue.use(VueRouter);

// ルートパラメータの設定をします。
// 表示言語をURLから取得できるようにします。
const PATH_PREFIX = '/:lang';

// コンポーネントベースのルータ構造となります。
let routes = [
  {
    // トップページの設定です。
    path: '/',
    // 「/【表示言語】/ichiran」へリダイレクトします。
    redirect: '/' + store.state.lang + '/ichiran'
  },
  // 404画面は{path: PATH_PREFIX}の前に定義しないと
  // 「/404」は{path: PATH_PREFIX}にマッチしてしまうので、
  // 後続のbeforeEachで無限ループになってしまうので注意してください。
  {
    path: '/404',
    component: NotFound
  },
  {
    path: PATH_PREFIX,
    redirect: PATH_PREFIX + '/ichiran'
  },
  {
    // 「/【表示言語】/ichiran」の設定をします。
    path: PATH_PREFIX + '/ichiran',
    // 名前を指定して画面遷移できるようにします。
    name: 'ichiran',
    // 「/【表示言語】/ichiran」はIchiran画面コンポーネントを表示します。
    component: Ichiran
  },
  {
    path: PATH_PREFIX + '/shosai',
    name: 'shosai',
    component: Shosai
  },
  {
    path: PATH_PREFIX + '/toroku',
    name: 'toroku',
    component: Toroku
  },
  {
    path: PATH_PREFIX + '/kanryo',
    name: 'kanryo',
    component: TorokuKanryo
  },
  {
    // 上記以外のURLは404のNotFound画面を表示します。
    path: '*',
    redirect: '/404'
  }
];

// VueRouterオブジェクトの作成
const router = new VueRouter({
  // デフォルトはhashモードでURLが「http://localhost:8080/#/ja/ichiran」のようになります。
  // historyモードにしてURLを「http://localhost:8080/ja/ichiran」のようにします。
  // 環境変数から取ってくるようにしているのはモックアップの際はhistoryでは動作しない為です。
  mode: process.env.VUE_APP_ROUTER_MODE,
  // HTTPサーバにアプリを置く際に特定のディレクトリに置く場合などに指定します。
  base: process.env.BASE_URL,
  // 上記のルートを設定します。
  routes
});

// ・・・省略

// 画面遷移前イベント
router.beforeEach((to, from, next) => {
  // ・・・省略

  // langが設定されていない場合、または対応していない言語の場合は404画面を表示するようにします。
  // langが設定されていない場合というのは、サンプルアプリケーションでは404画面です。
  // 「/404」が対応していない言語として「/404」へのリダイレクトが無限ループします。
  if (!to.params.lang || store.state.enableLangs.includes(to.params.lang)) {
    movePage(next, to.params.isCacheClear, TRANSITION_TIME);
  } else {
    next({path:'/404'});
  }
})

// 画面遷移後イベント
router.afterEach((to) => {
  // ・・・省略
})

// Vue Routerオブジェクトを返却
export default router;
src/main.js
// ・・・省略
new Vue({
  render: h => h(App),
  // VueにVue Routerを設定して、コンポーネントから利用できるようにします。
  router,
  i18n,
  store
}).$mount('#app');

src/Shosai.vue
// ・・・省略
<script>
// ・・・省略
  methods: {
    setTorokuJoho() {
      // 余談ですが、Vue Routerには、$routeと$routerがありますので、
      // ご利用の際は混乱しないようにお気を付けください。
      if (this.$route.params.torokuJoho) this.torokuJoho = this.$route.params.torokuJoho;
    },
    back() {
      // こんな感じで画面遷移できます。
      // pathでも画面遷移できますが、表示言語をURLに含める必要があるので、nameで遷移しています。
      this.$router.push({name: 'ichiran'});
    }
  },
// ・・・省略
</script>

おわりに

Vue Routerは日本語マニュアルも充実しているし、使い方もシンプルなのであまり苦労はしないかもしれません。
私は、無限ループで若干手間取りました。
リダイレクトとルートの定義順には気を付けましょう。

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

【Nuxt.js】Components番外編: 親子間のやりとりを$emit, props, Vuexで見てみよう

前置き

コンポーネント間のやりとりについて、
やりたいことがある時に
パターンがいくつかあるよという例です?
近々Vuexに関する記事を投稿していたので
そのパターンも含めて書いてみました✍️
それぞれどんな時に使うかも
目次ごとに記載しています?
・emit, $event
・propsをfunc
・ Vuex

propsでfuncすることは
あまりないのですが…!
初めから誰もが見やすい
完璧なコードを目指すのではなく、
動かすだけならこんな方法もある!
と知ることは大事だと思います??

?それぞれ基礎編の記事はこちら
emit基礎編
https://note.com/aliz/n/nd6e771724cd7
props基礎編
https://note.com/aliz/n/n99144d4556b9
vuex基礎編
https://note.com/aliz/n/n497914c981c8

やりたいこと

name.gif

buttonで名前を変えるだけです笑
とにかくシンプルにわかりやすく!
がモットーです?
表示はこちらです。
・親ではname
・子ではmyName

file
components/
--| Name.vue

pages/
--| index.vue

store/
--| index.js

パターン1: $emit, $event

【どんな時に使う?】
今回のように構造がシンプルな時⭕️
というか基本はコレです!✨
親で渡しているpropsの値を変えます。
これができればモーダルウィンドウも簡単!
https://note.com/aliz/n/n2f0bc857defb

❓では子で値を変えると
 動作はしますがエラーになりますね。
error.gif

Name.vue
<template>
 <div class="button">
   <p>myName: {{ myName }}</p>
   <button @click="changeName">
     reset
   </button>
 </div>
</template>

<script>
export default {
 props: {
   myName: {
     type: String,
     required: false
   },
 },
 methods: {
   changeName() {
     this.myName = "Max"
     this.$emit('nameSwitch', this.myName)
   // または1行にまとめて
     this.$emit('nameSwitch', 'Max')
   }
 },
}
</script>

【index.vue】
@nameSwitch="name = $event"
親にあるdataのname: 'Bob'を
イベントで操作します?
@nameSwitch="name"にすると
通常methodsのname()になるけど
dataのnameをイベントで渡すことにより
子のmethods, resetNameを使用します!

$emitカスタムイベント
https://jp.vuejs.org/v2/guide/components-custom-events.html

index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
     @nameSwitch="name = $event"
   />
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 data () {
   return {
     name: "Bob"
   }
 },
}
</script>

親にmethods追加して
名前を戻してみましょう。
ちゃんと戻りますね?
error2.gif

index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
     @nameSwitch="name = $event"
   />
   <button @click="resetName">
     reset
   </button>
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 data () {
   return {
     name: "Bob"
   }
 },
 methods: {
   resetName () {
     this.name = "Bob"
   },
 },
}
</script>

エラー解消は
子で値を変えなければOKです!
基礎編をご覧ください??

パターン2: propsをfunc

【どんな時に使う?】
あんまり使いません。
前置きに書いたように
動かす手段の1つとしてご紹介。

【特徴】
コードは単純で分かりやすいです?
ただこれをやるなら
$emitやvueの方が良いですね⭕️
子と親でのやりとりを
理解するのには最もシンプルで
良いかもしれません?

こちらが参考になります!
https://kuroeveryday.blogspot.com/2017/09/vuejs-callback-vs-emit-events.html

Name.vue
<template>
 <div class="button">
   <p>myName: {{ myName }}</p>
   <button @click="resetFn">
     resetFn
   </button>
 </div>
</template>

<script>
export default {
 props: {
   myName: {
     type: String,
     required: false
   },
   resetFn: {
     type: Function
   },
 },
}
</script>
index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
   :resetFn = "resetName"
   />
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 data () {
   return {
     name: "Bob"
   }
 },
 methods: {
   resetName () {
     this.name = "Max"
   },
 },
}
</script>

パターン3: vuex

【どんな時に使う?】
ネストが深すぎてpropsがどうなってるか
分かりにくい!!って時に使います。
あとはサーバーとの通信が基本なので
今回のシンプルかつ通信不要な際は使いません。

ただ導入が容易ではなく
気軽にオススメはしません?‍♂️?
本当に必要な時には⭕️

【特徴】
一見、コードもファイルも増えて
ごちゃついてそうな印象ですが☁️
何がどこにあって、が超絶見やすいですね✨?

Name.vue
<template>
 <div class="button">
   <p>myName: {{ myName }}</p>
 </div>
</template>

<script>
export default {
 props: {
   myName: {
     type: String,
     required: false
   },
 },
}
</script>
index.vue
<template>
 <div class="page">
   <p>name: {{ name }}</p>
   <Name
     :myName="name"
     @nameWasReset="name = $event"
   />
   <button @click="$store.commit('changeName')">
     change
   </button>
 </div>
</template>

<script>
import Name from '~/components/Name.vue'

export default {
 components: {
   Name
 },
 computed: {
   name () {
     return this.$store.getters.name
   },
 },
}
</script>
index.js
export const state = () => ({
 name: "Max",
})

export const getters = {
 name(state) {
   return state.name
 },
}

export const mutations = {
 changeName(state) {
   state.name = "Bob"
 },
}

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

https://twitter.com/aLizlab

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

Vue.js・Nuxt.jsに入門したときにみたリンク集

はじめに

Nuxt.jsでとあるサイト(HPぐらいのレベル感で状態管理とかはほとんどないシンプルなアプリケーション)を構築することになりVueに入門しました。
Nuxt.jsでSPAでの構築、UIフレームワークはVuetifyです。
筆者は、Reactの経験はありますが、Vueの経験はありません。
そのため、公式系をざざっと読み、Nuxtでアプリを構築しながら必要なことを調べながら実装しました。
そのときに調べたことのリンク集です。

だいたいこれで理解した。

公式系

Nuxt

Vue

Deploy

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

フロントエンドの速度改善でやったこと

最近、Vue.jsで作られたWebページの速度改善をやりました。
対応内容について書き残します。

前置き

  • ページ読み込み速度ではなく、読み込んだ後の速度を改善しました
  • 対応内容は、Vue.jsに限らず、フロント全般で有効な内容です
  • バックエンドの速度改善は含まれません

概要

No 何をした? どうやった?
1 API呼び出しの並列化 Promise.all()を使う
2 大量データの一覧表示が重くならないようにする virtual-scrollを使う

対応1: API呼び出しの並列化

before

async mounted() {
  // 4つのAPIを順次実行
  const response1 = await apiRequest1();
  const response2 = await apiRequest2();
  const response3 = await apiRequest3();
  const response4 = await apiRequest4();
},

mounted()内で複数のAPIを順次実行している箇所がありました。
当たり前の話ですが、この場合
処理時間 = api1の実行時間 + api2の実行時間 + api3の実行時間 + api4の実行時間
となるため、遅いです。
幸い今回はapiの実行順序を守る必要がなかったため、次のように並列化しました。

after

async mounted() {
  // 4つのAPIを並列実行
  const [
    response1,
    response2,
    response3,
    response4,
  ] = await Promise.all([
    apiRequest1(),
    apiRequest2(),
    apiRequest3(),
    apiRequest4(),
  ]);
},

これで、
処理時間 = api1〜4のうち一番遅いものの実行時間
となり、速くなりました。

なお、細かいエラー処理をしたい場合は次のようにもできます。

async mounted() {
  const [
    response1,
    response2,
    response3,
    response4,
  ] = await Promise.all([
    apiRequest1().catch((err) => {
      console.log(`apiRequest1() failed: err(${err})`);
    }),
    apiRequest2().catch((err) => {
      console.log(`apiRequest2() failed: err(${err})`);
    }),
    apiRequest3().catch((err) => {
      console.log(`apiRequest3() failed: err(${err})`);
    }),
    apiRequest4().catch((err) => {
      console.log(`apiRequest4() failed: err(${err})`);
    }),
  ]);
  console.log('Promise.all finished!');
  // 例えばapiRequest4()だけ失敗した場合は、
  //   apiRequest4() failed: err(エラー内容)
  //   Promise.all finished!
  // が出る。
  // response4はundefinedになる。
},

対応2: 大量データの一覧表示が重くならないようにする

about

データの一覧を表示している箇所について、データ数が多くなると、動作がもっさりする問題がありました。
対策として、virtual-scrollを導入しました。

virtual-scrollとは、ユーザーに見える部分だけDOMを生成することで、擬似的にスクロールを実現する仕組みのことを指します。
例えば1万件データがあった場合、
普通にv-forでlist表示をすると、1万個のDOMが描画されます。
一方、virtual-scrollでlist表示すると、ユーザに見える分+α(サイズにもよるけどせいぜい数十個)のDOM描画で済みます。

参考: https://kakkoyakakko2.hatenablog.com/entry/2018/10/23/003000
(ユーザに見える部分だけDOMを生成する様子がgifアニメになっており、わかりやすいです)

sample

各種フレームワークにvirtual-scroll用のライブラリがあるようですが、今回はvueを使っているため、
vue-virtual-scroll-listを使いました。
以下、サンプルコードと、実行結果です。

<template>
  <div class="container">
    <h1>Qiita Test</h1>

    <!-- 普通にv-forで回すと、データ量が多いときの描画コストが大きい -->
    <h2>Before: normal list</h2>
    <ul style="height: 200px; overflow: scroll;">
      <li
        style="list-style: none;"
        v-for="user in users"
        :key="user.id"
      >
        {{ user.name }}
      </li>
    </ul>

    <!-- virtual-scrollを使うと、表示される分だけ描写するため、描画コストがデータ量に依存しない -->
    <h2>After: virtual scroll list</h2>
    <ul style="height: 200px; overflow: scroll;">
      <vue-virtual-scroll-list
        :size="20"
        :remain="15"
      >
        <li
          v-for="user in users"
          :key="user.id"
        >
          {{ user.name }}
        </li>
      </vue-virtual-scroll-list>
    </ul>
  </div>
</template>

<script lang="ts">
import vue from 'vue';
import VueVirtualScrollList from 'vue-virtual-scroll-list';

export default vue.extend({
  components: {
    VueVirtualScrollList,
  },
  data() {
    return {
      users: [] as Array<{id: number, name: string}>,
    };
  },
  async mounted() {
    this.users = [
      {id: 1, name: 'user1'},
      {id: 2, name: 'user2'},
      {id: 3, name: 'user3'},
      {id: 4, name: 'user4'},
      // 後略。大量のユーザーがいるとします。
    ];
  },
});
</script>

1.png

最後に

virtual-scrollは同僚に教えてもらいました。
スクロールするとDOMがゴリゴリ変わっていくのは、なかなかにお洒落。
5, 6秒かかってた処理が一瞬になったときの気持ちよさったらなかったです。

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

PythonでGoogleカレンダーのfaviconみたいな画像をたくさん生成してVueのプロジェクトに組み込んでみた

この記事は株式会社クロノス「~2020年春~勝手にやりますアドベントカレンダー」の19日目の記事です。

はじめに

Googleカレンダーのfaviconをご存知でしょうか?
スクリーンショット 2020-03-15 17.56.17.png

この画像は3月15日に取った画像ですが、実はアクセスした日によって中の数字が変わる仕組みになってたりします。

ということで自分も前にカレンダー的なアプリを作ったのでそれに導入してみることにします。

前まではこんな感じでした。

スクリーンショット 2020-03-15 17.59.33.png

はい、デフォルトです。
めっちゃVue.jsで作ってます!!!!っていうのが伝わる感じになっていますね。
技術のアピールは大事ですが、アプリとしては若干かっこ悪いので修正してみます。

開発者ツールでGoogleカレンダーを見てみると、1〜31まで全パターンのfaviconを用意しているみたいなので、Googleにならって全パターン用意して表示を切り替えるような感じにしてみます。

画像を用意する

使用したツール等

  • Google Colaboratory
  • Python 3.6.9(Colaboratoryに入ってたバージョン)
  • PIL(Python Image Library)
  • ICOOON MONO(アイコン)

ベースとなる画像を用意する

今回はICOOON MONOにあるカレンダーのアイコンに数字を重ねて画像を生成してみます。
こちらのアイコンを使用させていただきました。

キャプチャ.PNG

サイズは48×48、カラーはアプリのテーマに合わせて紫(rgb(121, 88, 214))にしました。

Colaboratoryを開く

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

スクリーンショット 2020-03-15 18.23.25.png

まずベース画像をアップロードします。今回はbase.pngという名前でアップロードしました。

次に加工後の画像を格納するフォルダを作成します。今回はoutというフォルダを作成しました。

以上の準備ができたらPythonのコードを書いていきます。
真ん中に数字を配置するためにちょっとトリッキーなことやってたりします。

画像を合成するコード
from PIL import Image

# 各種設定
IMAGE_WIDTH = 48
IMAGE_HEIGHT = 48
THEME_COLOR = (121, 88, 214)

# 1〜31までループして作成する
for i in range(1, 32):
  # faviconに表示する数字
  i_str = str(i)

  # Font名、サイズを設定する 
  fnt = ImageFont.truetype('LiberationMono-BoldItalic', 25)
  # 数字を配置する場所を計算するために文字の横幅、縦幅を取得する
  w, h = fnt.getsize(i_str)

  # ベースの画像を読み込む
  im = Image.open('./base.png')
  draw = ImageDraw.Draw(im)
  # 読み込んだ画像にテキストを合成する
  draw.text(
      # こう書くと真ん中に配置できるみたい(高さだけ3px微調整してます)
      xy=((IMAGE_WIDTH - w) / 2, (IMAGE_HEIGHT - h) / 2 + 3 ), 
      text=i_str, 
      fill=THEME_COLOR, 
      font=fnt
  )

  # 保存 ./out/favicon01.pngみたいなファイル名にしています
  im.save("./out/favicon{}.png".format(i_str.zfill(2)))

ちなみにフォントは好きなものをダウンロードしてCalaboratoryにアップロードすれば使えるようになりますが、以下のコードを実行すると組み込まれているフォントを確認できます。
今回は数値を扱うだけだったので組み込みのものを使用しました。

Colaboratoryのフォントを確認するコード
import subprocess

res = subprocess.check_output("fc-list")

print(str(res).replace(":", "\n"))

上手く行けばoutフォルダ内に以下のような画像が生成されるはずです。
favicon01.png
outフォルダ内の画像をダウンロードしましょう。
Colaboratoryだとフォルダ丸ごとダウンロードできなさそうなので
以下のコードでzipにしておくと楽にダウンロードできます。

outフォルダをzipに固める
import shutil

shutil.make_archive('./out', 'zip', root_dir='./out')

Vueのプロジェクトに組み込む

Vueのプロジェクトのpublicフォルダ内に生成した画像を配置しましょう。

index.htmlを修正します。
faviconを設定しているlinkタグのhref属性を書き換えるscriptを追加します。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- 追加部分 -->
    <script>
      const faviconLink = document.querySelector("link[rel='icon']");
      // 0埋めした日付を取得して favicon01.png みたいな文字列を生成してます
      faviconLink.href = `favicon${("0" + new Date().getDate()).slice(-2)}.png`
    </script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

いい感じになりました。(満足)

スクリーンショット 2020-03-15 18.56.02.png

あとはビルドして本番環境にデプロイです。
めでたしめでたし・・・

と思いきや・・・

次の日(16日)にアクセスしてみると、アイコンが15から変わってないじゃないですか。
なんでや…!と思い、トランスパイルされたindex.htmlを見てみると

dist/index.html
<!DOCTYPE html><html lang=ja><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><script>const faviconLink = document.querySelector("link[rel='icon']");
これ   faviconLink.href =  `favicon15.png`</script><link rel=stylesheet href=https://use.fontawesome.com/releases/v5.2.0/css/all.css><title>ad-calendar</title><link href=/css/app.9c57fa73.css rel=preload as=style><link href=/css/chunk-vendors.a4393e1d.css rel=preload as=style><link href=/js/app.ed32e83e.js rel=preload as=script><link href=/js/chunk-vendors.80e1df9b.js rel=preload as=script><link href=/css/chunk-vendors.a4393e1d.css rel=stylesheet><link href=/css/app.9c57fa73.css rel=stylesheet></head><body><noscript><strong>We're sorry but ad-calendar doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.80e1df9b.js></script><script src=/js/app.ed32e83e.js></script></body></html>

なんとビルドを実行した日付に固定されてしまってますね。
index.html内でJavaScriptのテンプレート記法を使うと実行時の値に固定されてしまうっぽいです。

最終的には以下のように修正すると期待通り動作するようになりました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <script>
      const faviconLink = document.querySelector("link[rel='icon']");
      // 変数に格納するようにした
      const now = new Date();
      const nowDate = ("0" + now.getDate()).slice(-2);
      // +演算子で文字列結合する
      faviconLink.href = "<%= BASE_URL %>favicon" + nowDate + ".png";
    </script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

今度こそいい感じになりました。(満足)

参考

https://qiita.com/agajo/items/90a29627e7c9a06ec24a
https://www.tech-tech.xyz/drawtext.html
https://icooon-mono.com/license/
https://stackoverflow.com/questions/1970807/center-middle-align-text-with-pil

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

Vue.js + JavaScript 双方向データバインディング

はじめに

初めましての方は初めまして。
豚野郎です。

最近Vue.jsを勉強し始めた為、投稿してみました。
始めて行きます。

双方向データバインディングとは何か?

双方向データバインディングのお話をしていきます。

難しい説明をしようとすると自分もわからないので、自分が理解している範囲だと
・データを更新すればUIが更新
・UIを更新すればデータが更新
以上の2点が特徴です。

例を出すと、下記のように入力したと同時に画面にも文字が出てきます。

スクリーンショット 2020-03-26 23.19.01.png

解説

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>双方向データバインディング</title>
</head>
<body>
    <!-- データからUIに反映 -->
    <div id="app">
        <p>{{ message }}</p>        
    <!-- UIからデータに反映 -->
    <p><input type="text" v-model="message"></p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="js/main.js"></script>
</body>
</html>

{{ message }}部分に入力された文字が表示されます。
テキストボックスでv-modelを使うと、
input要素やselect要素に入力した値を使うことができます。

次がJSです。

js/main.js
(function() {
    'use strict';

    new Vue({
        el: '#app',

        // モデルにデータを保持
        data: {
            message: ''
        }
    })
})();

Vueインスタンス(new Vue)を作成すればVue.jsは動きます。
elはelementsの略で、今回idを指定しました。
dataには先ほどv-modelで指定したmessageを書いていきます。

もし最初からデータを保持させたい場合(例.ばなな)、下記を追記します。

        // モデルにデータを保持
        data: {
            message: 'ばなな'
        }

スクリーンショット 2020-03-26 23.58.58.png

以上です。
ありがとうございました。

最後に

Markdownの書き方がわからなかった為、下記を参考にさせて頂きました。
https://qiita.com/shizuma/items/8616bbe3ebe8ab0b6ca1
感謝です。

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