20200404のvue.jsに関する記事は8件です。

Vue.js で作るForm(フォーム)

Vue.jsで作るフォーム

form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます。それは、自動的に入力要素のタイプに基づいて要素を更新するための正しい方法を選択します。ちょっと魔法のようですが、v-model はユーザーの入力イベントにおいてデータを更新するための基本的な糖衣構文 (syntax sugar) で、それに加えて、いくつかのエッジケースに対しては特別な配慮をしてくれます。

<参考文献>

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

入力フォーム input (text)

HTML

<!-- v-model.lazyでinputからカーソルが離れた際に発火するようにする -->
<input id="title" type="text" v-model.lazy="eventData.title">
<pre>{{ eventData.title }}</pre> <!-- 確認 -->

JavaScript

export default {
  data(){
    return{
      eventData:{  //eventDataプロパティにtitleの初期値を設定
        title:'',
      }
    }
  }
}

入力フォーム input (number)

HTML

<!-- v-model.numberでvalueを数値に固定 -->
<input id="maxNumber" type="number" v-model.number="eventData.maxNumber"> 
<p>{{ typeof eventData.maxNumber }}</p> <!-- 確認 -->

JavaScript

export default {
  data(){
    return {
      eventData:{
        maxNumber: 0,
      }
    }
  }
}

input (先頭と後尾の改行を取り除く)

HTML

<!-- v-model.trimで改行を取り除く -->
<input id="host" type="text" v-model.trim="eventData.host"> 
<pre>{{ eventData.host }}</pre>

JavaScript

export default {
  data(){
    return{
      eventData:{
        host: ''
      }
    }
  }
}

チェックボックス(単体)

HTML

<input type="checkbox" id="isPrivate" v-model="eventData.isPrivate">
<label for="checkbox">非公開</label>
<p>{{ eventData.isPrivate }}</p>

JavaScript

export default {
  data(){
    return{
      eventData:{
        isPrivate: false, //boolean型で返ってきます
      }
    }
  }
}

チェックボックス(複数)

HTML

<input type="checkbox" id=10 value="10代" v-model="eventData.target"> 
<label for="10">10代</label>
<input type="checkbox" id=20 value="20代" v-model="eventData.target"> 
<label for="20">20代</label>
<input type="checkbox" id=30 value="30代" v-model="eventData.target"> 
<label for="30">30代</label>
<input type="checkbox" id=40 value="40代" v-model="eventData.target"> 
<label for="40">40代</label>
<p>{{ eventData.target }}</p>

JavaScript

export default {
  data(){
    return{
      eventData:{
        target: [], //配列で指定
      }
    }
  }
}

ラジオボタン

HTML

<input type="radio" id="free" value="free" v-model="eventData.price">
<label for="free">無料</label>
<input type="radio" id="paid" value="paid" v-model="eventData.price">
<label for="paid">有料</label>
<p>{{ eventData.price }}</p>

JavaScript

export default {
  data(){
    return{
      eventData:{
        price: "free"
      }
    }
  }
}

セレクトボックス

HTML

<select v-model="eventData.location" multiple>
<option v-for="location in locations" v-bind:key="location">
{{ location }}</option>
</select>
<p>{{ eventData.location }}</p>

JavaScript

export default {
  data(){
    return {
      locations: ["東京", "埼玉", "千葉", "神奈川"],
       eventData:{
         location: []
       }
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのSFC(単一ファイルコンポーネント)+PHPでWebアプリケーションを構築する - フロントエンド環境編

はじめに

本記事は『フロントエンド環境構編』です。続編は『バックエンド環境編』をご覧ください。


Vue.jsのコンポーネントとは、HTMLのテンプレート(+CSSスタイル)に、JavaScriptのデータとメソッドが1セットになった画面構成部品の事です。SFC(単一ファイルコンポーネント)は、このコンポーネントを1つのファイルで定義する仕組みのことです(慣例的に拡張子を.vueとするファイルを作成します)。

詳細は、以下のページをご覧ください。
Vue.jsのSFC(単一ファイルコンポーネント)について

SFCを使うには、webpackやBrowserifyなどのビルドツールの導入が必須です。
本記事は、Vue.jsのSFCをwebpackでビルドできるように設定を行い、バックエンドにPHPを組み合わせた構成で、Webアプリケーションを構築する一連の流れを説明する記事です。

前置き

めちゃくちゃ端的に言うと、私の日々の仕事は、企業で利用するWebアプリケーションの開発です。

では、Webアプリケーションとは一体どのような要素で構成されるのでしょうか?
Webアプリケーションの構成要素を表現する際の一つの切り口として、フロントエンド・バックエンドという役割に分けて説明することができます。

フロントエンドは、ユーザーが利用する画面などを指します。これらは、HTML・JavaScript・CSSなどを用いて、ブラウザに画面として表示するわけですね。

バックエンドは、フロントエンドから送られてきたデータを処理する層です。
こちらの層では、PHPなどのプログラミング言語を用いて、フロントエンドから受信したデータをDBに登録・更新・削除したり、フロントエンドの要求に応じて適切な情報をDBから検索して、フロントエンド側に返却します。

このように、Webアプリケーションというものを作る際には、フロントエンドとバックエンドの両方が必要であり、どちらが欠けても成り立ちません。

hello-vuejs_front_back_image.png

※バックエンドでは、入力チェックしたり、はたまた帳票を出力するようなこともあるので、バックエンドの役割はシステムの内容によって多彩です。上図は簡略化して描いているので場合によっては不正確かもしれません。

長年、Webアプリケーションを開発してきたプログラマ(例えば10年選手のような人たち)は、どちらかと言うとバックエンド層を得意とする人たちが多いと考えます。私個人の話をすると、JavaでStruts全盛の時代に新卒でIT業界に飛び込んだ人間です。そのような時代を生きてきた人間として言えることは、昔ながらのWebアプリケーションと言うものは、フロントエンドに求められる表現は最小限なものでした。

その昔、IE6が主流だったWebアプリ開発の現場においては、JavaScriptの関数の利用はご法度(利用したとしても最小化すべし)の時代です。HTMLソースは全てバックエンドでレスポンスとして出力されブラウザはただそれを表示する。バックエンドにロジックが偏ることが当たり前で、それを疑いもしない時代でした。

ですが年を追うごとにコンピュータの性能は上がり、ユーザーインターフェースの表現の幅も増加の一途を辿り、フロントエンドに求められる要求も日を追うごとに強まる一方です。そしてそれを実現するための技術スキルも大幅にアップデートされているのが実情で、Webアプリケーションを開発する側の人間の知識もアップデートを求められていると言っても過言ではありません。

このように、フロントエンドの要求が高まるにつれ、開発効率向上のための(フロントエンド側で使用する)ビューフレームワークなるものが栄枯盛衰されてきました。その一つが、本記事で紹介するVue.Jsです。

Vue.Jsとの付き合いは、かれこれ1年以上2年未満です。Vue.Jsを学習していて思った事に、チュートリアルにはフロントエンドの事しか書かれておらず、バックエンド技術(PHPなど)との組み合わせ方法が掲載されていないことが、大きな不満であり戸惑いの元でした。

以下は、Vue.Jsの学習において感じた、難しいことや不満だったことです。

  • webpackって何だろう。バックエンドでもビルド(JavaとかC#の場合は)するのにフロントエンドでもビルドするの面倒だ(導入に対する精神的障壁の大きさがあり辛さを感じる)
  • SPA(シングルページアプリケーション)なんか作りたくない、普通のアプリが作りたいだけです。
  • Vue CLIをおすすめされたけど勝手にディレクトリ構成決められちゃう。どこにPHPモジュール置けばいいの?

学習の最終目標は、Vue.jsを業務プロジェクトへと導入することにあり、プロジェクトの導入を進めるには費用対効果を考えなくては行けません。ただ、「今流行っているから導入してみよう」という理由だけでは、後の世代に負の遺産を残してしまう事にもなりかねません。

このような点に真正面から向き合い、自分なりの答えを導いたのが本記事です。結論から言うと、僕はSFC(の利用に伴うwebpackの導入)に未来を見ました。

本記事が、堅牢さと枯れた技術をこよなく愛する硬派なバックエンドエンジニアの方々にもご満足いただけるような、Vue.jsの紹介記事になれば幸いです。

本記事をおすすめしたい人

以下に当てはまる方におすすめしたいです。

  • Vue.jsを多少なりとも利用したことがある人
  • Vue.js+SFCを使って一からWebアプリケーションの土台を作りたい人
  • でも、Vue CLIは使いたくない人(余分なものは入れたくない)
  • でも、Vue.jsのSFC(単一ファイルコンポーネント)は使いたい
  • JavaScriptのES6にそろそろ慣れておきたい人
  • Webアプリ開発にwebpackを導入することのメリットについて、理解しておきたい人

解説すること、しないこと

解説すること

  • npmで導入したパッケージについて
  • webpackのビルド設定内容とビルドの実施について
  • Vue.jsのSFCについて
  • ソース全体のディレクトリ構成について

解説しないこと

  • 各種プログラミング言語の構文の詳細など
  • 公式リファレンスに掲載されている基本的な事柄など

説明の流れ

Windows10で環境構築しました。

Macなどの他OSユーザーは、コマンドプロンプトをターミナルにするなど、適宜読み替えてください。
基本的にはどんなOSでも同じように実現できるはずです。

以下の大項目にそれぞれ分けて説明して行きます。

  1. フロントエンド側の環境構築
  2. バックエンド側の環境構築

注意
今回はフロントエンド側の環境構築のみの説明となります。バックエンド側の環境構築は次回の記事で説明します。

フロントエンド側の環境構築

ソースのルートディレクトリを決定する

本記事では、hello-vuejsをプロジェクト名とします。
例として以下のディレクトリをルートディレクトリとして話を進めます。
C:\hello-vuejs

HINT
分かりやすさ重視でCドライブの直下にしました。適宜変更してOKです。
ただし、日本語ディレクトリやスペース文字がディレクトリ名として含まれると、トラブルの元になのでなるべく避けることをおすすめします。

インストールライブラリのまとめ

フロントエンド側で必要なものは以下の通りです。

ライブラリ バージョン 説明
Node.js 最新 npm使用のためにインストール
webpack ^4.42.1 webpack本体
webpack-cli ^3.3.11 webpackコマンドセット
webpack-dev-server ^3.10.3 webpack開発サーバー
webpack-merge ^4.2.2 webpack設定ファイルのマージに使用
babel-loader ^8.1.0 ES6のトランスパイル
@babel/core ^7.9.0
@babel/preset-env ^7.9.0
css-loader ^3.4.2 CSSをjs上にロードするモジュール
file-loader ^6.0.0 ファイルをjs上にロードするモジュール
vue ^2.6.11 Vue.js本体
vue-loader ^15.9.1 SFCをjs上にロードするモジュール
vue-template-compiler ^2.6.11 Vue 2.0テンプレートをレンダリング関数にプリコンパイル

NodeJs(npm)で必要なパッケージを導入する

まず最初に、NodeJSをインストールするところから始めます。
Node.JSにはnpmというパッケージ管理ツールが付属しており、こちらを使用するためにNode.Jsをインストールします。

Vue.jsで『SFC(単一ファイルコンポーネント)』を使うには何はともあれ、NodeJsをインストールするところから始めてください。
以下のページから最新版をダウンロードしてインストールしましょう。

NodeJsのページ

HINT
パッケージ管理ツールは、今やモダンな開発には無くてはならないものになっています。
バックエンド側のパッケージ管理ツールには、PHPを使用するならComposer、ASP.NetならNuGetが該当します。パッケージ管理ツールは、著者の観測範囲では使用するプログラミング言語によって異なります。

npmの初期化

npmでパッケージを追加するには、該当ディレクトリで初期化が必要です。

コマンドプロンプトを開いて、C:\hello-vuejscdしましょう。
そして、最初に以下を実行してください。
色々聞かれますが、全てEnterを押して、最後にyesを入力すればOKです。

詳細が知りたい人は、ググってみてください。

コマンドプロンプト
npm init

npmでパッケージインストール

次に、以下を実行して行きます。

コマンドプロンプト
npm install -D webpack webpack-cli webpack-dev-server webpack-merge
npm install -D babel-loader @babel/core @babel/preset-env
npm install -D css-loader
npm install -D file-loader
npm install -D vue
npm install -D vue-loader vue-template-compiler

HINT
npm installコマンドは、パッケージをインストールするコマンドです。-Dオプションは開発時に必要なライブラリ(ビルドに必要なもので、リリースに必要なランタイムモジュールではないという意味)です。

ちなみに、上の例では、-gオプションを付けていません。そのため、全てローカル(C:\hello-vuejs\node_modules配下)にインストールされます。ローカルインストールすることで、ライブラリ同士の衝突の可能性を考慮せずに済むので、特別な理由が無ければローカルインストールが良いかと思います。

-Dは、--save-devのエイリアスです。
installは、iで置き換えることもできます。


全て実行し終えると、以下のようなファイルが出来上がっているはずです。

ファイル構成
c:\hello-vuejs
    + node_modules
    + package.json
    + package-lock.json

node_modulesには、インストールしたライブラリファイルが格納されています。こちらは、バージョン管理が不要なディレクトリです。

package.jsonには、npm initした際の各種設定と、何をインストールしたかの情報が記載されています。
package-lock.jsonには、インストール時の厳密なライブラリのバージョン番号のスナップショットが記載されています。
こちらの2ファイルはコミットが必要です。

他の開発者は、こちらのpackage.jsonまたはpackage-lock.jsonから、npm installを実行して必要なライブラリをインストールすることになります。
したがって、本節で実施した、npm initnpm install -D [ライブラリ名]は、一度限りの実行になります。

他の開発者は、バージョン管理ツールからチェックアウトしたソースに対して、npm installを実行するだけで必要なライブラリを揃えることができます。

package.jsonのscripts編集

package.jsonを編集します。

ファイル構成
C:\hello-vuejs
    + ...
    + package.json

修正箇所は、"scripts"の部分です。
"start""build"のコマンドを以下のように追加しておきます。
"test"は不要です。

package.json
{
  ...
  "scripts": {
    "start": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
  ...
}

このようにすることで、npm run [scriptsに定義したコマンド名]でコマンドを実行できます。
startを実行する際は、npm run startとコマンドプロンプトに打ち込むことで実行可能です。

startは開発時に使用するコマンドです。コーディングの前に本コマンドを実行し、自動ビルドやHMR・自動更新を有効にするというイメージです。
buildはリリース時に使用するコマンドです。bundle.jsという最適化されたファイルが出力されます。

各コマンド引数の--config [ファイル名]部分は使用する設定ファイル名の指定です。こちらは次の手順で作成します。

webpack設定ファイルの作成

webpackの設定ファイルを作成します。

webpackの説明は割愛します。もし、webpackについて詳しく知りたければ、"webpackの基本"のようなキーワードでググると記事がたくさん出てくるので、それらを参考にすると良いかと思います。ただし、本記事で使用しているwebpackはバージョンが4なので、参考にする際はwebpack4の記事を見るようにしてください。

ファイル構成は以下の通りです。
webpack.common.jsが全環境で共通となる設定ファイルで、devとprodがそれぞれの環境に対応した設定ファイルです。

ファイル構成
C:\hello-vuejs
    + ...
    + webpack.common.js ... 共通設定ファイル
    + webpack.dev.js    ... 開発時の設定ファイル
    + webpack.prod.js   ... リリース時の設定ファイル

続いて、設定ファイルの内容を以下に示します。
とりあえず、細かい説明は割愛します。雰囲気で読み取ってみてください。

webpack.common.js
// webpack common setting
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

const prefixUri = "/hello-vuejs";

module.exports = {
    resolve: {
        modules: [
            path.resolve('./src'),
            path.resolve('./node_modules')
        ]
    },
    entry: path.resolve(__dirname, 'src', 'main.js'),
    output: {
        path: path.resolve(__dirname, 'public'),
        filename: 'dist/bundle.js',
        publicPath: prefixUri + "/"
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            // this will apply to both plain `.js` files
            // AND `<script>` blocks in `.vue` files
            {
                test: /\.js$/,
                loader: 'babel-loader'
            },
            // this will apply to both plain `.css` files
            // AND `<style>` blocks in `.vue` files
            {
                test: /\.css$/,
                use: [
                    'vue-style-loader',
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin()
    ]
};
webpack.dev.js
// webpack development setting
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

const prefixUri = "/hello-vuejs";

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    // Configuration for dev server
    devServer: {
        // 生成されたファイルをディスクに書き込むかどうか
        writeToDisk: false,
        // ブラウザの表示有無
        open: true,
        // ブラウザの表示ページ
        openPage: prefixUri.substring(1) + '/index.html',
        // HMR (Hot Module Replacement) の有無
        hot: true,
        // Webページリロードの有無
        //inline: true,
        // Webサーバーの公開ディレクトリ
        contentBase: path.join(__dirname, 'public'),
        contentBasePublicPath: prefixUri,
        // Webサーバーの公開ディレクトリの監視有無
        watchContentBase: true,
        // Webサーバーの公開URL
        publicPath: prefixUri + "/"
    }
});
webpack.prod.js
// webpack production setting
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'production'
});

設定ファイルの作成は以上です。
一先ず、こういった設定でVue.jsのSFCがビルドできるようになったんだなー、と理解してもらえればと思います。

深堀したい場合は、公式サイトのリファレンスをご覧になると良いかと思います。

index.htmlの作成

index.htmlは、Webアプリケーションのフロントコントローラになります。
ユーザーが、hello-vuejsというWebアプリケーションにアクセスする際には、ブラウザで必ずindex.htmlを指定することになります。

ファイル構成
C:\hello-vuejs
    + ...
    + public
        + index.html

ファイルの内容は以下の通りです。コンテンツとしては空の状態に近いです。

特徴的なのは、<div id="app" v-cloak></div>という部分です。こちらは、Vue.jsのコンポーネントを表示する領域です。
また、<script src="./dist/bundle.js" ></script>は、webpackでビルドした結果のファイルをロードしています。

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

    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta http-equiv="Pragma" content="no-cache">
        <meta http-equiv="Cache-Control" content="no-cache">
        <meta http-equiv="Expires" content="Mon, 26 Jul 1997 05:00:00 GMT">
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale = 1.0, maximum-scale = 1.0, user-scalable = no">

        <title>Hello Vue.js</title>

    </head>

    <body class="layout-base">
        <div id="app" v-cloak>
        </div>
        <script src="./dist/bundle.js" ></script>
    </body>

</html>

main.jsの作成(webpackのエントリポイント)

webpackにおけるビルドを簡単に説明すると…

  1. エントリポイント(main.js)と呼ばれるファイルの内容を読み取る
  2. そこに書かれているimport構文からサードパーティライブラリとして何が使用されるのかを解析する
  3. それらのファイルをまとめて繋ぎ合わせ、最終的に一つのファイルに出力する(bundle.js)

以上のようになります。
ここで作成するmain.jsとはwebpackのエントリポイントのファイルの事を指します。

ファイル構成
C:\hello-vuejs
    + ...
    + src
        + main.js

main.jsで実施している処理は以下の通りです。

  1. Vue.jsをインポートする
  2. GETパラメータから表示対象のコンポーネントパス(componentPath変数)を取得
  3. コンポーネントパスに対応するモジュールを動的インポートする
  4. Vueインスタンスを生成して動的インポートしたコンポーネントを描画するように指示する

ポイントは、GETパラメータによる表示対象コンポーネントの変更動的インポートです。

main.js
// -----------------------------------------------------------------------------
// ライブラリのインポート
// -----------------------------------------------------------------------------
// VueJs
import Vue from 'vue';

// -----------------------------------------------------------------------------
// GETパラメータの取得
// -----------------------------------------------------------------------------
let queryObject = {};

if (window.location.search) {

    // 1文字目がクエスチョン'?'になっているので、substringで2文字目以降を取得する
    const queryString = window.location.search.substring(1);
    // '&'キーワードで分解する
    var parameters = queryString.split('&');

    for (let i = 0, ilen = parameters.length; i < ilen; i++) {

        // '='キーワードでキーと値に分解する
        var element = parameters[i].split('=');

        // デコードを忘れずに実施する
        var paramName = decodeURIComponent(element[0]);
        var paramValue = decodeURIComponent(element[1]);

        queryObject[paramName] = paramValue;
    }
}

// ------------------------------------------------------------------------------
// 動的インポートを実施
// ------------------------------------------------------------------------------
// URL指定例)
// http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Hello/Front/View/Hello
// http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Goodbye/Front/View/Goodbye
if (typeof queryObject['componentPath'] !== 'undefined') {

    // componentPathを以下のルールで変換
    // 例)
    //    /Func/Hello/Front/View/Hello
    //     ↓
    //   ./Func/Hello/Front/View/Hello.vue
    const componentPath = queryObject['componentPath'];
    const componentId = componentPath.substring(componentPath.lastIndexOf('/') + 1);

    const componentLoadPromise = import("./" + componentPath.replace(/^\//, "") + ".vue");

    componentLoadPromise.then(function (value) {
        // 動的インポート完了

        // VueのComponentのグローバル登録は、Vueインスタンス生成前に実施する必要あり
        Vue.component(componentId, value.default /* import関数の戻り値に default があるので、そちらを使用する(defaultエクスポート定義を読み込むという意味になる) */);
        // Vueインスタンスの生成
        const vm = new Vue({
            el: '#app',
            render: h => h(componentId)
        });
    });

} else {
    console.warn('componentPathパラメータが見つかりませんでした。');
}

コンポーネントの作成

コンポーネントは、HelloとGoodbyeの2つを作成することにします。

ディレクトリ階層が深く見えるかもしれませんが、今後PHPモジュールと絡ませる予定なので、このような階層にしました。
Funcは機能別にモジュールを配置するためのルートディレクトリです。
Frontはフロントエンドとバックエンドを区別するためにあえて作成したディレクトリです。

ファイル構成
C:\hello-vuejs
    + ...
    + src
        + Func
            + Hello\Front\View
                + Hello.vue
                + Hello.html
                + Hello.css
                + Hello.js
            + Goodbye\Front\View
                + Goodbye.vue
                + Goodbye.html
                + Goodbye.css
                + Goodbye.js
    + ...

特別説明するようなこともない、シンプルな内容です。

Hello.vue
<template src="./Hello.html"></template>
<script src="./Hello.js"></script>
<style src="./Hello.css" scoped></style>
Hello.html
<div class="hello-component" v-cloak ref="view">

    <div>
        This is Hello Component.<br>
        {{message}}
    </div>

</div>
Hello.css
.hello-component {
    color: blue;
}
Hello.js
export default {
    data() {
        return {
            message: 'Hello Vue.js'
        };
    },
    methods: {
    }
}
Goodbye.vue
<template src="./Goodbye.html"></template>
<script src="./Goodbye.js"></script>
<style src="./Goodbye.css" scoped></style>
Goodbye.html
<div class="goodbye-component" v-cloak ref="view">

    <div>
        This is Goodbye Component.<br>
        {{message}}
    </div>

</div>
Goodbye.css
.goodbye-component {
    color: red;
}
Goodbye.js
export default {
    data() {
        return {
            message: 'Goodbye Vue.js'
        };
    },
    methods: {
    }
}

動かしてみる

処理イメージは下図の通りです。

hello-vuejs_index_html_flow.png

C:\hello-vuejsで以下のコマンドを実行してください。すると、ブラウザが起動してページが表示されるはずです。

コマンドプロンプト
npm run start

最初は、コンポーネントの表示指定が無いので、以下のような白い画面が表示されるはずです。

hello-vuejs_screen_blank.png

この状態で、Helloコンポーネントを表示するためにURLに以下を指定します。
http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Hello/Front/View/Hello

hello-vuejs_screen_hello.png

この状態で、Hello.jsを開いて、dataのmessageを編集してみましょう。

Hello.js
...
    data() {
        return {
            message: 'こんにちは Vue.js'
        };
    },
...

すると、画面の内容が以下のように変化(リアルタイム)しました。

hello-vuejs_screen_hello_change.png

ちなみに、Goodbyeコンポーネントを表示する画面は以下のURLです。
http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Goodbye/Front/View/Goodbye

hello-vuejs_screen_goodbye.png

どうでしょうか。便利だと思いませんか?

webpack(正確には、webpack-dev-server)を利用する大きなメリットの一つに、HMR(Hot Module Replacement)があります。編集内容をリアルタイムで確認できるので、開発効率が向上すること間違いなしです。また、該当ファイルが保存された瞬間に自動でビルドが実行されるので、ビルドシステムが一つ余計に増えたからと言って、開発効率が落ちることはありません。

HINT
ファイルの変更内容によっては(JavaScriptコードの大幅な変更など)、HMRでコンテンツの置き換えができないことがあります。
その場合は、表示中のページがリロードされます。

リリースビルドの実行

C:\hello-vuejsで以下のコマンドを実行してください。すると、bundle.jsが生成されます。

コマンドプロンプト
npm run build
出力ファイル
C:\hello-vuejs
    + ...
    + public
        + dist
            + bundle.js
    + ...

HINT
npm run startの場合は、bundle.jsが出力されません。ビルド内容はメモリ上に保持されます。

最終的なファイル構成

ファイルの最終的な構成は以下の通りです。

ファイル構成
C:\hello-vuejs
    + node_modules               ... npmでインストールしたライブラリ(バージョン管理対象外とするディレクトリ)
    + public                     ... Webサーバーの公開ディレクトリ
        + index.html             ... フロントコントローラ
    + src
        + Func                   ... 機能別ディレクトリ
            + Hello\Front\View   ... Helloコンポーネント
                + Hello.vue
                + Hello.html
                + Hello.css
                + Hello.js
            + Goodbye\Front\View ... Goodbyeコンポーネント
                + Goodbye.vue
                + Goodbye.html
                + Goodbye.css
                + Goodbye.js
        + main.js                ... webpackエントリポイントファイル
    + package.json               ... npmに関する設定ファイル
    + package-lock.json          ... npm installのスナップショットファイル
    + webpack.common.js          ... webpackの共通設定ファイル
    + webpack.dev.js             ... webpackの開発用設定ファイル
    + webpack.prod.js            ... webpackのリリース用設定ファイル

フロントエンドのまとめ

ここまで読み進めて頂いた方、もしくは実践的に試して頂いた方なら、SFC(webpack)の凄さが実感できたのではないでしょうか。覚えることも多いですが実際に導入してみると、それを上回る恩恵があるかと思います。

今回作成したソースはGitHubにアップしておきました。以下のページにアクセスして確認してみてください。
hello-vuejsソース

ちなみに、フロントコントローラがindex.html(静的ページ)なので、GETパラメータの読み取りのみ可能で、POSTパラメータの読み取りはできません。そのため、動的コンテンツに対応したWebアプリケーション(Webアプリはそもそも動的コンテンツですよね…、という突っ込みもあるかと思いますが)を今回の構成で作成することはできません。静的なWebページならこちらの構成で作成可能です。

※シングルページアプリケーションなら、今回の構成でも構築可能かと思います(ページ間のパラメータ連携とか考えなくて良いはずなので)。

動的コンテンツに対応するには、今回作成したindex.htmlをindex.phpに変更して、こちらのファイルをフロントコントローラにする必要があります。
そちらに関しては、『バックエンド環境編』をご覧ください。

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

Vue Internals②watcher 2-2実際の動き

はじめに

前回はwacherの生成とwacther↔depsが追加されるタイミングについてみていきました。
今回は実際にmessageに新しい値を入れてどのような動きをするか見ていきましょう。

実際の動きを見ていく

vm._data._message="test"とconsoleに入力してデバッグを開始します。
するとmessageのsetterが起動してvm.messageが"test"になり、dep.notify()へ
messageSetter.png
dep.notify().png

まず一つ目のcomputedのwatcher.update()
wacther.update.png
computedのwacther.lazy=trueだったのでdirty.trueにするだけです。
二つ目の全体のwactherはqueueWactherに行きます。
ただ、処理が複雑なうえに、本筋とはほとんど関係ないのでここは飛ばします。
そのまま全体のwacther.run()→wacther.get()へ
wacther.run().png

wacther.get()周りの動きは前回やりました。
今回のpushTargetやらpopTarget()、dep.depend()はすでにmessageのdepが二つのwatcherに、wactherのdepsにはmessageのdepがもう入っているため特に何もしません。

全体のwatcherのgetterなのでvm._update(vm._render())へ

computedGetter2.png

comp_messageのgetterであるcomputedGetterが呼ばれ、computedのwatcherをdirtyに設定していたのでwacth.evaluate()へ

wacth.evaluate()→wacther.get()→comp_message()→messageのgetterの流れで進み、messageのgetterからvalueである"test"が返ります。
comp_message()からは"comp test"となって返り、
watcher.evaluate()に戻ってcomputedのwatcher.valueが"comp test"に。

そして

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"vue_example"}},[_v("\n  "+_s(comp_message)+"\n")])}
})

ここのcomp_messageが"comp_test"になり、後の処理は①astを中心にとほとんど同じで、違いはvm._updateのdiffアルゴリズムだけです。
このdiffアルゴリズムは④diffアルゴリズムでやっていきましょう。

ここまでで②wactherは終わりです。
次回からはv-系の処理について見ていきます。

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

Vue.jsの基本的な機能だけでTODOリストを作成する

See the Pen Vue.js practice20200404 by tseno (@tseno) on CodePen.

ソース

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

<head>
  <meta charset="UTF-8" />
  <title>TODO List 20200404</title>
  <style type="text/css">
    <!--
    .checked {
      text-decoration: line-through;
    }

    .v-enter-active,
    .v-leave-active {
      transition: opacity 500ms;
    }

    .v-move {
      transition: transform 1s;
    }

    .v-leave-active {
      position: absolute;
    }

    .v-enter,
    .v-leave-to {
      opacity: 0.0;
    }

    .v-enter-to,
    .v-leave {
      opacity: 1.0;
    }
    -->
  </style>
</head>

<body>
  <div id="app">
    <transition-group tag="div">
      <div v-for="(item, i) in items" :key="item.text">
        <label :class="{checked: item.check}">
          <input type="checkbox" v-model="item.check" />
          {{ item.text + '(' + item.dueDate.toLocaleDateString() + ')' }}
        </label>
        <button @click="onDelete(i)">X</button>
      </div>
    </transition-group>
    <input type="text" v-model="addText" />
    <el-date-picker v-model="addDate" type="date" placeholder="期限">
    </el-date-picker>
    <button @click="onClick" :disabled="addButtonDisabled">追加</button>
  </div>
  <script src="http://unpkg.com/vue/dist/vue.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script src="http://unpkg.com/element-ui/lib/umd/locale/ja.js"></script>
  <script>
    ELEMENT.locale(ELEMENT.lang.ja);
    var vm = new Vue({
      el: "#app",
      data: {
        addText: "",
        addDate: "",
        items: [{
            text: "旅行の予約をする",
            check: true,
            dueDate: new Date(),
          },
          {
            text: "領収書をまとめる",
            check: false,
            dueDate: new Date(),
          },
          {
            text: "レンタカーの手配",
            check: false,
            dueDate: new Date(),
          },
          {
            text: "天気予報チェック",
            check: false,
            dueDate: new Date(),
          },
        ],
      },
      computed: {
        addButtonDisabled: function() {
          return this.addText.length === 0 || this.addDate == null;
        },
      },
      methods: {
        onClick: function() {
          this.items.push({
            text: this.addText,
            check: false,
            dueDate: this.addDate,
          });
          this.addText = "";
          this.addDate = null;
        },
        onDelete: function(i) {
          this.items.splice(i, 1);
        },
      },
    });
  </script>
</body>

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

Vue Internals②watcher 2-1wactherとdeps

はじめに

Vue Internals①では全体の流れを見ていきました。
ただwatcher周りはほとんど触れていないのでVueにおいてどのような働きをするのかよく理解できませんでした。
Vue Internals②ではこのwatcherの働きとcomputed,watchについてをやっていきたいと思います。

Watcherについて

Vue Internals① 1-4mountComponentにてnew watcherのgetで実際のDOMをrenderingする過程を見ました。
watcherの役割はこのrenderingにあります。watcherdiagram.png

ただ、re-renderingの際、何がこのwatcherのトリガーとなるのでしょうか?
答えはdefinePropertyにあります。
Vue Internals① 1-1compile前まででmessageをObservable化したことを思い出してみましょう。
もしvm._data.message="test"みたいにするとmessageのsetが起動して、
setの処理中にdep.Notify()という項目があり、そこからwatcherにつながります。

今回調べたいことはwactherとdep関連なんですが、そのつながりがどこで作られるかです。
先にmessageに新しい値がsetされた時の動きの略図を見て、どこでwactherとdepのつながりが作られるかを実際のコード探索で見ていくことにします。
大まかな流れ.png
ここで全体のwactherとは1-4mountComponentで出てきたwactherのことでexpressionが"function () {
vm.update(vm._render(), hydrating);
}"となっているやつのことです。re-renderingを担っているので全体のwactherと呼んでいます。

今欲しい情報としてはmessageのDepのsubsにどこでwactherが登録されるか?、またwactherにどこでmessageのdepが登録されるかです。なのでこの後は
①computedのwactherが作られるところ
②messageのDepが作られるところ
③messageのDepにwactherが登録されるところ、またwactherにmessageのDepが登録されるところ
を見ていこうと思います。

今回のコード

<html>
<script src="https://unpkg.com/vue"></script>
<body>

<div id="vue_example">
  {{comp_message}}
</div>
</body>

<script>
var vue_example = new Vue({
  el: '#vue_example',

  data: {
    message: 'Hello Vue.js!',
  },
  computed:{
     comp_message(){
         return `comp ${this.message}`
     }
  }
})
</script>


</html>

Vue Internals①に細かい処理は譲り、ここではcomputed,watcher絡みのところを見ていきます。

init~compileまで

まずmergeOptionでoptionを作るときにstratsから対応する関数を取り出してその戻り値がoptionに入ります。
この時strats.computedは
strats.computed.png

結局option[computed]={comp_message:...}となります。

次はinitStateに行くと、initData、initComputedがあります。
initDataでは①でもみましたがdataをObservableにしています。
ここでdataとnew Observerの関係を図で見てみます。
observeDep.png

ここで分からなかったことはnew Observerの存在意義で、messageに新しい値を入れるときにはdefinePropertyで設定されたsetが使われて、そのままid:3のdepが使われて対応するwatcherに行くはずなので、new Observerを介していないんですよね・・・こうなるとid:2のdepの存在意義がわからない・・・
ともかく②messageのDepが作られるところはinitDataということですね。

initComputedに行きます。
computedWacther.png

ここでcomputed用のwactherを作ります。
ここでcomputed用のwactherはoptionでlazyがtrueになっていることに注意します。これが実際にmessageの値を変えるときに役になってきます。
vm.wacthersにcomputedのwactherを追加して、
lazy=trueなのでthis.get()はここでは発火しません。
initComputedに戻って、
wacthers["comp_message"]=new Wactherだったのでcomputedのwactherが追加され、さらに
watchers = vm._computedWatchersだったのでvm._computedWacthersにも追加されます。

defineComputedでは後にcomp_messageをgetするときのgetterを定義します。通常setterは定義されません。

defineComputed.png
createComputedGetter.png

comp_messageをgetしようとするとcomputedGetterが起動して、vm._computedeWacthers[comp_message]からwactherを取り出す流れで使います。詳しくは後ほど。

ここまででinitComputedは終わりです。①computedのwactherが作られるところはinitComputedでした。

ここから一気にmountComponentまで飛びます。AST関連は前回と大して違いがないので省略

mountComponent

renderWacther.png

全体のwactherを作成するところで、this.lazy=falseなのでthis.getが起動します。
ここでdepsにwactherが追加されるのですがpushTargetとpopTargetという関数が重要になってきます。
動きを図で見てみましょう。
pushAndpopTarget.png
pushTargetはwactherをtargetStackにpushして、DepTargetをそのwactherとします。
popTargetはtargetStackからpopしてDepTargetをStackの頂点のwactherとします。

DepTargetはglobalなので、どのdepからDepTargetを見ても同じwactherなので注意しましょう。
今のDepTargetが全体のwactherであることを念頭に置いて、

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"vue_example"}},[_v("\n  "+_s(comp_message)+"\n")])}
})

の実行まで飛びます。
ここでcomp_messageの値を得るときにcomputedGetterが起動するのでした。
computedのwacther.evaluate()に行きます。

computedWacther
Watcher.prototype.evaluate = function evaluate () {
    this.value = this.get();
    this.dirty = false;
  };

ここで2度目のpushTargetで今度はcomputedのwactherをpushします。動きは上記の図を思い出してください。
DepTargetはcomputedのwactherに。
computedのwactherのgetterはcomp_messageでした。

 comp_message(){
         return `comp ${this.message}`
     }

messageを取得するときに①astを中心にでもやった通り、messageのgetterが呼ばれることに注意してください。
messageGetter.png
さて、ここでDep.targetは今computedのwactherでした。
なのでmessageのdep.depend()が呼ばれます。

Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };

wactheraddDep.png
addDepによってmessageのdepのsubsにcomputedのwactherが追加されました。

③messageのDepにwactherが登録されるところはmessageのgetterからでした。

comuptedのwactherのgetに戻り、次はpopTargetです。
ここでDepTargetが全体のwactherに戻ります。

cleanupDepsに行きます。
computedWactherCleanup.png
ここでnewDepsからDepsに移ります。
newDepsはdep.depned→Dep.target.addDep内でwactherにnewDepsが追加されていました。

③のwactherにdepsを登録するのはcleanupDepsでした。
最初はnewDepsに入れてそれからDepsに移す流れです。

computedGetterに戻って、watcher.depend()へ

Watcher.prototype.depend = function depend () {
    var i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  };

今回はcomputedのwactherのdepsはさっき追加した。messageのdepのみなので、そのdep.depend()だけですね。

今のDep.targetが全体のwacherで会ったことを思い出すとmessageのdepのsubsに全体のwactherが追加されてこれでmessageのdepはcomputedと全体のwacther二つがあることになります。

まとめると、①全体のwacther.get()→②computedのwatcher.get()でcomputedGetterが呼ばれる、computedGetterではまず②でpushしたcomputedのwactherをmessageのdepに入れて、そのあとに①でpushしたwactherもmessageのdepに入れました。

wactherにdepsが追加されるのはdep.depnedでnewDepsとしていったん入った後、watcher.get()のcleanupDepsでnewDepsからDepsするときです。

図でもまとめてみましょう。depsの追加.png

後の工程は①ASTを中心にでやったことと同じです。
ここまでで、wactherの生成、deps↔wacherの追加されるタイミングがわかったと思います。
ようやく準備も整ったので次回実際にmessageに新しい値を入れた後の動きを見ましょう。

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

ESLintを導入してVSCode上で問題として検知する

概要

既存のVueプロジェクトにLintを導入したかったのでやってみました。

ゴール

  • ESLintを導入する
  • VSCode上でLintのエラーが表示される

環境

Version
macOS Catalina 10.15.4
node 10.17.0
Vue 2.5.18

ライブラリのインストール

npm i eslint --save-dev

初期設定

npx eslint --init

上のコマンドでconfigファイルが作成される。
選択肢はプロジェクトに合わせて以下のように選択した。
(TypeScript化したい。。)

? How would you like to use ESLint? To check syntax and find problems
? What type of modules does your project use? JavaScript modules (import/export)
? Which framework does your project use? Vue.js
? Does your project use TypeScript? No
? Where does your code run? Browser
? What format do you want your config file to be in? JavaScript
The config that you've selected requires the following dependencies:

eslint-plugin-vue@latest
? Would you like to install them now with npm? Yes

処理終了後、.eslintrc.jsが作成された。
json形式のほうが一般的なのかな?

VSCode

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

ESLint - Visual Studio Marketplace

必要なライブラリ

eslintのグローバルインストールが必要みたい

npm install -g eslint

ルールの設定

ルールを2つ追加してみる。

  • 文末のセミコロンは必須
  • 複数行の配列、オブジェクトでは最後のコンマは必須
eslintrc.js
module.exports = {
  ...
+ "rules": {
+   "semi": [ "error", "always" ],
+   "comma-dangle": [ "error", "always-multiline" ],
+ }
};

VSCodeを確認すると、これらのルールに反するものが問題として検知された!

グローバル変数への対応

グローバル変数がno-undefのエラーになってしまう。
グローバルであることを明示的に示すことで解決できた。

eslintrc.js
module.exports = {
  ...
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly",
+   "firebase": true,
+   "process": true,
  },
};

おわりに

GitHubActionsを使ったチェックとかやってみたい!

参考

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

Vue で Chrome の新しいタブを VS CODE っぽい WISYWIG Markdown Editor にする拡張機能をつくりました

フロントエンド開発が趣味のUIデザイナーです。
VS CODE と Chrome と Vue が好きなので、自然とできたやつを紹介させてください。

つくったもの

Tabulasa
https://psephopaiktes.github.io/tabulasa/
新しいタブをシンプルなMarkdownエディタにするだけのChrome拡張です。

こだわりポイント

  • デザイン (サイトみて:pray:)
  • ローカルホストに自動保存してくれてるので安心
  • Markdownで入力すればリアルタイムにWISYWIGで表示してくれる
  • :emoji:やチェックボックスなどGFMも対応
  • VS CODE のキーバインディングがかなり使える
  • Gistに投稿したりHTMLで保存したり地味に便利な機能

用途

  • EvernoteやNotionなどを開くまえに、とにかくいったんメモしたいとき
  • MTG中に議事録をとるとき
  • PRやSlack投稿前の下書き

たまに、ブラウザで data:text/html, <html contenteditable> を開いてメモ帳代わりにしている人がいますが、そういう人に特におすすめしたいです。てかChromeとVS CODE使ってる人は全員さわってみてほしいです。

技術的な知見

いろいろ試してみた知見をメモ程度に共有しときます!

EditorはCodeMirror

ブラウザ上で動くエディターのライブラリはいくつかあるのですが、今回はCodeMirrorという歴史ある定番のヤツを採用しました。だいぶレガシーになりかけているのですが、PCだけで使うならとても良く動きますしかんたんでした。スマホでも使おうとすると厳しいらしく、リニューアル版が開発中だったりします。

ほんとはMicrosoft公式のMonacoを使いたかったのですが、機能過多すぎるのと、デザインの自由度が全然無くてありがちになっちゃいそうでやめました。

Dark Mode 対応はやっぱりCSS変数が便利

他でも書いたのですが、WebでもOSのLight/Darkモードに応じてデザインを出し分けられるようになりましたね。以下の記事で書いた方法で色を変数管理しながら対応させました。

Webのダークモード対応をSCSS変数で管理する方法を考える - Qiita

今回ちゃんとダーク/ライト両方のデザインに対応させたのですが、これは最初にしっかり変数化して作らないと大変ですね。既存のページをあとから対応させると絶対抜けが出るだろうしやりたくないなあと思いました。

SVG画像の個人的にベストプラクティス

ReactやTypeScriptでSVGの画像やアイコンを扱う場合、どうやって扱っているでしょうか?

A. publicフォルダに.svg画像ファイルを直接<img>タグで普通に使う
B. Component(jsファイル)にして適宜importして使う
その他 ( HTML部分に直接<svg..を書く、objectタグなど

A,Bのメリデメは以下のような感じでしょうか

メリット:thumbsup: デメリット:thumbsdown:
A わかりやすい
更新が楽
SVGの数だけリクエストが増えるので遅くなる
CSSでSVGをいじれない
B インラインになるのでリクエストが減る
CSSで見た目を変えられる
StoryBookなどで管理できる
いちいち@importするのが面倒
更新がだるい

今回は折衷案としてvue-svg-inline-loaderを使用しました。簡単に言うと<img svg-inline src="@/assets/icon/about.svg" />と書くとビルド時にインラインに変換してくれるパッケージです。

個人的には↑の両方良い所どりなので、しばらくこれを使っていこうかなと思います。(PUGには非対応...)

その他の雑感想

  • pugで書くとコード量めっちゃ減るけど、@include使えないしLinterも効かないのはつらみ
  • TypeScriptいちおう使ったけどぜんぜんわからん
  • 個人開発だとデザインファイルもドキュメントもタスクチケットも全部GitHubにまとめるのが便利ですね

これからの予定

Issues · psephopaiktes/tabulasa

  • こまかいUXのブラッシュアップ
  • Vue3で作り直す
  • (需要ありそうなら)VimとかAtomのキーバインディングに対応

ただしばらくはTRPG用のアプリを作る予定なので、こっちの更新は遅くなるかもです :bow:

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

Vue.jsのthis変数を可変的、動的に変更する

Vue.jsのthis変数を可変的に変更したいときがあったけども、ぐぐっても出てこなかったのでメモ。

.を付けずに、[]を使用して表現する。

vue.js
data() {
  return {
    isCheck: false
  }
},
methods: {
  CheckFunc: arg => {
    // 正解例
    // this.isCheckと同義
    this[arg] = true;

    // NG例
    this.[arg] = true;
  }
}

実は公式リファレンスにサラッと書いてある。
https://jp.vuejs.org/v2/cookbook/adding-instance-properties.html

つまみ食いしながら触っているとこういう事が起こりますね。

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