20200216のvue.jsに関する記事は22件です。

Vue Composition API を利用した段階的なコアロジックからのモダン化

jQueryなどを利用したレガシーコードにVue.jsを導入してコードを書き換える際、雑に分類すると2つの方法があります。

  1. 気合いで一気にやる
  2. 段階的に書き換える

全体像が把握できている場合は 「1. 気合いで一気にやる」も全然アリですが、多くの場合はリスク軽減といった観点から「2. 段階的に書き換える」ための手段を模索することになります。

さて、Vue.js 3.x からは Composition API という関数ベースでの新しいAPIが導入されました。
これを利用すると、Vue.js 2.x 系までは主にVue.jsのコンポーネント内部で利用されていたリアクティブシステムについて、Vue.jsの外部でも利用可能となります。

また、CompositionAPIを利用する関数は、Vue.jsコンポーネント内部でも利用可能なので、「意味のある機能単位で関数として切り出す」→「テンプレート部をVue.js化していく」といった順番での書き換えも選択肢に入ります。

Vue Composition API を利用した書き換え(実例)

次のようなシンプルなTODOアプリの置き換えを例に考えてみます。
TODOを追加して件数を表示するだけのシンプルなものです。

index.html
<!DOCTYPE html>
<html>
  <body>
    <button id="addTodo">Add Todo</button>
    <div id="todoList"></div>
    <span id='todoCount'></span>
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
    <script src="./legacy.js"></script>
  </body> 
</html>
legacy.js
function addTodo() {
  var input = $('<input>');
  input.attr('type', 'text');

  var todo = $('<div>');
  todo.addClass('todo');
  todo.append(input);

  $('#todoList').append(todo);
}

function updateCount() {
  var count = $('.todo').length;
  $('#todoCount').text('Total: ' + count);
}

$(function() {
  $('#addTodo').on('click', function() {
    addTodo();
    updateCount();
  });

  updateCount();
});

ここからCompositionAPIによる切り出しを試してみましょう。
(ビルドの設定などに関する説明はこのエントリでは省略します)

TODOの管理・追加を useTodo.js というファイルに切り出すと次のようなイメージになります。

useTodo.js
import { reactive, computed, toRefs } from "@vue/composition-api";

export const useTodo = () => {
  const state = reactive({
    todoList: []
  });

  const count = computed(() => state.todoList.length);

  const addTodo = () => {
    state.todoList.push({ todo: "" });
  };

  return {
    ...toRefs(state),
    count,
    addTodo
  }
};

この関数を適切な箇所で呼び出すことで、レガシーコードと並行して状態管理を構築することができます。

legacy.js(useStore.jsを呼び出し)
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';
import { useTodo } from './useTodo';

Vue.use(VueCompositionApi); // Vue2.x系で利用する場合は必須

const todoState = useTodo(); // CompositionAPIを呼び出し

function addTodo() {
  var input = $('<input>');
  input.attr('type', 'text');

  var todo = $('<div>');
  todo.addClass('todo');
  todo.append(input);

  $('#todoList').append(todo);
}

function updateCount() {
  var count = $('.todo').length;
  $('#todoCount').text('Total: ' + count);
}

$(function() {
  $('#addTodo').on('click', function() {
    addTodo();
    updateCount();
    todoState.addTodo(); // 状態管理に書き込み
  });

  updateCount();
});

レガシーコードを動作させたまま、Vue.jsで必要となる状態(データ)のみを収集可能となりました。
こういったコードを増やしていき、状態管理が整った時点でVue.jsに一気に置き換えることで、見通しが良い状態で書き換えに着手することができます。

watchによるDOMからの段階的な切り離し

ここまでに説明した内容は、Vue Composition API 以外の方法でも実現可能です。

外部でVuex/Redux/MobXといった状態管理ライブラリで状態を集約しても良いですし、
Vue2.x系でも Vue.observable といったAPIを利用することでもほぼ同様のことが可能です。

しかし、Composition API を利用した書き換え時の利点として watch APIの存在が挙げられます。
watch APIは Composition API で生成されたリアクティブな値を監視し、変更時に任意の処理を実行できます。

これにより、従来はDOMに依存していた箇所を、少しずつデータを中心とする形に書き換えていけます。

legacy.js(データ依存での動作に変更)
import Vue from 'vue';
import VueCompositionApi, { watch } from '@vue/composition-api';
import { useTodo } from './useTodo';

Vue.use(VueCompositionApi);

const todoState = useTodo();

// 件数の変更をトリガーにDOMを更新
watch(todoState.count, (count) => {
  $('#todoCount').text('Total: ' + count);
});

// 件数の増分だけTODOを追加
watch(todoState.count, (count, prevCount = 0) => {
  [...Array(count - prevCount)].map(() => {
    var input = $('<input>');
    input.attr('type', 'text');

    var todo = $('<div>');
    todo.addClass('todo');
    todo.append(input);

    $('#todoList').append(todo);  
  });
});

$(function() {
  $('#addTodo').on('click', function() {
    todoState.addTodo();
  });
});

件数の表示やTODOの追加の処理が、データの変更をトリガーに動作するようになりました。

何が嬉しいのか

コアとなる処理のみを先に切り出せる

古き良きDOM依存コードは、カオスなDOM操作コードの影に隠れている 「アプリケーション全体はどういうデータを必要とするのか?」「結局何をやってるのか?」という点を洗い出すのに非常に苦労します。

今回の方法の場合、予めコアとなるロジック部をデータを含めて切り出すことができ、既存の挙動を維持したままコア部分のみテストを書き足して動作を保証しておく、といったことも可能となります。

DOM依存箇所を徐々に減らしていける

DOMに依存しているコードを削っていく場合、影響箇所を把握するのが一番骨の折れる作業になります。

watch の利用は人によっては抵抗がありそうですが、データを中心とした世界に徐々にシフトしていくことで、Vue.jsコンポーネントに切り出す際も、どういった単位で進めれば良いかの判断がしやすくなります。

切り出したものがVue.jsコンポーネント内部でそのまま使える

最終的にVue.jsへの置き換えを想定する場合、大きい恩恵になるのがコレだと予想されます。

Vue Composition APIを利用している関数は、当然ですが最終的にテンプレートごとVue.jsコンポーネント化した際にそのまま利用できます。事前にレガシーコード時に切り出したコアロジックが、そのまま持っていけるのです。

上記サンプルコードの場合、最終的に次のような形で利用可能です。

App.vue
<template>
  <div>
    <button @click="addTodo">Add Todo</button>
    <div>
      <div v-for="(todo, index) in todoList" :key="index">
        <input type="text" :value="todo.todo" @input="updateTodo(index, $event.target.value)" />
      </div>
    </div>
    <span>Total: {{count}}</span>
  </div>
</template>

<script>
import { createComponent } from "@vue/composition-api";
import { useTodo } from "./useTodo";

export default createComponent({
  name: 'App',
  setup() {
    return {
      ...useTodo() // 事前に作成したコアロジックを利用
    }
  }
});
</script>
useTodo.js(※updateTodoのみ新たに追加)
import { reactive, computed, toRefs } from "@vue/composition-api";

export const useTodo = () => {
  const state = reactive({
    todoList: []
  });
  const count = computed(() => state.todoList.length);

  const addTodo = () => {
    state.todoList.push({ todo: "" });
  };

  const updateTodo = (index, todo) => {
    state.todoList[index].todo = todo;
  }

  return {
    ...toRefs(state),
    count,
    addTodo,
    updateTodo
  }
};
app.js
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';
import App from "./App.vue";

Vue.use(VueCompositionApi);

new Vue({
  render: (createElement) => createElement(App)
}).$mount("#app");

テストを先に書いておけば、コアロジックは壊していないことを担保したままで、Vue.jsテンプレートとの連携だけを意識して書き換えていくことが可能となり、段階的な移行時には安心して作業を進めていけそうです。

まとめ

というわけで、Vue Composition APIを利用した段階的なモダン化の考察でした。

正直冗長な部分もあるので、どんなシチュエーションでも使える手法ではないとは思います。
(さっさとVue.jsでガッと書き換えた方が速いでしょ、と思う気持ちもありますし、そもそもゼロから全部書き換えたい気持ちになることも多そう)

ひとまず、手札のひとつとして持っておくと便利かもな〜と思いました。

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

【Vuejs】axiosを使ってみる

公式ドキュメントを参考に試してみました。

公式ドキュメント#基本的な例
https://jp.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BE%8B

スタブ

スタブを用意するのが面倒なので公式ドキュメントにも載っている以下を利用します。
https://api.coindesk.com/v1/bpi/currentprice.json
こんな感じのJSONが返ってきます。
Screenshot2020-02-16_21-31-40.png

作ったもの

sample.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test</title>
</head>

<body>
    <style>
        .bpi.header {
            background-color: beige;
        }
        .bpi.even {
            background-color: azure;
        }
        .bpi.odd {
            background-color: beige;
        }
    </style>

    <div id="app">
        <div id="rawdata">
            <strong>rawdata :</strong><br>{{coindesk}}
        </div>
        <br>
        <div id="update_time">
            <strong>update_time :</strong>{{coindesk.time.updated}}
        </div>
        <div id="desc">
            <strong>disclaimer :</strong>
            {{coindesk.disclaimer}}
        </div>
        <div id="chartName">
            <strong>chartName :</strong>{{coindesk.chartName}}
        </div>

        <table>
            <tr class="bpi header">
                <th>No.</th>
                <th>code</th>
                <th>symbol</th>
                <th>rate</th>
                <th>description</th>
                <th>rate_float</th>
            </tr>
            <tr v-for="(e,key,index) in coindesk.bpi" v-bind:class="[index % 2 == 0 ? 'even' : 'odd']" class="bpi">
                <td>{{index+1}}</td>
                <td>{{e.code}}</td>
                <td>{{decode(e.symbol)}}</td>
                <td>{{e.rate}}</td>
                <td>{{e.description}}</td>
                <td>{{e.rate_float}}</td>
            </tr>
        </table>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                coindesk: null,
            },
            mounted() {
                axios
                    .get('https://api.coindesk.com/v1/bpi/currentprice.json')
                    //.then(response => (console.log( response.data)))
                    .then(response => (this.coindesk = response.data))
            },
            methods: {
                decode: function (str) {
                    return str;// TODO デコード結果を返したい
                }
            }
        })
    </script>
</body>
</html>

実行結果

Screenshot2020-02-16_23-02-51.png

ポイント

  • 配列ではなくオブジェクトをv-forする場合はkey,indexの2つ必要だった。
  • 公式ドキュメントだとresponse => (this.info = response)となっているけどAPI叩いた時のレスポンスはresponse => (this.info = response.data)の中にある。
  • ライフサイクルはmountedでなくてもcreatedでも多分OK。

TODO

次はaxiosを使って非同期でデータ取得を試したい。

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

うんちメーカーを支える技術 ~ 状態遷移はうんちの夢を見るか?

うんちメーカーの誕生

「うんちメーカー」というブラウザゲームを作りました。

ホーム - うんちメーカー no unchi.png

うんちの長さを競い合うゲームで、オンラインのランキング機能があります。
使用した技術はDjango, Vueなどです。
今回はこのうんちメーカーで使っている技術について書いてみたいと思います。

主役はVue

フロントエンドのフレームワークはVueを使いました。
Vueはコンポーネント志向のフレームワークで、SFC(Single File Component)という単位でモジュールを定義できます。
例えば↓のようにです。

<script>
export default {
  data () {
    return {
    }
  }
}
</script>

<template>
  <div class="">Hello, World!</div>
</template>

<style lang="scss" scoped>
</style>

SFCはJavaScript, HTML, CSSを1つのファイルにまとめたコンポーネントで、これらを1つにまとめることで効率よく開発することが出来ます。
↓のようにコンポーネントを利用すると、属性にデータを渡すことができます。

<my-component :my-data="1" />

属性に渡されたデータはSFCではpropsというオブジェクト内の変数に共有されます。
この変数はたとえば↑のmy-dataの値を変更すると、その変更がリアルタイムでSFCにも反映されます。
このpropsの機能を使うことで、状態を持つコンポーネントを簡単に定義することが出来ます。

コンポーネントの状態遷移

ゲームは状態遷移のかたまりだと、今回の開発で思いました。
状態遷移とは、状態を定義した変数をひとつ用意して、その変数の値を次々に変化させて、モジュールなどの振る舞いを変える技術を指します。

たとえば↓のような変数があるとして、

const status = 'first'

この変数は↓のように参照されます。

switch (status) {
case 'first': /* TODO */ break
case 'running': /* TODO */ break 
case 'waiting': /* TODO */ break
}

switch文のcaseに状態に応じた処理を書くことで、状態遷移を実現させることが出来ます。
状態を持つ変数自体は、各状態の処理の中で変更していきます。

先ほどのコンポーネントのpropsにこのような状態を持たせることで、コンポーネントに状態遷移を行わせることが出来ます。
例えば↓のようにです。

<my-component :status="myComponentStatus" />

コンポーネント内では、setIntervalでループを回し、この状態を監視させます。

mounted () {
  this.iid = setInterval(this.update, 16.66)
},

methods: {
  update () {
    switch (this.status) {
    case 'first': /* TODO */ break
    case 'running': /* TODO */ break
    case 'waiting': /* TODO */ break
    }
  },
},

うんちメーカーのオブジェクトは複雑な状態を持っていますが、基本的にはこのような状態遷移で動作しています。

ゲーム開発における状態遷移の有用性

私は状態遷移は文字列のパースなどで学んだのですが、今回の開発でゲームにも応用できることがわかりました。
ゲームは複雑な状態を持っていますが、それらを一度に処理しようとすると高い確率で肛門がパンクします。
しかし、状態という単位にゲーム全体の振る舞いを分割統治することで開発が容易になります。

ゲーム開発における状態遷移の有用性は確かなもので、これを知っていると知らないとでは作れるものが異なってくると思いました。
知らないとクソゲーを作ることになってしまいますが、知っていればうんちメーカーのようなクソゲーを作ることが出来ます。

デザインパターンへの応用

状態を管理するデザインパターンに有名なGoFのStateパターンがありますが、今回はこれは使いませんでした。

うんちメーカーはかなり規模の小さいアプリだったので利用する必要もなかったのですが、規模の大きさによってはこれらのデザインパターンの利用を検証する必要があるかもしれません。

おわりに

ストレスの多い世の中ですが、うんちメーカーでいっぱい出してすっきりしましょう。

以上です。

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

Vue.js初心者がチャットアプリケーションの開発を通じて勉強した話

はじめに

Vue.js初心者がチャットアプリケーションを作りながら勉強した話です。
題材はBuild a Real-time Chat App with Pusher and Vue.js(*1)です。(ありがとうございます)

このアプリケーション全てを理解するのは初心者には大変だったので
細かい部分の理解は置いておき、全体の理解に努めたつもりです。

完成品はこちら:hugging:
image.png
↓ログインするとチャット画面に遷移
image.png

ちなみに私のスペックは

  • Vue.jsの知識は全くない
  • 開発経験はある(Reactとか)

という感じです。

(*1) 一部日本語訳と私のコメントですm(_ _)m

手順

では、本題です。

製作物の確認(1/9)

Slackのようなチャットアプリケーションを作ります。
要件は

  • 複数の部屋がある
  • ルームメンバーをリストし、ログイン状態を表示する
  • 他のユーザが入力開始するタイミングを検出し表示する

です。(本格的ですね:flushed:
バックエンドはChatKitというサービスを利用し、フロントエンドをVue.jsで実装します。

事前準備として、Vue CLIをグローバルにインストールしておきます。
(Nodeがマシンにインストールされている必要があります)

$ npm install -g @vue/cli

バックエンドの準備(2/9)

PusherのChatKitというサービスを利用するので、まずは右上からサインアップします。
(Githubでサインアップしてエラーが出た場合、 パスワードのリセットで解消する可能性があります)

サインインするとポップアップが出ますが、一旦スキップでも大丈夫です。
image.png

「CHATKIT」を選び、「CREATE」を押します。
image.png
INSTANCE NAMEは VueChatTut でOKです。

インスタンスができたら「Console」タブの「CREATE USER」からユーザを作成します。
image.png

"John"(User Identifier)と“John Wick"(Display Name)とします。
同じ要領で

  • salt, Evelyn Salt
  • hunt, Ethan Hunt

というユーザも作りましょう。

続いて部屋を作り、ユーザを割り当てます。
「Create and join a room」をクリックし、"John Wick"(Select a user to create the room)を選択、"General"(Room Name)と記入し「CREATE ROOM」をクリックします。

image.png

部屋ができたら「Add user to room」でsaltとhuntを追加します。
同様に

  • Weapons (john, salt)
  • Combat (john, hunt)

という部屋も作り、メンバーを追加しましょう。

次に、「Add message to room」からテストメッセージを送っておきます。例えば、"General"で"John Wick"(Select a message author)を選択、"test"(Messge)と記入し「CREATE MESSAGE」をクリックします。

プロジェクトの作成(3/9)

Vue CLIでプロジェクトを作ります。何個か質問されますが、全てEnterしました。

$ vue create vue-chatkit

不要なファイルの削除と、今回使うファイルの作成を行います。

$ mkdir src/assets/css
$ mkdir src/store
$ mkdir src/views

$ touch src/assets/css/{loading.css,loading-btn.css}
# ↑はhttps://github.com/sitepoint-editors/vue-chatkit/tree/master/src/assets/cssから内容をコピペしておきます

$ touch src/components/{ChatNavBar.vue,LoginForm.vue,MessageForm.vue,MessageList.vue,RoomList.vue,UserList.vue}
$ touch src/store/{actions.js,index.js,mutations.js}
$ touch src/views/{ChatDashboard.vue,Login.vue}
$ touch src/{chatkit.js,router.js}

$ rm src/components/HelloWorld.vue

srcディレクトリ以下はこんな感じになります。

$ tree -L 2 --matchdirs src
src
├── App.vue ←大元のビュー
├── assets ←CSSや画像
│   ├── css
│   └── logo.png
├── chatkit.js ←ChatKitと接続する
├── components ←コンポーネント。コンポーネントを集めてビューを作る
│   ├── ChatNavBar.vue
│   ├── LoginForm.vue
│   ├── MessageForm.vue
│   ├── MessageList.vue
│   ├── RoomList.vue
│   └── UserList.vue
├── main.js ←Vueアプリケーションを起動する
├── router.js ←ルーティング
├── store ←状態管理
│   ├── actions.js
│   ├── index.js
│   └── mutations.js
└── views ←ビュー
    ├── ChatDashboard.vue
    └── Login.vue

5 directories, 16 files

次に、依存関係をインストールします。

$ npm i @pusher/chatkit-client bootstrap-vue moment vue-chat-scroll vuex-persist vue-router vuex
  • @pusher/chatkit-client、ChatKitのリアルタイムクライアントインターフェイス
  • bootstrap-vue、CSSフレームワーク
  • moment、日付と時刻のフォーマットユーティリティ
  • vue-chat-scroll、新しいコンテンツが追加されると自動的に下にスクロールする
  • vuex、Vue.jsアプリケーションのための状態管理ライブラリ。Reactで言うReduxのイメージ
  • vuex-persist、ブラウザのローカルストレージにVuexの状態を保存する
  • vue-router、Vue.jsのルータ

では、Vue.jsプロジェクトの設定をしましょう。src/main.jsを開き、以下のように更新します。

src/main.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import VueChatScroll from 'vue-chat-scroll'

import App from './App.vue'
import router from './router'
import store from './store/index'

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import './assets/css/loading.css'
import './assets/css/loading-btn.css'

Vue.config.productionTip = false // trueにすると開発者向けメッセージがコンソールに出る
Vue.use(BootstrapVue) // ライブラリを利用する宣言
Vue.use(VueChatScroll)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app') // Vueインスタンスを作成し、#appにマウント(idがappであるDOMを置換)
// 詳しく知りたい方はネットで調べてみてください。私のような初心者の内はおまじないという認識で良さそう

src/router.jsを以下のように更新します。

src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/Login.vue'
import ChatDashboard from './views/ChatDashboard.vue'

Vue.use(Router)

export default new Router({
  mode: 'history', // ページのリロードなしにURL遷移を実現する(SPAのためという理解)
  base: process.env.BASE_URL,
  routes: [
// 「/」というパスのルートをLoginコンポーネントにマップする
    {
      path: '/',
      name: 'login',
      component: Login
    },
    {
      path: '/chat',
      name: 'chat',
      component: ChatDashboard,
    }
  ]
})

src/store/index.jsを更新します。

src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import mutations from './mutations'
import actions from './actions'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

const vuexLocal = new VuexPersistence({
  storage: window.localStorage
})

export default new Vuex.Store({
  state: {
  }, // ストアの状態
  mutations, // ストアの状態を変更するメソッド群(ミューテーションをコミットすることで変更できる)
  actions, // ミューテーションをコミットするメソッド群
  getters: {
  }, // ストアの状態を加工して取得するメソッド群
  plugins: [vuexLocal.plugin], // LocalStorageを使うよ
  strict: debug // 開発時に状態変更をデバッキングツールで追跡できるようにする(ChromeかFireFoxの方は「Vue.js devtools」という拡張機能が便利)
})

UIインターフェイスの構築(4/9)

ここからちょっとハードになって行きますよ〜まずはsrc/App.vueを更新します。

src/App.vue
<template>
  <div id="app">
    <router-view/> // 先ほどのrouter.jsによってマッチしたコンポーネントが描画される
  </div>
</template>

次に、src/store/index.jsのstateとgettersセクションを更新します。
loadingで「CSSローダーを実行する必要があるか」やerrorで「エラー情報」を管理していますが、一個一個分かっていなくてOKです。

// ...
state: {
  loading: false,
  sending: false,
  error: null,
  user: [],
  reconnect: false,
  activeRoom: null,
  rooms: [],
  users: [],
  messages: [],
  userTyping: null
},
getters: {
  hasError: state => state.error ? true : false
},
// ...

ログイン画面src/view/Loing.vueを作ります。構成はこんな感じで、フォームがコンポーネントとなっています。
image.png

src/view/Loing.vue
<template> // テンプレートはtemplateタグで囲む
  <div class="login">
// b-xxはbootstrapのコンポーネント
    <b-jumbotron  header="Vue.js Chat"
                  lead="Powered by Chatkit SDK and Bootstrap-Vue"
                  bg-variant="info"
                  text-variant="white">
      <p>For more information visit website</p>
      <b-btn target="_blank" href="https://pusher.com/chatkit">More Info</b-btn>
    </b-jumbotron>
    <b-container>
      <b-row>
        <b-col lg="4" md="3"></b-col>
        <b-col lg="4" md="6">
          <LoginForm />
        </b-col>
        <b-col lg="4" md="3"></b-col>
      </b-row>
    </b-container>
  </div>
</template>

<script>
import LoginForm from '@/components/LoginForm.vue'

export default {
  name: 'login',
  components: {
// Loginのテンプレート内でLoginFormを使えるようにする
    LoginForm
  }
}
</script>

ログイン画面で利用するコンポーネントsrc/view/LoginForm.vueを更新します。

src/view/LoginForm.vue
<template>
  <div class="login-form">
    <h5 class="text-center">Chat Login</h5>
    <hr>
// ↓preventDefaultを実行し、onSubmitメソッドを呼ぶ
    <b-form @submit.prevent="onSubmit">
 // ↓showの頭にコロンをつけているので、値としてscriptタグ内のhasErrorという算出プロパティを使える。また、変数を表示する場合は{{}}で囲う
      <b-alert variant="danger" :show="hasError">{{ error }} </b-alert>

      <b-form-group id="userInputGroup"
                    label="User Name"
                    label-for="userInput">
        <b-form-input id="userInput"
                      type="text"
                      placeholder="Enter user name"
// ↓双方向データバインディングを作成する。入力に応じてuserIdが更新される
                      v-model="userId"
                      autocomplete="off"
                      :disabled="loading"
                      required>
        </b-form-input>
      </b-form-group>

      <b-button type="submit"
                variant="primary"
                class="ld-ext-right"
                v-bind:class="{ running: loading }" // v-bind:classでクラスを動的に切り替えられる。loadingがtrueのときrunningクラスをつける
                :disabled="isValid">
                Login <div class="ld ld-ring ld-spin"></div>
      </b-button>
    </b-form>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  name: 'login-form',
  data() { // データ定義
    return {
      userId: '',
    }
  },
  computed: { // 算出プロパティ。テンプレートからロジックを切り出せる
    isValid: function() {
      const result = this.userId.length < 3;
      return result ? result : this.loading
    },
    ...mapState([
      'loading',
      'error'
    ]), // stateを返す
    ...mapGetters([
      'hasError'
    ]) // getterの評価後の値を返す
  }
}
</script>

ここまで書くと表示確認ができます:blush:
npm run serve でVue devサーバを起動し、http://localhost:8080 を開いてみましょう。

image.png

続いて、チャット画面src/view/ChatDashBoard.vueを作ります。
チャット画面は

  • ChatNavBar、ナビゲーションバー
  • RoomList、ログインしたユーザがアクセスできる部屋の一覧表示
  • UserList、選択したルームのメンバーをリスト
  • MessageList、選択したルームに投稿されたメッセージをリスト
  • MessageForm、選択したルームにメッセージを送信するためのフォーム

の5つのコンポーネントで構成されています。
image.png

src/view/ChatDashBoard.vue
<template>
  <div class="chat-dashboard">
    <ChatNavBar />
    <b-container fluid class="ld-over" v-bind:class="{ running: loading }">
      <div class="ld ld-ring ld-spin"></div>
      <b-row>
        <b-col cols="2">
          <RoomList />
        </b-col>

        <b-col cols="8">
          <b-row>
            <b-col id="chat-content">
              <MessageList />
            </b-col>
          </b-row>
          <b-row>
            <b-col>
              <MessageForm />
            </b-col>
          </b-row>
        </b-col>

        <b-col cols="2">
          <UserList />
        </b-col>
      </b-row>
    </b-container>
  </div>
</template>

<script>
import ChatNavBar from '@/components/ChatNavBar.vue'
import RoomList from '@/components/RoomList.vue'
import MessageList from '@/components/MessageList.vue'
import MessageForm from '@/components/MessageForm.vue'
import UserList from '@/components/UserList.vue'
import { mapState } from 'vuex';

export default {
  name: 'Chat',
  components: {
    ChatNavBar,
    RoomList,
    UserList,
    MessageList,
    MessageForm
  },
  computed: {
    ...mapState([
      'loading'
    ])
  }
}
</script>

全てのコンポーネントを表示するため、ボイラープレートコードを挿入します。
ここはコピペで大丈夫です。

src/view/ChatNavBar.vue
<template>
  <b-navbar id="chat-navbar" toggleable="md" type="dark" variant="info">
    <b-navbar-brand href="#">
      Vue Chat
    </b-navbar-brand>
    <b-navbar-nav class="ml-auto">
      <b-nav-text>{{ user.name }} | </b-nav-text>
      <b-nav-item href="#" active>Logout</b-nav-item>
    </b-navbar-nav>
  </b-navbar>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'ChatNavBar',
  computed: {
    ...mapState([
      'user',
    ])
  },
}
</script>

<style>
  #chat-navbar {
    margin-bottom: 15px;
  }
</style>
src/components/RoomList.vue
<template>
  <div class="room-list">
    <h4>Channels</h4>
    <hr>
    <b-list-group v-if="activeRoom">
      <b-list-group-item v-for="room in rooms"
                        :key="room.name"
                        :active="activeRoom.id === room.id"
                        href="#"
                        @click="onChange(room)">
        # {{ room.name }}
      </b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'RoomList',
  computed: {
    ...mapState([
      'rooms',
      'activeRoom'
    ]),
  }
}
</script>
src/components/UserList.vue
<template>
  <div class="user-list">
    <h4>Members</h4>
    <hr>
    <b-list-group>
      <b-list-group-item v-for="user in users" :key="user.username">
        {{ user.name }}
        <b-badge v-if="user.presence"
        :variant="statusColor(user.presence)"
        pill>
        {{ user.presence }}</b-badge>
      </b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'user-list',
  computed: {
    ...mapState([
      'loading',
      'users'
    ])
  },
  methods: {
    statusColor(status) {
      return status === 'online' ? 'success' : 'warning'
    }
  }
}
</script>
src/components/MessageList.vue
<template>
  <div class="message-list">
    <h4>Messages</h4>
    <hr>
    <div id="chat-messages" class="message-group" v-chat-scroll="{smooth: true}">
      <div class="message" v-for="(message, index) in messages" :key="index">
        <div class="clearfix">
          <h4 class="message-title">{{ message.name }}</h4>
          <small class="text-muted float-right">@{{ message.username }}</small>
        </div>
        <p class="message-text">
          {{ message.text }}
        </p>
        <div class="clearfix">
          <small class="text-muted float-right">{{ message.date }}</small>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'message-list',
  computed: {
    ...mapState([
      'messages',
    ])
  }
}
</script>

<style>
.message-list {
  margin-bottom: 15px;
  padding-right: 15px;
}
.message-group {
  height: 65vh !important;
  overflow-y: scroll;
}
.message {
  border: 1px solid lightblue;
  border-radius: 4px;
  padding: 10px;
  margin-bottom: 15px;
}
.message-title {
  font-size: 1rem;
  display:inline;
}
.message-text {
  color: gray;
  margin-bottom: 0;
}
.user-typing {
  height: 1rem;
}
</style>
src/components/MessageForm.vue
<template>
  <div class="message-form ld-over">
    <small class="text-muted">@{{ user.username }}</small>
    <b-form @submit.prevent="onSubmit" class="ld-over" v-bind:class="{ running: sending }">
      <div class="ld ld-ring ld-spin"></div>
      <b-alert variant="danger" :show="hasError">{{ error }} </b-alert>
      <b-form-group>
        <b-form-input id="message-input"
                      type="text"
                      v-model="message"
                      placeholder="Enter Message"
                      autocomplete="off"
                      required>
        </b-form-input>
      </b-form-group>
      <div class="clearfix">
        <b-button type="submit" variant="primary" class="float-right">
          Send
        </b-button>
      </div>
    </b-form>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'

export default {
  name: 'message-form',
  data() {
    return {
      message: ''
    }
  },
  computed: {
    ...mapState([
      'user',
      'sending',
      'error',
      'activeRoom'
    ]),
    ...mapGetters([
      'hasError'
    ])
  }
}
</script>

かなりそれっぽくなりましたね。
image.png

データがない状態なので、stateにモックデータを入れてみます。

src/store/index.js
// ...
state: {
  loading: false,
  sending: false,
  error: 'Relax! This is just a drill error message',
  user: {
    username: 'Jack',
    name: 'Jack Sparrow'
  },
  reconnect: false,
  activeRoom: {
    id: '124'
  },
  rooms: [
    {
      id: '123',
      name: 'Ships'
    },
    {
      id: '124',
      name: 'Treasure'
    }
  ],
  users: [
    {
      username: 'Jack',
      name: 'Jack Sparrow',
      presence: 'online'
    },
    {
      username: 'Barbossa',
      name: 'Hector Barbossa',
      presence: 'offline'
    }
  ],
  messages: [
    {
      username: 'Jack',
      date: '11/12/1644',
      text: 'Not all treasure is silver and gold mate'
    },
    {
      username: 'Jack',
      date: '12/12/1644',
      text: 'If you were waiting for the opportune moment, that was it'
    },
    {
      username: 'Hector',
      date: '12/12/1644',
      text: 'You know Jack, I thought I had you figured out'
    }
  ],
  userTyping: null
},
// ...

いい感じです。
image.png

stateは元に戻しておきます。

/src/store/index.js
// ...
state: {
  loading: false,
  sending: false,
  error: null,
  user: null,
  reconnect: false,
  activeRoom: null,
  rooms: [],
  users: [],
  messages: [],
  userTyping: null
}
// ...

パスワードレス認証(5/9) ※公開するサービスでは適切で安全な認証システムにしましょう

プロジェクトのルートに以下のファイルを作成し、環境変数を設定します。

.env.local
VUE_APP_INSTANCE_LOCATOR=
VUE_APP_TOKEN_URL=
VUE_APP_MESSAGE_LIMIT=10

ChatKitの「Credentials」タブに移動し、「TEST TOKEN PROVIDER」の「ENABLED?」にチェックをつけます。
そして、
VUE_APP_INSTANCE_LOCATORに「Instance Locator」
VUE_APP_TOKEN_URLに「Your Test Token Provider Endpoint」
の値を書きます。

VUE_APP_MESSAGE_LIMITは取得するメッセージの数を制限しているだけです。

次に、src/chatkit.jsに行き、ChatKitへ接続する土台を作ります。

src/chatkit.js
import { ChatManager, TokenProvider } from '@pusher/chatkit-client'

const INSTANCE_LOCATOR = process.env.VUE_APP_INSTANCE_LOCATOR;
const TOKEN_URL = process.env.VUE_APP_TOKEN_URL;
const MESSAGE_LIMIT = Number(process.env.VUE_APP_MESSAGE_LIMIT) || 10;

let currentUser = null;
let activeRoom = null;

async function connectUser(userId) {
  const chatManager = new ChatManager({
    instanceLocator: INSTANCE_LOCATOR,
    tokenProvider: new TokenProvider({ url: TOKEN_URL }),
    userId
  });
  currentUser = await chatManager.connect();
  return currentUser;
}

export default {
  connectUser
}

続いて、stateにデータをセットする処理を追加します。
長いですが、ただのセッターなのでがっつり見る必要はありません。

src/store/mutations
export default {
  setError(state, error) {
    state.error = error;
  },
  setLoading(state, loading) {
    state.loading = loading;
  },
  setUser(state, user) {
    state.user = user;
  },
  setReconnect(state, reconnect) {
    state.reconnect = reconnect;
  },
  setActiveRoom(state, roomId) {
    state.activeRoom = roomId;
  },
  setRooms(state, rooms) {
    state.rooms = rooms
  },
  setUsers(state, users) {
    state.users = users
  },
 clearChatRoom(state) {
    state.users = [];
    state.messages = [];
  },
  setMessages(state, messages) {
    state.messages = messages
  },
  addMessage(state, message) {
    state.messages.push(message)
  },
  setSending(state, status) {
    state.sending = status
  },
  setUserTyping(state, userId) {
    state.userTyping = userId
  },
  reset(state) {
    state.error = null;
    state.users = [];
    state.messages = [];
    state.rooms = [];
    state.user = null
  }
}

次に、src/store/actions.jsを更新します。

src/store/actions.js
import chatkit from '../chatkit';

// Helper function for displaying error messages
function handleError(commit, error) {
  const message = error.message || error.info.error_description;
  commit('setError', message);
}

export default {
// ChatKitに接続し、stateを更新する
  async login({ commit, state }, userId) {
    try {
      commit('setError', '');
      commit('setLoading', true);
      // Connect user to ChatKit service
      const currentUser = await chatkit.connectUser(userId);
      commit('setUser', {
        username: currentUser.id,
        name: currentUser.name
      });
      commit('setReconnect', false);

      // Test state.user
      console.log(state.user);
    } catch (error) {
      handleError(commit, error)
    } finally {
      commit('setLoading', false);
    }
  }
}

今、作ったメソッドをsrc/components/LoginForm.vueから実行します。

src/components/LoginForm.vue
import { mapState, mapGetters, mapActions } from 'vuex'

//...
export default {
  //...
  methods: {
    ...mapActions([
      'login'
    ]),
    async onSubmit() {
      const result = await this.login(this.userId);
      if(result) {
// ログインできたらチャット画面に遷移する。loginメソッドがbooleanを返していないからまだ動かない
        this.$router.push('chat');
      }
    }
  }
}

.env.localをロードするために、Vueサーバを再起動します。
間違ったユーザ名を入力するとエラーになることが確認できます。

image.png

チャットに参加する(6/9)

チャット入室時にRoomListUserListが反映されるようsrc/chatkit.jsを更新します。

src/chatkit.js
//...
import moment from 'moment'
import store from './store/index'

//...
function setMembers() {
  const members = activeRoom.users.map(user => ({
    username: user.id,
    name: user.name,
    presence: user.presence.state
  }));
  store.commit('setUsers', members);
}

async function subscribeToRoom(roomId) {
  store.commit('clearChatRoom');
  activeRoom = await currentUser.subscribeToRoom({
    roomId,
    messageLimit: MESSAGE_LIMIT,
    hooks: {
      onMessage: message => {
        store.commit('addMessage', {
          name: message.sender.name,
          username: message.senderId,
          text: message.text,
          date: moment(message.createdAt).format('h:mm:ss a D-MM-YYYY')
        });
      },
      onPresenceChanged: () => {
        setMembers();
      },
      onUserStartedTyping: user => {
        store.commit('setUserTyping', user.id)
      },
      onUserStoppedTyping: () => {
        store.commit('setUserTyping', null)
      }
    }
  });
  setMembers();
  return activeRoom;
}

export default {
  connectUser,
  subscribeToRoom
}

ChatKitサービスのイベントハンドラの意味はこちらです。

  • onMessage、メッセージを受信する
  • onPresenceChanged、ユーザがログインまたはログアウトした時にイベントを受け取る
  • onUserStartedTyping、ユーザが入力しているイベントを受け取る
  • onUserStopperTyping、ユーザが入力を停止したというイベントを受け取る

ログイン後、チャット画面にリダイレクトするよう更新します。

src/store/actions.js
//...
try {
  //... (place right after the `setUser` commit statement)
  // Save list of user's rooms in store
  const rooms = currentUser.rooms.map(room => ({
    id: room.id,
    name: room.name
  }))
  commit('setRooms', rooms);

  // Subscribe user to a room
  const activeRoom = state.activeRoom || rooms[0]; // pick last used room, or the first one
  commit('setActiveRoom', {
    id: activeRoom.id,
    name: activeRoom.name
  });
  await chatkit.subscribeToRoom(activeRoom.id);

  return true;
} catch (error) {
  //...
}

これで正しいユーザ名でログインした時チャット画面に遷移するようになりました。

部屋を変更する(7/9)

RoomListのクリックで部屋を変更できるようにします。
まず、src/store/actions.jsloginメソッドの後にこちらを追加します。
stateのactiveRoomを更新するものです。

src/store/actions.js
async changeRoom({ commit }, roomId) {
  try {
    const { id, name } = await chatkit.subscribeToRoom(roomId);
    commit('setActiveRoom', { id, name });
  } catch (error) {
    handleError(commit, error)
  }
},

次に、src/componenents/RoomList.vueのscriptタグ内に以下を追加します。
すでにルーム名をクリックするとonChangeを呼び出す実装はされている@click="onChange(room)"ので、先ほど作ったchangeRoomonChangeメソッドから実行します。

src/componenents/RoomList.vue
import { mapState, mapActions } from 'vuex'
//...
export default {
  //...
  methods: {
    ...mapActions([
      'changeRoom'
    ]),
    onChange(room) {
      this.changeRoom(room.id)
    }
  }
}

ブラウザで部屋を切り替えると、MessageListUserListが更新されることが確認できます:ok_woman:

もしCannot read property 'subscribeToRoom' of nullというエラーが出たらログインし直してみてください。
次のセクションで対応します。

ページの更新後のユーザの再接続(8/9)

前セクションでご紹介したエラーはページをリロードした際に、ChatKitサーバに接続する参照がnullにリセットされるために起こるものです。
これを修正するには再接続操作を実行する必要があります。

src/components/ChatNavBar.vueのscriptタグ内を以下のように更新します。

src/components/ChatNavBar.vue
<script>
import { mapState, mapActions, mapMutations } from 'vuex'

export default {
  name: 'ChatNavBar',
  computed: {
    ...mapState([
      'user',
      'reconnect'
    ])
  },
  methods: {
    ...mapActions([
      'logout',
      'login'
    ]),
    ...mapMutations([
      'setReconnect'
    ]),
    onLogout() {
      this.$router.push({ path: '/' });
      this.logout();
    },
    unload() {
      if(this.user.username) { // User hasn't logged out
        this.setReconnect(true);
      }
    }
  },
  mounted() {
    window.addEventListener('beforeunload', this.unload);
    if(this.reconnect) {
      this.login(this.user.username);
    }
  }
}
</script>
  1. upload。ページの更新が発生すると呼び出される。user.usernameが設定されている=ログインしていると、stateのreconnectにtrueを設定する。
  2. mounted。ChatNavBarビューのレンダリングが完了するたびに呼び出される。最初に、ページがアンロードされる直前に呼び出されるイベントリスナーにハンドラーを割り当てる。また、stateのreconnectがtrueであれば、ログイン手順が実行され、ChatKitサービスに再接続される。

また、ログアウト機能も追加されていますが、これは次のセクションで。

これらの更新を行った後、ページをリロードして部屋を切り替えてもエラーにならないはずです:ok_woman:

メッセージの送信、ユーザの入力の検出、ログアウト(9/9)

ラストです。src/chatkit.jsを更新します。

src/chatkit.js
//...
async function sendMessage(text) {
  const messageId = await currentUser.sendMessage({
    text,
    roomId: activeRoom.id
  });
  return messageId;
}

export function isTyping(roomId) {
  currentUser.isTypingIn({ roomId });
}

function disconnectUser() {
  currentUser.disconnect();
}

export default {
  connectUser,
  subscribeToRoom,
  sendMessage,
  disconnectUser
}

src/atore/actions.jsに移動し、changeRoomメソッドの直後に以下を挿入します。

src/atore/actions.js
async sendMessage({ commit }, message) {
  try {
    commit('setError', '');
    commit('setSending', true);
    const messageId = await chatkit.sendMessage(message);
    return messageId;
  } catch (error) {
    handleError(commit, error)
  } finally {
    commit('setSending', false);
  }
},
async logout({ commit }) {
  commit('reset');
  chatkit.disconnectUser();
  window.localStorage.clear();
}

logoutメソッドでは、セキュリティのためストアのリセットとローカルストレージのクリアも行います。

次に、src/components/MessageForm.vueのinputディレクティブを更新します。

src/components/MessageForm.vue
<b-form-input id="message-input"
              type="text"
              v-model="message"
              @input="isTyping"
              placeholder="Enter Message"
              autocomplete="off"
              required>
</b-form-input>

src/components/MessageForm.vueのscriptタグ内を以下の通り更新します。

src/components/MessageForm.vue
<script>
import { mapActions, mapState, mapGetters } from 'vuex'
import { isTyping } from '../chatkit.js'

export default {
  name: 'message-form',
  data() {
    return {
      message: ''
    }
  },
  computed: {
    ...mapState([
      'user',
      'sending',
      'error',
      'activeRoom'
    ]),
    ...mapGetters([
      'hasError'
    ])
  },
  methods: {
    ...mapActions([
      'sendMessage',
    ]),
    async onSubmit() {
      const result = await this.sendMessage(this.message);
      if(result) {
        this.message = '';
      }
    },
     async isTyping() {
      await isTyping(this.activeRoom.id);
    }
  }
}
</script>

そして、src/MessageList.vueを更新します。

src/MessageList.vue
import { mapState } from 'vuex'

export default {
  name: 'message-list',
  computed: {
    ...mapState([
      'messages',
      'userTyping'
    ])
  }
}

メッセージ送信機能が動作するはずです。

別のユーザが入力しているという通知を表示するには、src/MessageList.vuemessage-group直後にこのスニペットを追加します。

src/MessageList.vue
<div class="user-typing">
  <small class="text-muted" v-if="userTyping">@{{ userTyping }} is typing....</small>
</div>

最後に、src/components/ChatNavBar.vueにログアウト処理をつけます。

src/components/ChatNavBar.vue
 <b-nav-item href="#" @click="onLogout" active>Logout</b-nav-item>

完成です٩( 'ω' )و

まとめ

知識ない状態でアプリケーションをイチから作るのは(記事があるとは言え)不安もありましたが、この辺が良かったのでまたやりたいと思います:v:

  • ドキュメントをただ読むより楽しい!
  • ファイル構成や、ライフサイクル、機能といったVue.jsの特徴を知ることができた
  • チャットのライブラリやUIライブラリなど実用的なツールを知ることができた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsでTodoアプリ作ってみよう!

今回はVue.jsでTodoアプリを作ってみます
早速やっていきましょう~!
なお超基礎的な知識として下記の記事に書いてることを前提でお話します!
https://qiita.com/takepon_it/items/f89e0e3023a3070dbce6

1.画面を作ろう

今回はTodoリストの機能を作りたいだけなので見た目は簡素な物でいきます!
https://unpkg.com/vue
を読み込むのを忘れないでください!

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Todoアプリ</title>
        <link rel="stylesheet" href="style.css">
        <script src="https://unpkg.com/vue"></script>

    </head>
    <body>
        <div class="container">
            <h1>Todoアプリ</h1>
        </div>
        <div id="app">
            <div>
                <input type="text" placeholder="テキスト入力してね">
                <button type="button">追加</button>
                <ul>
                </ul>
            </div>
        </div>
        <script src="main.js"></script>
    </body>
</html>

image.png

こんな感じですね!

2.コンポーネントを作ろう!

早速タスクを追加する機能を追加していきましょう!

まずコンポーネントを定義していきます
コンポーネントはVue.componentを使うことで定義することできます!

ToDo の要素を todo-item というコンポーネントとして定義してみましょう!
それと合わせて既にここでタスクを追加した際のhtmlも作っておきましょう!
テンプレートをtemplateオプションに渡せばできます!

main.js
Vue.component('todo-item', {
    props: {
        todo: {
            type: Object,
            required: true
        }
    },
    //htmlの挿入
    template: '<div>' +
        '<input type="checkbox" v-model="todo.completed">' +
        '<span>{{todo.text}}</span>' +
        '</div>'
});

取り合えずこれでtodo-itemというコンポーネントを作れました!

3.タスクを保持するデータを作ろう!

次にテキストボックスの値をinputという名前で保存しよう!
では今からdatainputを入れていくよ~

main.js
var vm = new Vue({
    el: '#app',
    data: {
        input: '',
        //タスクを管理する配列
        todos: []
    },

タスクを管理する配列として todos:[] も作っとこう!

4.テキストとデータをくっつけよう!

早速テキストボックスとinputv-modelを使ってくっつけよう!

v-modelはinput要素にv-modelという属性をつけることで、データとinput要素の入力値をくっつけることができます!

追加ボタンの上のテキストボックスの所を

index.html
<input type="text" v-model="input" placeholder="テキスト入力してね">

にしてみましょう! 早速次にいきましょう!

5.タスクを追加するインスタンスメソッドを作ろう!

本題のTodoを追加するための機能を作っていきましょう!
addTodoというメソッドを作ろう!
インスタンスメソッドを作るにはmethodsオプションを使用したらできるよ

main.js
 methods: {
        //Todo追加関数
        addTodo: function () {
            this.todos.push(
                {
                    completed: false,
                    text: this.input
                })
            this.input = ''
        }
    }

これを追加してください!
配列へのpushやそういったものは割愛させて頂きます

6.タスクを追加する処理を書いていこう!

そしたらボタンをクリックしたらタスクを追加できるようにしたいので
ボタンとメソッドを紐づける必要があります!
これをするには
v-on:click
これを使って、ボタンが押されるのを監視し、ボタンが押された時に先ほどのaddTodoメソッドを実行できるようにしよう!

index.html
<button type="button" v-on:click="addTodo">追加</button>

こうですね!

7.タスクを削除する機能を作ろう!

タスクを追加したら、それを遂行した時の為に当然削除っていう機能も欲しいですよね?
こちらを今から実装していきたいと思います!

冒頭で作成したtempleteオプションに続けて記述したいと思います

main.js
 //htmlの挿入
    template: '<div>' +
        '<input type="checkbox" v-model="todo.completed">' +
        '<span>{{todo.text}}</span>' +
        '<button type="button" v-on:click="onclickRemove">削除</button>' +
        '</div>',

最終的にはこうですね!
しかしこれだけではなv-onオプションを使っている所を見て頂きたいのですが
onclickRemoveとあります
実際に削除ボタンを押されたときの処理をまだ書いていないので今から書いていきましょう。

main.js
    methods: {
        onclickRemove: function () {
            this.$emit('remove')
        }
    }

こちらを始めのインスタンスメソッドに追記します
$emitでカスタムイベントのremoveを発動します!
そして最後に

index.html
 <li v-for="(todo, index) in todos">
 <todo-item v-bind:todo="todo" v-on:remove="todos.splice(index,1)"></todo-item>
</li>

ul要素の中に上記を追記してください
v-forで要素を繰り返し表示します、この場合はtodos配列の中に格納されているタスクを表示させる為ですね
v-bindでデータを属性の値に紐付けします。

以上をもってTodoアプリの完成です!!!!!!

8.最後に

プログラミング初心者の割には頑張れた気がします
この調子でQiitaとか使ってアウトプットしていきたいな・・・

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

Vue.js:アロー演算子を使うタイミング

Vue.jsで「アロー演算子」を使うタイミングが分からないなと思ったので、
このパターンが来たら、アロー演算子の出番だよ。と言うタイミングをメモっておいた。

https://jp.vuejs.org/v2/guide/components.html

data: function () {  //←function()
  return {           //←return
    count: 0
  }
}

出た!このパターンがきたら、アロー演算子を使う。

data: () => {
  count: 0
}

スッキリする!

computed:{
  hoge:{
    get: function(){  //←function()
      return this.$store.getters.hoge //←return
    }
    set: function(val){
      this.$store.dispatch('setHoge',val)
    }
  }
}

このパターンが来たらアロー演算子

computed:{
  hoge:{
    get() => (
      this.$store.getters.hoge
    )
    set(val){
      this.$store.dispatch('setHoge',val)
    }
  }
}

スッキリする。

getFilteredCity:{
  get: function(){  //←function & return
    return this.citys.filter(function(City){  //←function & return  
      return (City.pref.indexOf(this.prefCode) !== -1 && (City.pref == this.prefCode || this.prefCode == ''))
      }
    }
  }
},

フィルタ関数のこのパターンも

getFilteredCity:{
  get() => (
    this.citys.filter(
      (City) => (City.pref.indexOf(this.prefCode) !== -1 && (City.pref == this.prefCode || this.prefCode == '')
    ) 
  )
},

こんなにスッキリする。

watch:{
  meta(){
    this.$emit('updateHead')
    setTimeout( funtion(){
      this.getStoreLoading(false)
    }, 1000);
  },
},

setTimeoutも。

watch:{
  meta(){
    this.$emit('updateHead')
    setTimeout(() => {
      this.getStoreLoading(false)
    }, 1000);
  },
},

ちょっとスッキリする。

でもPrettierとかESLintで--fixを設定してると…

勝手に直してくれるから、勝手にイケてる感じにしてくれる。

Babelのプラグイン

自動で直す設定にしておくと、ドンドンイケてる感じに直って行くので、
babel.config.jsに、プラグインを入れておいたほうが良いと思います。

module.exports = {
  presets: ["@babel/preset-env"],
  overrides: [
    {
      presets: [
        ["@babel/preset-env", { targets: { ie: "11" } }],
      ],
      plugins: [
        '@babel/plugin-transform-arrow-functions', //←これ
      ]
    }
  ],
}

以上です。

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

Vue.js:アロー演算子を使うタイミング

Vue.jsで「アロー演算子」を使うタイミングが分からないなと思ったので、
このパターンが来たら、アロー演算子の出番だよ。と言うタイミングをメモっておいた。

https://jp.vuejs.org/v2/guide/components.html

data: function () {  //←function()
  return {           //←return
    count: 0
  }
}

出た!このパターンがきたら、アロー演算子を使う。

data: () => {
  count: 0
}

スッキリする!

computed:{
  hoge:{
    get: function(){  //←function()
      return this.$store.getters.hoge //←return
    }
    set: function(val){
      this.$store.dispatch('setHoge',val)
    }
  }
}

このパターンが来たらアロー演算子

computed:{
  hoge:{
    get() => (
      this.$store.getters.hoge
    )
    set(val){
      this.$store.dispatch('setHoge',val)
    }
  }
}

スッキリする。

getFilteredCity:{
  get: function(){  //←function & return
    return this.citys.filter(function(City){  //←function & return  
      return (City.pref.indexOf(this.prefCode) !== -1 && (City.pref == this.prefCode || this.prefCode == ''))
      }
    }
  }
},

フィルタ関数のこのパターンも

getFilteredCity:{
  get() => (
    this.citys.filter(
      (City) => (City.pref.indexOf(this.prefCode) !== -1 && (City.pref == this.prefCode || this.prefCode == '')
    ) 
  )
},

こんなにスッキリする。

watch:{
  meta(){
    this.$emit('updateHead')
    setTimeout( funtion(){
      this.getStoreLoading(false)
    }, 1000);
  },
},

setTimeoutも。

watch:{
  meta(){
    this.$emit('updateHead')
    setTimeout(() => {
      this.getStoreLoading(false)
    }, 1000);
  },
},

ちょっとスッキリする。

でもPrettierとかESLintで--fixを設定してると…

勝手に直してくれるから、勝手にイケてる感じにしてくれる。

Babelのプラグイン

自動で直す設定にしておくと、ドンドンイケてる感じに直って行くので、
babel.config.jsに、プラグインを入れておいたほうが良いと思います。

module.exports = {
  presets: ["@babel/preset-env"],
  overrides: [
    {
      presets: [
        ["@babel/preset-env", { targets: { ie: "11" } }],
      ],
      plugins: [
        '@babel/plugin-transform-arrow-functions', //←これ
      ]
    }
  ],
}

以上です。

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

Techpitの教材「Vue.js と Firebase ではてなブックマーク風なブックマークサービスを作ってみよう!」を終えての感想

説明

TechCommitさんのお年玉企画で、Techpitさんの教材「Vue.js と Firebase ではてなブックマーク風なブックマークサービスを作ってみよう!」が当選したので使ってみた感想を書きました。

前提

筆者のレベル

言語・ツール レベル
HTML・CSS 基礎レベル
JavaScript 業務で少し触ったことがある   
Vue.js はじめて
Firebase はじめて

扱った技術

  • Vue.js
  • Bulma
  • Firebase Authentication
  • Firestore
  • Firebase Storage
  • Firebase Cloud Function
  • Firebase Hosting

作った機能

  • ブックマークサービス
    • トップページ(全ユーザーのブックマーク一覧)
    • サインアップ
    • サインイン
    • 認証情報変更
    • ブックマークへのコメント
    • ブックマークコメントのスター登録
    • プロファイルページ

完成品

公開したアプリケーション。
https://pitmark-prod-9c6b2.firebaseapp.com/

トップページ

スクリーンショット 2020-02-16 17.27.46.png

サインアップ

スクリーンショット 2020-02-16 17.35.36.png

サインイン

スクリーンショット 2020-02-16 17.35.54.png

認証情報変更

スクリーンショット 2020-02-16 17.39.43.png

ブックマークへのコメント&スター登録

スクリーンショット 2020-02-16 18.02.23.png

プロファイルページ

スクリーンショット 2020-02-16 17.59.03.png

感想

  1. 手順通りに進めるとアプリができあがるので、Progate等で基礎を学んだあとに何か成果物を作りたい人に良いと思いました。Vue.jsとFirebaseの使い方を丁寧に説明してくれるので詰まる箇所も少なく進めていけました。ただし説明が少ない箇所もあるので自分で調べる必要もあります(Vueの公式ページがわかりやすいので助かる)。

  2. HTML・CSS、JavaScriptの基礎を学んでいないと消化不良になりそうです。自分はJavaScriptをしっかり学んでいなかったのでJavaScriptとVue.jsの両方を調べながら教材を進めていきましたが、それだとVue.jsの内容が頭に入りきらないことがあったのでちょっともったいなかったと感じました。

  3. Firebaseは初めて触りましたが、基礎的な内容を学ぶことができ勉強になりました。アプリを簡単に公開までもっていけます。学んだ内容は

    1. Firebase Authentication:認証機能
    2. Firestore:データベース機能
    3. Firebase Storage:ファイルストレージ機能
    4. Firebase Cloud Function:データが作成・更新・削除されたときに特定の処理を裏側で行わせる等で使用
    5. Firebase Hosting:アプリの公開に使用
  4. Bulmaが便利。Flexboxベースの柔軟なレイアウトを簡単に構築できる。教材に沿って使っただけなので自分で色々試してみたい。

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

【Vuejs】v-forで配列を出力する時に1つ目の要素とそれ以降でstyleを変える

テーブルとかもそうですが、最初の要素だけスタイルを切り替えたい時にどうするんだろうと調べていたら3項演算子が使えました。

class属性を配列の要素番号で切り替える

sample.html
    <style>
        .first{
            color: aquamarine;
        }
        .two-or-more{
            color: burlywood;
        }
    </style>
    <div id="app">
        <ul>
            <li v-for="(e,index) in mylist" v-bind:class="[index == 0 ? 'first' : 'two-or-more']">{{e.name}}</li>
        </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        var app = new Vue({
            el: "#app",
            data: {
                mylist: [
                    { name: "1つ目" },
                    { name: "2つ目" },
                    { name: "3つ目" },
                    { name: "4つ目" },
                ],
            }
        })
    </script>

結果

画面はこんな感じで、
Screenshot2020-02-16_20-09-52.png

要素はこんな感じで出力されました。
Screenshot2020-02-16_20-13-42.png

参考

公式ドキュメント#配列構文
https://jp.vuejs.org/v2/guide/class-and-style.html#%E9%85%8D%E5%88%97%E6%A7%8B%E6%96%87

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

Vue.jsならPugとStylusが良い

「vue.js」での「pug」と「stylus」は相性が良いです。

インストール

$ npm install -D pug
$ npm install -D pug-loader
$ npm install -D stylus
$ npm install -D stylus-loader

ファイル準備

/stylus/_reset.styl
/stylus/_variable.styl
/stylus/_mixin.styl
元々あるやつをファイル変換しておく。

Component.vue

コンポーネントの中で使っていく

<template lang="pug"></template>
<style lang="stylus" scoped></style>
<script></script>

外部ファイルは先に読み込んでおく。コンポーネントからの
相対パスで指定する必要があると思います。ここはsassより不便。

<template lang="pug"></template>
<style lang="stylus" scoped>
@import "../../../../scss/_reset.styl"
@import "../../../../scss/_variable.styl"
@import "../../../../scss/_mixin.styl"
</style>
<script></script>

中身を書いていく

実際に中身を書いてみる

<template lang="pug">
.component
  .wrapper
    .container
      .inner
        .item
          .text
            a.link ボタン
</template>
<style lang="stylus" scoped>
@import "../../../../scss/_reset.styl"
@import "../../../../scss/_variable.styl"
@import "../../../../scss/_mixin.styl"
.component
  component()  //mixinを読み込む
  margin 0
  .wrapper
    wrapper()  //mixinを読み込む
    margin 0
    .container
      container()  //mixinを読み込む
      width $default
      .inner
        inner()  //mixinを読み込む
        margin-bottom 0
        .item
          item()  //mixinを読み込む
          display flex
          jusitify-content space-between
          .text
            text()  //mixinを読み込む
            font-size $normal
            a.link
              link()  //mixinを読み込む
              color $link
</style>
<script></script>

どうですか?
_variable_mixinから呼び出して使って書いても
めっちゃ書きやすくないですか?{:};@include書かなくて良いんです。

以上です。

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

【Nuxt.js】Vue.filter()で文字列整形

Fiter()で文字列の整形

グローバルな Vue.filter() を使用してカスタマイズしたフィルタを登録します。
コードは'/plugins'ディレクトリの中に作成。
今回は日付をフィルターにかけて文字列を整形してみます。

plugins/filter.js
import Vue from 'vue'

//定数monthsに月名を文字列で用意
const months = [
  "1月",
  "2月",
  "3月",
  "4月",
  "5月",
  "6月",
  "7月",
  "8月",
  "9月",
  "10月",
  "11月",
  "12月"
];

const dateFilter = value => {
  return formatDate(value);
};

function formatDate(inputDate) {
  const date = new Date(inputDate);
  const year = date.getFullYear();
  const month = date.getMonth();
  const day = date.getDate();
  const formattedDate = year + "" + " " + months[month] + " " + day + "";
  return formattedDate;
}

Vue.filter('date', dateFilter)

'date'という名前で、定数'dateFilter'の値をコンポーネント側に渡します。
これでどのコンポーネントからでも使用できるようになりました。

config.jsに登録

nuxt.config.js
plugins: [
  '~/plugins/filter.js',
]

filterを適用

コンポーネント側で利用してみましょう。
'date'という名前で使用できます。
他にも書き方はありますが今回はmustache型で記述してみました。
'updateDate'はStoreで別途用意しています。

index.vue
<template>
  // {{ フィルタに渡すデータ | フィルタ }}
  <div>更新日:{{ updateDate | date }}</div>
</template>

ありがとうございました。

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

Vue.jsでFontAwesomeを使う

普通にFontawesomeを読み込んで使っても大丈夫と思うんですが、
あえてvue.js専用にラップしてある物の使い方(備忘録)です。

インストール

$ npm install --save @fortawesome/fontawesome-svg-core
$ npm install --save @fortawesome/free-solid-svg-icons
$ npm install --save @fortawesome/free-brands-svg-icons
$ npm install --save vue-fontawsome

main.js

import Vue from 'vue'
import App from './App.vue'
//(省略)
import { library } from '@fortawesome/fontawesome-svg-core'
//基本セットを読み込んで、使いたいアイコンを読み込みます。
import { faHome , faSearch} from '@fortawesome/free-solid-svg-icons'
import { faFacebook , faLine } from '@fortawesome/free-brands-svg-icons'
//使うものはあらかじめ読み込んで「library」に追加しておきます。
library.add(faHome)
library.add(faSearch)
library.add(faFacebook)
library.add(faLine)
//さらに「vue-fontawesome」を読み込みます。
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
//カスタムタグ「font-awesome-icon」として使えるようにします。
Vue.component('font-awesome-icon', FontAwesomeIcon)

Component.vue

<template lang="pug">
.btn
  a
    font-awesome-icon(
    :icon="['fas','home']"
    )
    | Home
  a
    font-awesome-icon(
    :icon="['fas', 'search']"
    )
    | Search
  a
    font-awesome-icon(
    :icon="['fab','facebook']"
    )
    | Facebook
  a
    font-awesome-icon(
    :icon="['fab', 'line']"
    )
    | Line
</template>

これでアイコンが使えるようになります。
慣れた人ならCSSで定義したほうが速いような気がしますが、
このやり方もスッキリしていて、割と良い気がします。

参考にした記事

https://github.com/FortAwesome/vue-fontawesome

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

BootstrapVueで、さらっと表を作る

はじめに

Vue.jsというと、RouterやVuexを併用してSPAを作るものと思う方もいるかもしれませんが、
PHPなどでの同期処理やちょっとした非同期通信だけでも、いろいろな事ができます。
例えば、ユーザーのアクション(クリックやタップ婦)で動的に切り替わるUIなどです。

Vue単体でもいろいろ作れますが、BootstrapVueのようなフレームワークを追加すると、より簡単に色々なUIが作成できます。

今回は、バックエンドから割と好評だった表のサンプルを、備忘を兼ねて掲載します。

使用するもの

Vue.js (https://jp.vuejs.org/)
BootstrapVue (https://bootstrap-vue.js.org/)

*より簡単にやるために、BootstrapVue を使用しています。
また、元はBootstrapなので、デザイナーさんがstyleを設定しやすいという事もあります。

作り方

Buildの環境をつくる

自分でwebpackで設定したり、VueCLI などで簡単に作っても良いです。
https://cli.vuejs.org/

ブラウザで読み込む(script要素利用)

とりあえずこのサンプルでは、ブラウザで読み込んでしまいます。
(一番手っ取り早い方法でもあります)

参考はこちら。
https://bootstrap-vue.js.org/docs#browser
上記のサンプルだとIntersectionObserverのpollyfillをloadしていますが、大抵は無くても大丈夫です。

表を作る

表のコンポーネント

表を作るコンポーネントはこちらになります。
BootstrapVueのコンポーネントになります。

b-table
https://bootstrap-vue.js.org/docs/components/table/

b-tableのプロパティ

プロパティはいろいろありますが、まずはこの2つです。
データを表示する表であれば、これだけで十分です。

  • items

表に表示するのデータ本体です。
このプロパティにデータをArrayで渡します。
またデータのArrayは、Key:ValueのObjectになります。

// sample
[
  { 
    age: 40,
    name: 'admin1',
    email: 'hoge@for.jp'
  },
  ...
]
  • fields

fieldsは「見出し」になります。HTMLでのthです。
これもArrayで渡します。

// sample
[
  {
    key: 'name',
    label: 'Name'
  },
]

keyitemsのObjectのKeyになります。
labelは見出しとして表示するデータになります。
fieldsのkeyの値と、items内のObjectのkeyがマッチすると見出しとして表示されます。

具体例

下記を見て頂くとわかりやすいかと思います。

    <b-table striped hover caption-top
          label="Table Options"
          :items="[
            { age: 40, name: 'admin1', email: 'hoge@for.jp' },
            { age: 21, name: 'admin2', email: 'huga@for.jp' },
            {  age: 89, name: 'admin3', email: 'piyo@for.jp' },
            {  age: 38, name: 'admin4', email: 'aaaaa@for.jp' }
          ]"
          :fields="[
            {
              key: 'name',
              label: 'Name'
            },
            {
              key: 'email',
              label: 'Email'
            },
            {
              key: 'age',
              label: 'Old'
            }
          ]"
    >
      <template v-slot:table-caption>表サンプル</template>
    </b-table>

ちょっとした応用

特定のデータを非表示にしたい。でもitemsを変更したくない

こんな時は、fieldsから表示したくないkey:valueを削除する事で対応できます。

サンプル

https://codepen.io/H_Naito/pen/RwPaKxJ
(年齢非表示サンプルを参照してください)

ソートつけたい、一部にHTMLを追加したい

まずソートの場合は、fieldsにsortableの追加で対応できます。
sortableを追加した列がソート対象になります。

一部のセルにHTMLを入れたい、例えばリンクしたい場合などはもう少しコードの追加が必要になります。
下記のコードで、セルを指定して表示したいデータとHTMLを設定します。
下記では、emailのセルにmailtoのリンクを設置しています。

        <template v-slot:cell(email)="data">
          <a :href="`mailto:${data.item.email}`">{{ data.item.email}}</a>
        </template>

サンプル

      <b-table striped hover caption-top
          :items="[
            { age: 40, name: 'admin1', email: 'hoge@for.jp' },
            { age: 21, name: 'admin2', email: 'huga@for.jp' },
            {  age: 89, name: 'admin3', email: 'piyo@for.jp' },
            {  age: 38, name: 'admin4', email: 'aaaaa@for.jp' }
          ]"
          :fields="[
            {
              key: 'name',
              label: 'Name',
              sortable: true
            },
            {
              key: 'email',
              label: 'Email',
              sortable: true
            },
            {
              key: 'age',
              label: 'Old',
              sortable: true
            }
          ]"
    >
        <template v-slot:table-caption>リンクとソート機能付き</template>
        <template v-slot:cell(email)="data">
          <a :href="`mailto:${data.item.email}`">{{ data.item.email}}</a>
        </template>
    </b-table>

サンプルの全コード

https://codepen.io/H_Naito/pen/RwPaKxJ

参考

Tables
https://bootstrap-vue.js.org/docs/components/table/

Sorting
https://bootstrap-vue.js.org/docs/components/table/#sorting

Custom data rendering
https://bootstrap-vue.js.org/docs/components/table/#custom-data-rendering


普通にtableを書くと、table要素からtr、ht、tdを記述して、そこにloop設定してとなりますが、結構簡単に作れると思います。
他にもいろいろなプロパティがありますので、何かのプロジェクト等で利用してみてください。

閲覧ありがとうございました。

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

【Laravel 7 リリース】向け、 Laravel7のインストール方とUpgrade Guideの日本語訳

1_GfmHsC_I5egGyPx1lvXR9Q.png

Laravel 7系

Laravel公式サイトにより、Laravel 7のリリースは2020年の3月3日と発表されました?

皆さんは6系にしましたか?7系もう目の前ですよ!?
ただし、7はLTS(Long-Term Support)じゃなさそうなので、商用なら6の方が良いかな。

Laravel 6

  • 2021年9月3日までのバグ修正
  • 2022年9月3日までのセキュリティ修正

Laravel 7

  • 2021年9月3日までのバグ修正
  • 2022年9月3日までのセキュリティ修正

Screenshot 2020-02-16 12.37.50.png

現時点Laravel 7のインストール方

packagistのLaravel 7.0すでにdev-developブランチに更新したので、
1_VcaiBk0M6oRApRQ1rypIUA.png

以下のコマンドはxyyoというLaravelアプリが作成できます。

composer create-project --prefer-dist laravel/laravel xyyo dev-develop

インストール完了したら、
?7.xだ?
Screenshot 2020-02-16 17.22.00.png

7系と遊びたい方はぜひ!!

変わっているものは、以下に記載されているようにそんなに多くありませんが、5.x系前半からなど、だんだんキツくなるでしょう。個人プロジェクトは早くアップデートしましょうね。では、早速見てみましょう?

Upgrade Guide

6.xから7へ

PHPバージョン

  • 7.2.5 以上は必須

Symfony 5 関連のアップグレード

  • 影響性 : 高

Laravel7 は Symfony5系のコンポネートを使用しているので、Symfony5系の変更によって、変更が必要になってきます。

App\Exceptions\HandlerクラスのreportrenderメソードはExceptionインスタンスを格納されなくて、インターフェイスThrowableのインスタンスを格納します。

// Laravel 6
public function report(Exception $exception);
public function render($request, Exception $exception)

⬇︎

// Laravel 7
use Throwable;
public function report(Throwable $exception);
public function render($request, Throwable $exception);

コンフィグファイルのsessionoptionのデフォルト値の変更

// Laravel 6
'secure' => env('SESSION_SECURE_COOKIE', false).
'same_site' => null,

⬇︎

// Laravel 7
'secure' => env('SESSION_SECURE_COOKIE', null),
'same_site' => 'lax',

Authenticationー認証

  • 影響性 : 高

全ての認証系のscaffoldingはlaravel/uiに移動されました。もし5系のphp artisan make:authか、6系php artisan ui:auth利用しているなら、laravel/ui~2.0にする必要があります。

composer require laravel/ui "~2.0"

ちなみに、scaffoldingのフロント側デフォルトはvue使用ですが、パラメータ指定でArguments:type ー The preset type (bootstrap, vue, react)変更することもできます。

Date Serializationー日付のシリアライズ化

  • 影響性 : 高

EloquentのtoArraytoJsonメソードを使う際、Laravel7は新たな日付のシリアライズフォーマットを使用します。Laravel7は日付のシリアライズをする時に、タイムゾン秒の小数部に対応しているISO-8601に適用されるCarbonのtoJsonメソードを使用します。プラス、この変更はより良いフロント側の日付の対応を提供します。

7系前の日付フォーマット2019-12-02 20:01:00は7系から2019-12-02T20:01:00.283041Zに更新されます。

もし7系以前のフォーマットを使用したければ、モデルにserializeDateをoverrideしてください。

/**
 * Prepare a date for array / JSON serialization.
 *
 * @param  \DateTimeInterface  $date
 * @return string
 */
protected function serializeDate(DateTimeInterface $date)
{
    return $date->format('Y-m-d H:i:s');
}

この変更はモデルレベルの影響しかないです。データベースに保存されているデータに影響しません。

Blade

The component Method

  • 影響性 : 中

Blade::componentメソードをBlade::aliasComponentに変更したので、使用している場合は変更してください。

Blade Components & "Blade X"

  • 影響性 : 中

Laravel7 は公式的にBladeの"tag components"をサポートします。もし続いてspatie/laravel-blade-xパッケージ利用の場合は、Blade::withoutComponentTagsメソードを使って、Bladeの"tag components"を無効にしてください。
もしspatie/laravel-blade-xパッケージ使用てなければ、これを無視してよろしいです。
使用している場合は、withoutComponentTagsメソードをAppServiceProviderbootに追加してください。

use Illuminate\Support\Facades\Blade;

Blade::withoutComponentTags();

Factory Types

  • 影響性 : 中

7系から"factory types"機能を削除します。この機能は2016年の10月からドキュメントに下げられて、もし使っている場合はよりフレキシブルなfactory states機能に移行してください。

Eloquent

The addHidden / addVisible Methods

  • 影響性 : 低

ドキュメントされていないaddHiddenaddVisibleメソードが削除されました。使用している場合はmakeHiddenmakeVisibleを使ってください。

The booting / booted Methods

  • 影響性 : 低

モデルの"boot"の時にロジックを追加するメソードbootingbootedをEloquentに追加しました。もし同じ名前のメソードを使っている場合は名前を変更してください。

Route Binding

  • 影響性 : 低

Illuminate\Contracts\Routing\UrlRoutableインターフェイスにあるresolveRouteBindingメソードは$fieldパラメータを使います。もしこのインターフェイスを使用していれば、更新してください。

更に、Illuminate\Database\Eloquent\ModelクラスのresolveRouteBindingメソードも$fieldパラメータを使いますので、もしこのメソードをoverrideしたら、更新してください。

最後、Illuminate\Http\Resources\DelegatesToResourcestraitのresolveRouteBindingメソードも$fieldパラメータを使いますので、もしこのメソードをoverrideしたら、更新してください。

The getOriginal Method

  • 影響性 : 低

$model->getOriginal()メソードはキャストされたモデールを参考にします。もし以前のキャストされない、生の属性を使用したい場合はgetRawOriginalを使ってください。

TokenRepositoryInterface クラス

  • 影響性 : 低
  • Illuminate\Auth\Passwords\TokenRepositoryInterfacerecentlyCreatedTokenメソードを追加したので、もしこのインターフェイスを使用している場合は、recentlyCreatedTokenメソードを追加してください。

HTTPーPSR-7の適用性

  • 影響性 : 低

PSR-7レスポンス生成ためのZend Diactorosライブラリが推奨されません。もしPSR-7の適用性のために、使用している場合はnyholm/psr7composerパッケージを使用してください。更に、composerパッケージのsymfony/psr-http-message-bridge ^2.0も一緒にインストールしてください。

MailーMarkdownメールテンプレートの更新

デフォルトのMarkdownメールテンプレートのデザインなどはより良くなります。プラス、ドキュメントに書かれていないpromotionテンプレートは削除されます。

Queueー非推奨される--daemon削除

  • 影響性 : 低

queue:workコマンドのフラグ--daemonが削除されます。daemonとして実行されるのはデフォルトになります。

ResourcesーIlluminate\Http\Resources\Json\Resourceクラス

  • 影響性 : 低

非推奨されるIlluminate\Http\Resources\Json\Resourceは削除されます。リソースはIlluminate\Http\Resources\Json\JsonResourceクラスを継承にします。

Sessionーarray セッションドライバ

  • 影響性 : 低

array セッションドライバのデータはカレントリクエストに持続的にアクセスできるようになります。7系以前は使えません。

TestingーassertSee アサーション

  • 影響性 : 中

TestResponseクラスのassertSee アサーションは自動的にエスケープされます。もし手動的にエスケープしてきたら、しないでください。

Validationーdifferentルール

  • 影響性 : 中

differentルールはリクエストに特定のパラメータが足りない場合に通りません。


個人的に7系に非常に楽しみにしています?
特にこれ、元々はモデルにgetRouteKeyNameメソード使って、primary keyをoverrideしないといけないこと以下のように記述すれば、終わりみたい!便利そう!

Route::get('/posts/{post:slug}', function (Post $post) {
    // ...
});

リソース:
https://laravel.com/docs/master/upgrade
https://laravel-news.com/implicit-route-model-binding

現在2020年2月16日の最新情報です。リリースノートなど新たな情報が出たら、更新したいと思います。

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

Vue.jsとDjango-Rest-Frameworkで神経衰弱アプリを作ってみる【その6】~ユーザ認証編2~

<< その5

ログインを実装

ログイン画面作成

Login.vue
<template>
  <v-form>
    <v-container>
      <v-row>
        <v-col cols="12">
          <v-text-field
            v-model="username"
            label="Username"
            outlined
          ></v-text-field>
        </v-col>
      </v-row>

      <v-row>
        <v-col cols="12">
          <v-text-field
            v-model="password"
            outlined
            label="Password"
            style="min-height: 96px"
            type="password"
          >
          </v-text-field>
        </v-col>
      </v-row>

      <div class="text-right">
        <v-btn depressed large color="primary">ログイン</v-btn>
      </div>
    </v-container>
  </v-form>
</template>

<script>
export default {
  name: 'Login',
  data: function () {
    return {
      username: null,
      password: null
    }
  },
  mounted () {
    //
  }
}
</script>

router/index.tsを修正

router/index.ts
import Vue from 'vue'
import Router from 'vue-router'
import PlayScreen from '@/views/PlayScreen.vue'
import Login from '@/views/Login.vue'

Vue.use(Router)

export default new Router({
  mode:'history',
  routes: [
    {
      path: '/',
      redirect: 'login'
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    },
    {
      path: '/playscreen',
      name: 'playscreen',
      component: PlayScreen
    },
  ]
})

npm run devすると作成したログイン画面が初期表示されるようになります。

image.png

vuexのインストール

npm install vuex --save

axiosをインストール

axiosは「Promise ベースの HTTP クライアント」です(公式より)。

npm install axios --save

axiosでget, postリクエストを投げる共通関数を用意

src直下にapiフォルダを用意し、またその直下にcommon.tsを作成してaxiosの共通関数を用意

concentratio # プロジェクトルートディレクトリ
├── config
│   ├── ...
│
├── frontend
│   ├── ...
│   ├── ..
│   ├── .
│   └── src
│         └──api # 作成
│             └──common.ts # 作成
└── manage.py
src/api/common.ts
import axios from "axios";

const baseRequest = axios.create({
  baseURL: "http://localhost:8000",
  headers: {
      "Content-Type": "application/json",
  }
});

/**
 * GETリクエスト
 * @param url URL
 */
function get(url) {
  return baseRequest.get(url);
}

/**
 * POSTリクエスト
 * @param url URL
 */
function post(url, payload) {
  return baseRequest.post(url, payload);
}

export default {
    get,
    post
};

認証用のvuexモジュールを用意


単一のgettersやactionsで管理するとソースコードが膨れ上がるだけだなあと思って機能毎にモジュール分割してみようと思って挑んだのですが、見事に半日ほどハマりました・・・。

concentratio # プロジェクトルートディレクトリ
├── config
│   ├── ...
│
├── frontend
│   ├── ...
│   ├── ..
│   ├── .
│   └── src
│         └──store # 作成
│             └──authenticates # 作成
│                 ├──actions.ts # 作成
│                 └──index.ts.ts # 作成
│                 └──mutations.ts # 作成
└── manage.py
store/authenticates/actions.ts
import commonAxios from "../../api/common";

export default {
  LOGIN: function({ commit }, payload) {
    return new Promise((success, error) => {
      commonAxios.post("/api-token-auth/", payload).then(
        response => {
          // 成功時の処理
          commit("SET_TOKEN", response.data.token); // mutations.tsのSET_TOKEN関数にトークンを渡す
          commonAxios.baseRequest.defaults.headers.common['Authorization'] = `JWT ${response.data.token}`;
          success();
        }
      );
    });
  }
}


上記、urlの最後に"/(スラッシュ)"を付け忘れるとCORSではじかれてエラーになります。
これにも気づくのに時間がかかって小一時間ほどハマりました・・・。

 
 

store/authenticates/index.ts
import actions from './actions';

export const state = {};

export default {
  namespaced: true, // 必ずつける、外すとvueのmapActionsで読み込む時にエラーになるよ。
  state,
  actions,
  mutations
};

store/authenticates/mutations.ts
export default {
  SET_TOKEN: function(state, token) {
    state.token = token
  }
};

src/store直下にindex.tsを作成

store/index.ts
import Vue from "vue";
import Vuex from "vuex";
import authModules from "./authenticates"; // authenticates配下のモジュールをauthModulesという名前で読み込む

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    authModules: authModules,
  }
});

src/main.tsを修正

src/main.ts
.
..
...
import store from "./store"; // 追加
...
..
.
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
  vuetify: new Vuetify(),
  store // 追加
});

Login.vueを修正

Login.vue
<template>
  <v-form>
    <v-container>
      ...
      ..
      .

      <div class="text-right">
        <v-btn depressed large color="primary" @click="login">
          ログイン
        </v-btn>
      </div>
    </v-container>
  </v-form>
</template>

<script>
import { mapActions } from "vuex";

export default {
  name: "Login",
  data: function() {
    return {
      username: null,
      password: null
    };
  },
  methods: {
    ...mapActions("authModules", ["LOGIN"]), // モジュール分割したaction.tsからLOGIN関数を読み込み
    login: function() {
      const data = {
        username: this.username,
        password: this.password
      };
      this.LOGIN(data).then(res => {
        console.log(res);
      });
    }
  },
  mounted() {
    //
  }
};
</script>

responseが返ってくるか確認

正しいusernameとpasswordを入力後ログインボタン押下でresponseが返ってくる確認します。

image.png

返ってきました!

認証成功後、トップページに遷移するようにする。

Login.vueconsole.log(res);this.$router.push("/playscreen");に書き換えるだけ。

以上

無事、ログイン画面からトップページに遷移することができました。

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

SIerの僕、プログラミング始めてみた(10月スタート・初投稿)

簡単な自己紹介

SIerに2年間在籍しております、かなたです。
誰も興味がない趣味とか仕事とか、本記事のきかっけを初めに。

  • 好き①:服(Margielaとか)
  • 好き②:映画(おすすめは Into the Wild)
  • 業務①:DataSpiderを用いたデータ連携基盤を構築
  • 業務②:C#デスクトップ系業務アプリ

10月からWEBアプリケーションの勉強を独学で始めまして、よくよくエラー等ひっかかります。
昨日の自分自身に対して、教えるならどのように伝えるかを、ここに表現できればと思っています。
(あとモチベーションアップに繋がれば…という思いです)

作っているもの

SIer(以外もそうだと思うんですが)は基本的に、実績ベースで予定・工数を積み上げ、スケジューリングします。
ですが、実績を記録しているPJ/チームを僕は見たことがありません。
そのため、予実データ化し改善までつながるアプリケーションを開発しよう!と始めてみました。

成果

さてさて、10月からおよそ4ヶ月の成果はざっくり以下の通りです。

プログラミング基本

  • HTML/CSS/JavaScript/Ruby/Railsの基本(Progate)
  • Railsチュートリアル2周(GitHub~Herokuデプロイ)
  • Vueサンプル複写(基礎から学ぶ Vue.js)

  • リーダブルコード
  • 初めてのRuby
  • Webを支える技術
  • Web API: The Good Parts
  • テスト駆動開発
  • リファクタリング
  • エンジニアファースト
  • エンジニアの知的生産術
  • コンピュータはなぜ動くのか
  • デザイン系の本を数冊

アプリケーションの実装状況

  • フロントエンド ・・・ Vue.js + Vuex + VueRouter + Vuetify
  • サーバサイド ・・・ RubyonRails
  • 実装:メールアドレスを利用したユーザの登録〜ログインおよびユーザ情報の更新
  • 詳細:ユーザログイン時には、WebStorageを利用。ログイン永続化を実装。

ざっくりこんな状況です。頑張らないと・・・

さいごに

また、目標・スケジュールをこちらに記載できればと思っています(もしかして、そういった場でない?)
そして、次回からは、冒頭で述べたとおり、自分自身の備忘録として、
学んだことを表現させていただきます。

よろしくお願いいたします。

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

Vue-cli3、Webpack4、babel7で、IE11向けの「@babel/preset-env」を使いたい

Vue.jsで作っていて、IE11が真っ白になる時。

SCRIPT1003 Expected ':'

このエラーが何か分からなくて、困る時がありますよね?
おそらくbabelの設定が、うまくできてないだけだと思います。

IE11は...

{ value }というようなshort-handが使えず、
{ key: value }にしていないと駄目だったり、

hoge(){}というような関数の省略した書き方は使えず、
hoge: function(){}という書き方にしたり、

data:() => {}というようなアロー演算子は使えないので、
data: function(){ return }に直してみたり...

そもそもコレbabelで解決するべき事ですよね。。

babel.config.jsって、難しいですよね。
babel-polyfillは非推奨ですって言うけど、じゃあどうしろとか説明ないし。

babel7以降で、IE11向けに@babel/preset-envを使いたい時。

とりあえずインストール

今回参考にした記事がvue2-dropzoneissueなので、ほぼ関係ないと思いますが、
vue2-dropzone以外でも使える書き方みたいです。
まあとりあえず、参考記事の通り入れます。

$ npm install -D vue2-dropzone

vue.config.js

module.exports = {
  //(※通常の設定は省略しています)
  //↓webpack.config.jsの内容を変更する時の書き方
  configureWebpack: {
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: (file) => (
            /node_modules/.test(file) &&
            !/vue2-dropzone/.test(file)
          ),
          use: [
            { loader: "babel-loader" },
          ],
        }
      ],
    },
  }
};

babel.config.js

module.exports = {
  presets: ["@babel/preset-env"],
  overrides: [
    {
      test: ["./node_modules/vue2-dropzone"],
      presets: [
        ["@babel/preset-env", { targets: { ie: "11" } 
        }],
      ],
      plugins: [
        '@babel/plugin-transform-arrow-functions',
        '@babel/plugin-transform-shorthand-properties',
      ]
    }
  ],
}

'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-shorthand-properties',
これがうまく動いていれば、IE11で見えるはずです。

以上です。

参考記事

https://github.com/rowanwins/vue-dropzone/issues/439

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

[Vue.js]外側をクリックすると閉じるメニュー(複数の場合)

はじめに

  • メニューボタン押すと開く
  • メニューボタンもう一度押すと閉じる
  • メニューの外側押しても閉じる

この操作はこちら↓の記事を読んでもらえればできます。
[Vue.js]外側をクリックすると閉じるドロップダウンメニュー

ですが、複数ボタンがある場合、この処理だけだと操作性の問題が生じたため、いろいろ改善しました。それを紹介します。

注意 : 本記事は上で紹介した記事の技術がわかった上でお話をします。

[問題 1] メニューが閉じない場合がある

複数のボタンがある時、メニューを一度開き、別のボタンを押すと先に開いていたメニューが閉じない。
デモ (JSFiddle)

以下の画像をご覧ください。
Info 1 から順番にボタンを押していくと、Info 2を押した時にInfo 1のメニューが閉じない。
image.png

構造

親コンポーネント↓ : <msg-comp> を複数呼び出して並べる。

<div id="app">
  <div class="containter">
    <msg-comp
      v-for="msg in msgList"
      :msg="msg"
    ></msg-comp>
  </div>
</div>

<msg-comp>コンポーネント↓ : ボタンを押すと <info-menu> が開く。

<div class="msg">
  <button v-on:click.stop="showInfo = !showInfo">Info {{msg}}</button>
  <info-menu 
      v-if="showInfo"
      v-on:close="showInfo = false">
  </info-menu>
</div>

<info-menu>コンポーネント↓ : クリックイベントが発生した際、クリックした要素がこのコンポーネント内の要素ではない場合に閉じる。

<div class="info">
    <div>AAA</div>
    <div>BBB</div>
    <div>CCC</div>
</div>

問題1 の原因

<!--  <msg-comp> コンポーネント内  -->
<button v-on:click.stop="showInfo = !showInfo">Info {{msg}}</button>

.stop modifierでバブリングの停止を行っているのが原因。
Info 2 を押してメニューを開ける時に、自分自身のメニューを閉じるイベントが発火しないようになっているのはいいんだが、 Info 1のメニューを閉じるイベントも発火しない。

問題1 解決策

  • .stop modifierをやめて自前で実装。
  • クリックした要素が開けるボタンだった時に、閉じる処理を発火させないようにする。

問題1 の解決策 デモ (JSFiddle)

まず、<msg-comp>コンポーネント内のbuttonから.stopを削除。
ref="msgBtn"でボタンのDOMを取得。取得したDOM要素を<info-menu>コンポーネントへpropsで投げる。

<!--  <msg-comp> コンポーネント内  -->
  <div class="msg">
    <button 
      ref="msgBtn" 
      v-on:click="showInfo = !showInfo"
    >
      Info {{msg}}
    </button>
    <info-menu 
      v-if="showInfo"
      v-on:close="showInfo = false"
      :msgBtn="$refs.msgBtn"
    >
    </info-menu>
  </div>

<info-menu>コンポーネントで、ボタンのDOM(msgBtn)を受け取る。
イベントリスナーの発火条件を追加。
 これで判定できる→ !this.msgBtn.contains(e.target)
 → 意味:クリックしたDOMが、ボタンのDOM内にあればfalseになる

Vue.component('info-menu', {
  mixins:[listener],
  template: '#info',
  props: {
    msgBtn: {
      type: HTMLButtonElement
    }
  },
  created:function(){
    this.listen(window, 'click', function(e){
      if (!this.$el.contains(e.target) && !this.msgBtn.contains(e.target)){
        this.$emit('close');
      }
    }.bind(this));
  }
}

注意点:propsにtype指定する際、msgBtnのDOMの型(type)をHTMLButtonElementとしている。a要素とかを使うなら変わる。

以上です。
Info 1を押して開いた状態でInfo 2を押すとInfo 1のmenuが閉じる! 解決できました。

image.png

[問題 2] Tab移動でInfo内部から抜けた時に勝手に閉じて欲しい

これは問題というより、そういう設計にしたかったという願望です。
Infoのサイズが大きい場合にはUX的にもそうしたいですよね。

問題2 解決策

問題2 の解決策 デモ (JSFiddle)

これはめちゃ簡単。新しくイベントリスナー追加するだけ。

created:function(){
  // ~~~
  this.listen(window, 'keyup', function(e){
    if (!this.$el.contains(e.target) && e.key === 'Tab'){
      this.$emit('close');
    }
  }.bind(this));
}

注意点としてはkeydownではなくkeyupにするところ。
keydownにしてしまうと、まだbuttonにいて、Infoに移ろうとする時に発火してしまい、すぐ閉じてしまう。
keyupにしておくと、Tabのkeydown時にInfoに移るので、keyupが発火する時にはInfoにいる。よって閉じない。

[おまけ] Nuxt.jsでやりたい!

Nuxt.jsでやる際に参考になりそうな話をします。

mixinをNuxtでやる

Nuxt.jsの場合、mixinは pluginsフォルダにmixin-common-methods.js 的なものを作り、
nuxt-config.js 内に plugins: ['@/plugins/mixin-common-methods'] とすれば良い。
各コンポーネントからわざわざ呼び出しとかもなし、これだけで使える。

参考 : Nuxt.jsで異なるコンポーネントから共通で利用できる関数を定義する(mixin編)

plugins/mixin-common-methods.js
import Vue from 'vue'

Vue.mixin({
  destroyed() {
    if (this._eventRemovers) {
      this._eventRemovers.forEach(function(eventRemover) {
        eventRemover.remove()
      })
    }
  },
  methods: {
    listen(target, eventType, callback) {
      if (!this._eventRemovers) {
        this._eventRemovers = []
      }
      target.addEventListener(eventType, callback)
      this._eventRemovers.push({
        remove() {
          target.removeEventListener(eventType, callback)
        }
      })
    }
  }
})
~/components/info-menu.vue
export default {
  props: {
    msgBtn: {
      type: HTMLButtonElement
    }
  },
  //~~~
  created:function(){
    this.listen(window, 'click', function(e){
      if (!this.$el.contains(e.target) && !this.msgBtn.contains(e.target)){
        this.$emit('close');
      }
    }.bind(this));
  },
  //~~~
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#Vue の初心者向けチュートリアルが面倒なので、1個のHTMLファイルにまとめて動作確認する ( はじめに - Vue.js )

チュートリアル

はじめに — Vue.js

手順

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<body style="font-size: 150%;">
  <div id="app"></div>

  <hr>

  <div id="app-2">
    <span v-bind:title="message">
      Hover your mouse over me for a few seconds
      to see my dynamically bound title!
    </span>
  </div>

  <hr>

  <div id="app-3">
    <span v-if="seen">Now you see me</span>
  </div>

  <hr>

  <div id="app-4">
    <ol>
      <li v-for="todo in todos">
        {{ todo.text }}
      </li>
    </ol>
  </div>

  <hr>

  <div id="app-5">
    <p>{{ message }}</p>
    <button v-on:click="reverseMessage">Reverse Message</button>
  </div>

  <hr>

  <div id="app-6">
    <p>{{ message }}</p>
    <input v-model="message">
  </div>
</body>

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

var app2 = new Vue({
  el: '#app-2',
  data: {
    message: 'You loaded this page on ' + new Date().toLocaleString()
  }
})

var app3 = new Vue({
  el: '#app-3',
  data: {
    seen: true
  }
})

var app4 = new Vue({
  el: '#app-4',
  data: {
    todos: [
      { text: 'Learn JavaScript' },
      { text: 'Learn Vue' },
      { text: 'Build something awesome' }
    ]
  }
})

var app5 = new Vue({
  el: '#app-5',
  data: {
    message: 'Hello Vue.js!'
  },
  methods: {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('')
    }
  }
})


var app6 = new Vue({
  el: '#app-6',
  data: {
    message: 'Hello Vue!'
  }
})

</script>


これをてきとうなパスに保存して開くこと。

Macならテキストをコピーした後に

pbpaste > ~/vue-start.html && open ~/vue-start.html

とか。

動作例

image

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2992

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

#Vue js を環境構築なし、サーバーなし、HTMLファイル1個だけで一瞬で始める、動かす。 (初心者向けチュートリアルを始めたい)

local に html ファイルを作る

自分のPCでファイルを作成、編集してアクセスする

~/start.html

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

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

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

</script>

open ~/start.html

これだけで動くみたいだ。

image

npm とか 色々用意しないと動かせないのかと思いこんでいたが、なんと

チュートリアルの二個目

ちゃんと動いている。

テキストにマウスを乗せるとポップアップが出てくるぜ。

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app-2">
  <span v-bind:title="message">
    Hover your mouse over me for a few seconds
    to see my dynamically bound title!
  </span>
</div>

<script>
var app2 = new Vue({
  el: '#app-2',
  data: {
    message: 'You loaded this page on ' + new Date().toLocaleString()
  }
})
</script> 

image

オンラインサイトを使う場合

公式でもおすすめされているサイト

https://jsfiddle.net/chrisvfritz/50wL7mdz/

にアクセスすると既にHTML / Javascriptが入力されており、 Run を実行するだけで動作させられるようだ!

image

参考

「Vue js はじめに」にこのことが書いてあるのだが、そもそも、膨大なドキュメントのどれが「はじめのはじめ」なのかが、初学者には分からないのですよ。

https://jp.vuejs.org/v2/guide/index.html

この「はじめに」にインストールボタンがあって、そこから npm の話とか出てくるじゃないですか…。

image

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2991

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

ReactとVueとAngularの戦い。

日本ではReactとVueが競い合っているように思えますが、実際はどうなのかを調べてみました。

npm trends

npm trendsという場所がありまして、そこではnpmパッケージのダウンロード数をグラフに表示してくれます。

使い方は簡単、パッケージ名を一つづつ入力していくだけです。

調査結果

react vs @angular/core vs vue vs jquery vs typescript | npm trends

reactとvueと@angular/coreとjqueryとtypescriptの図
npmtrends.PNG

Reactの圧勝ですね。Vueは日本だと記事を色々見かけて人気なのはなぜなのでしょうか?

The State of JavaScript 2019

ついでにこちらでも調べてみます。
The State of JavaScript 2019でフレームワークを見てみます。

Front End Frameworksカテゴリ

React
https://2019.stateofjs.com/front-end-frameworks/
Vue
https://2019.stateofjs.com/front-end-frameworks/
Angular
https://2019.stateofjs.com/front-end-frameworks/angular/

濃いピンクが、以前使用したことがあり、また利用したい。
薄いピンクが、以前使用したことがあり、もう使わない。
濃い緑が、聞いたことがあり学びたいと思っている。
薄い緑が、聞いたことはあるが興味はない。

満足度(=濃いピンク/(濃いピンク+薄いピンク))はReactとVueは同じくらいのようです。

SvelteがVueを抜き第二位に入っていますね。
The State of JavaScript 2019: Front End Frameworks

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

Vuetify使おうと思ってwebpack3からwebpack4への移行した時のメモ

概要

タイトル的なことをやったらハマったけど最終的に出来たっぽいのでまとめてみた。

経緯

Vuetifyのクイックスタートガイドに以下のような記述があり、自分のvue init webpackで作成したプロジェクトを確認したらwebpack3系で作られていた。

ツリーシェーキングに vuetify-loader を使用する場合は、Webpackのバージョン >=4であることを確認してください。

バージョン色々

  • vue: 2.5.22 -> 2.6.11
  • vue-cli: 3.3.0
  • webpack: 3.12.0 -> 4.41.6

移行手順

  • 依存関係の更新
  • ソースコードの編集
    • 上記のリンクにも記載がありましたが、より詳細がわかりやすかったのでこちらを参考にさせて頂いた。
  • 個別にやったこと
    • 上記を実施した際に、なぜかwebpack-dev-serverのバージョンだけ上がってなかったので、とりあえずnpm isntall --save-dev webpack@latestした。

ハマったとこ

上記の移行手順をやり切って、npm run buildは無事成功。
お次はnpm run devでローカル動作確認を、と思ったらdevサーバは立つもブラウザに以下のエラーが出て画面は描画されず。。。
image.png
とりあえずググるとこのような記事が。なるほど、そんなんもうわからん…

解決

ひたすらググり続けて同事象のissueに以下のコメントを発見。

use webpack-dev-server@3.5.1 instead of 3.9.0 solved the problem

ほう?そういえばさっきwebpack-dev-serverを適当に最新化したぞ…

package.json
    "webpack-dev-server": "^3.10.3"

やっぱり。
npm isntall --save-dev webpack-dev-server@3.5.1してみよう。

package.json
    "webpack-dev-server": "^3.5.1"

npm run dev実行
image.png
おー、できた!(画面も描画された)

そんなわけでひとまずwebpack3 -> webpack4の移行は完了。Vuetifyの導入に戻ります。

参考文献

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