20190811のvue.jsに関する記事は10件です。

create-nuxt-appで静的サイトを作るためにセットアップしたこと

やりたいこと

Nuxt.jsを使って静的サイトを作りたい。(今流行りのStatic Siteをgenerateしたい。)
つい最近まで素のHTMLとCSS(Scss)を書いていることが多かったけど、いろんな局面でNuxtの1つや2つくらい使えると便利だと思い、頑張ってもろもろを勉強しています。

これまでの自分のやり方で、今後Nuxtでも実現したいこと

  • リセット系cssを読み込む
  • 共通scssを読み込む(フォントなどの指定)
  • 共通scss変数を読み込む(色やブレイクポイントの数値など)

assets配下にcssscssフォルダを作成して、その中にこれらのファイルを入れます。

:fire: リセット系CSSを読み込む

nuxt.config.js内にcssという指定があると思うので、そこに読み込むファイルを追加する。

nuxt.config.js
/*
 ** Global CSS
 */
css: ['~/assets/css/ress.css']

※ちなみにリセットにはress.cssを使っています。
参考:2019年版!おすすめのリセットCSSまとめ - ress.css

:fire: 共通SCSSを読み込む(フォントなどの指定)

参考:API: css プロパティ - Nuxt.js

別途パッケージをインストールすれば、先程と同様にnuxt.config.js内のcssに書くことで認識してくれます(CSSとは名ばかりSCSSも読んでくれる)

パッケージのインストール

npm i -D node-sass sass-loader

nuxt.config.jscssに追記

nuxt.config.js
/*
 ** Global CSS
 */
css: ['~/assets/css/ress.css', '~/assets/scss/base.css']

:fire: 共通scss変数を読み込む

参考:Nuxt.jsでグローバルSass変数を使いたい - Qiita

あれ、base.scssと一緒に読み込まない(nuxt.config.jscss内に書かない)のは何でなんでしたっけ…?(前にどっかで読んだけど正確に覚えてない…多分重複して読み込まれるスタイルが発生するから…?)

とりあえず別途ミドルウェアを使用します。
いろんな種類があるぽいけどここでは@nuxtjs/style-resourcesを使用します。

インストール

npm i -D @nuxtjs/style-resources

nuxt.config.jsmoduleに追記

nuxt.config.js
/*
 ** Nuxt.js modules
 */
modules: [
  //他のモジュール設定
  '@nuxtjs/style-resources' //追記
]

nuxt.config.jsstyleResourcesを追記

nuxt.config.js
//buildとかの他の設定と同じ階層に追記する
styleResources: {
  //読み込みたいファイルを指定
  scss: ['@/assets/scss/variables.scss']
},

こまってること[WIP]

  • LinterやFormatterが何かとつらみ

「きれいなコードを書こう!」と意気込んで、LinterやFormatterを有効にすると、結構きびしくエラーを吐かれるので「もうちょっと素早く作業を進める予定だったのに…」みたいなことになりがちです。vscodeのformatOnSaveの設定と、lint --fixの自動実行みたいなところをうまくミックスしたいのですが、まだできていません…

おわりに

create-nuxt-appのようなボイラープレートを使っていれば自分で設定することなんてない!(どや)」なんて甘く見ていたんですが、意外と追加で設定する必要のある項目が多かったので、自分のメモ書きとして初めてQiitaの記事を書いてみました。今後も無理をせずに自分にとって有益な情報を発信していきたいとおもっとりゃす。

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

Vuetifyが2.0にアップデートして、ようやくgridsome.jsへの導入方法が分かったので共有してみたり

参考

https://github.com/gridsome/gridsome.org/pull/225/commits/96442da8cd154ae19d1eb6cabb9ee20cfad978fd

まもなく、公式にアップされそう
とりあえず、半分は自分用のメモという事で

めちゃくちゃ迷走したが、結局何の事は無い

appOptionsに追加するだけだった

main.js
import DefaultLayout from '~/layouts/Default.type1.vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import colors from 'vuetify/es5/util/colors'



export default function (Vue, { router, head, isClient, appOptions }) {

  // use
  Vue.use(Vuetify)

  appOptions.vuetify = new Vuetify({
    customVariables: ['~/assets/css/variables.scss'],
    theme: {
      dark: false,
      themes: {
        dark: {
          primary: colors.grey.darken4,
          accent: colors.shades.black,
          secondary: colors.amber.darken3,
          info: colors.teal.lighten1,
          warning: colors.amber.base,
          error: colors.deepOrange.accent4,
          success: colors.green.accent3
        }
      }
    }
  })

(省略)......

}

gridsome.server.jsも少しいじる

その前にwebpack-node-externalsモジュールをインストール

npm i webpack-node-externals
gridsome.server.js
const nodeExternals = require('webpack-node-externals')
// Server API makes it possible to hook into various parts of Gridsome
// on server-side and add custom data to the GraphQL data layer.
// Learn more: https://gridsome.org/docs/server-api

// Changes here require a server restart.
// To restart press CTRL + C in terminal and run `gridsome develop`

module.exports = function (api) {

  api.chainWebpack((config, { isServer }) => {
    if (isServer) {
      config.externals([
        nodeExternals({
          whitelist: [/^vuetify/]
        })
      ])
    }
  })

  (省略)......
}

こんだけでOKのようです!
sass-loaderとかnode-sassはいる

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

【Vue.js】Vue CLIでVue.jsを動かす〜プロジェクト作成まで

Vue.jsとは

  • JavaScriptのフレームワーク。
  • どのような規模・段階のアプリケーションにも対応することができる(プログレッシブフレームワーク)
  • ページの一部で部分的に使用することもできるし、大規模なSPA(シングルページアプリケーション)を構築することもできる。

プログレッシブフレームワークとは、Vue.jsの生みの親であるEvan You氏の提唱する概念です。

参考記事

Vue.jsについて学習してみた <基礎編>
Vue.js概要?

Vue CLIとは

  • 迅速なVue.js開発を支援するためのコマンドラインインターフェイス。
  • Vue.jsを利用した開発には、様々なパッケージやツールについての知識・複雑な設定が必要になる。それを自動化し、開発環境の構築を補助してくれる。
  • 個別に公開されているパッケージ(ビルドツール、コンパイラなど) の集合体でもある。Vue CLIを利用することにより、そこから必要な機能だけを使うことができる。

参考記事

公式サイト(英語)

インストール

あらかじめディレクトリを作成し、初期化しておきます。(今回初期化の必要はないかもしれませんが、念の為。)

$ mkdir vue_cli_test
$ cd vue_cli_test
$ npm init

npmによるインストール

グローバルインストールで行っています。

$ npm install -g @vue/cli

加えて、@vue/cli-initもインストールします。

$ npm install -g @vue/cli-init

@vue/cli-initのインストールがされていない場合、プロジェクト作成のコマンドnpm init実行時に以下のエラーが発生します。

Command vue init requires a global addon to be installed.
Please run npm install -g @vue/cli-init and try again.

グローバルインストールの場合、パッケージはnpm list -gで確認できるディレクトリ配下のnode_modulesディレクトリにインストールされます。

$ npm list -g
/Users/yuki/.nodebrew/node/v12.7.0/lib

パッケージ名を指定して確認することもできます。

$ npm list -g @vue/cli
/Users/yuki/.nodebrew/node/v12.7.0/lib
└── @vue/cli@3.10.0 

環境変数の設定

このままだとvueコマンドが使えないため、.bash_profileにパスを設定します。
まず、コマンドがどこに存在するのか確認します。

$ npm bin -g
/Users/yuki/.nodebrew/node/v12.7.0/bin

.bash_profileに追記します。

$ vi ~/.bash_profile
export PATH="$HOME/.nodebrew/node/v12.7.0/bin:$PATH"

反映させます。

$ source ~/.bash_profile

Vue.jsのバージョン確認

vueコマンドが通るようになったので、Vue.jsのバージョンを表示してみます。
確認ができれば、Vue CLIのインストールは成功です。

$ vue -V
3.10.0

プロジェクト作成

npm init テンプレート名 プロジェクト名でプロジェクトを作成します。

今回、テンプレートはwebpackを指定します。

テンプレートの種類については、こちらの記事が参考になります。
Vue-cli(webpack)解剖 ーディレクトリ構成ー

$ vue init webpack my-app

いくつか質問をされます。
今は余分な機能を入れたくなかったので、以下の質問はNoにしました。

// SPA構築に必要なVue.js公式ルータ
? Install vue-router? No
// 構文チェックツール
? Use ESLint to lint your code? No
// テストツール
? Set up unit tests No
// テストツール
? Setup e2e tests with Nightwatch? No

プロジェクト名であるmy-appディレクトリが作成され、必要な機能がインストールされます。
最後の方にあるメッセージの通り、my-appディレクトリに移動します。

To get started:

  cd my-app
  npm run dev
$ cd my-app

次にnpm run devでサーバを起動します。
devというエイリアスコマンドは、プロジェクト名のディレクトリ/package.jsonscriptで設定されています。

デフォルトでは以下の設定になっています。
npm startnpm run startでもサーバを起動することができるようです。

myapp/package.json
"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "build": "node build/build.js"
},
$ npm run dev

表示されるメッセージの通りhttp://localhost:8080にアクセスして、Vue.jsのデモ画面が確認できれば成功です。

Your application is running here: http://localhost:8080
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【読切】非同期処理の沼~Vue.js Django編~

あらすじ

2019年8月10日。私はVue.jsとDjango REST Frameworkを使い、ある業務システムの画面開発を担当していた。
フレームワークなんて代物はまだ触り始めて2,3か月。とは言えどこを触れば何が変わるか多少は理解してきている自負もあった。
そんな良くも悪くも弾みのついていた私を戒めるかのように、単体テスト中、そいつは突然現れた。

出会い

『更新中の…ステータスが…… エラーコード:409』

「(動作確認では何度も正常に作動していた機能開発...そんなはずは...)」
NG判定とエラー内容をテスト記録として記述する。

"""
HTTP 409 Conflict はリクエストが現在のサーバーの状態と競合したことを示すステータスコード。

競合は PUT メソッドを使用したリクエストのレスポンスで最も発生しやすい。例えば、サーバーにすでに存在しているファイルよりも古いバージョンのファイルをアップロードした際に409の応答が返され、バージョン管理システムの競合が発生する可能性がある。
"""
409 Conflict

困った困った。なけなしの知識と経験であれやこれやと思考した。
そして画面の再描画後、再度そいつは現れた。

『更新中の…ステータスが…… エラーコード:409』

非同期処理の沼

「ここか。」
答えに辿り着くのに長くはかからなかった。

heavyHoge.ts
 // post(url, data, loading)
 // url : 呼び出し先のURL
 // data : REST側に送りたいデータ
 // loading : 処理終了までローディング画面を表示

 // 重ための処理
  private async heavyProcess(): Promise<void> {
    const payload = '画面から送る何かのデータ群';
    this.$http.post(url='hoge-url1', data={'payload': payload}, loading=false);
    this.$http.post(url='hoge-url2', data={'payload': payload}, loading=false);
    // 画面の更新処理
    this.onShown();
  }

ボタンを押下して、この処理が走っている間に他の作業も進められるようにしたい。数十秒~数分なんて流石に待っていられない。
そのような声は当然にあり、この処理は上記の通り非同期処理で実装していた。
ここでの2つのhttp.post(以下、処理①と処理②)は類似処理を行っているが、具体的には下記の処理を行っている。

1.画面内のデータを取得しREST側へPOST
2.取得したデータの条件が合えば、外部APIへ有料の関連データを取得
3.種々のCRUD処理を行う

更にエラーの発生パターンについて調べたところ、大きく下記の2つであった。

1.処理①が上記1~3を終えて、戻り値を返して処理②を行おうとした直後
2.処理①のみor処理②のみが走るように画面側で予め条件設定し、1度目の処理を終えた直後に同じ処理のトリガーを引いた直後

つまり、処理量の多さにCRUD処理が実際には完了しきいっていない状態の時に再び処理①②を行うことでエラーが起きていた。
それを示すように awaitを付記するとloading画面は処理終了まで続くがエラーは消える。(当然っちゃ当然だが)

heavyHoge.ts
 // post(url, data, loading)
 // url :
 // data : 何かのデータ群
 // loading : 処理終了までローディング画面を表示

 // 重ための処理
  private async heavyProcess(): Promise<void> {
    const payload = '画面から送る何かのデータ群';
    await this.$http.post(url='hoge-url1', data={'payload': payload}, loading=false);
    await this.$http.post(url='hoge-url2', data={'payload': payload}, loading=false);
    // 画面の更新処理
    this.onShown();
  }

とにかく、今回のエラーの発生要因やその背景なんかを整理すると
・外部APIが有料ということもあり、連続or多数の本チャンデータ取得パターンの確認を怠った。
・1度のボタン押下で行う非同期処理を2つに分けてしまった。
 (※片方は他画面で使用しているViewに飛んでいた為、このような実装になった)
・REST側で各処理を行うViewを呼び出す為の、共通クラスを用意しなかった。
 (※やってることは2個目と同じ)

しかし、上記の課題を全てクリアして当初の設計通り実装しようとしても、まだエラーの発生パターン2が解決しない。
そう、ユーザーが短時間に連続で同じ処理を行おうとした時だ。完全に非同期処理の沼に嵌まってしまったのである。

2019年8月11日。27歳の誕生日を迎えた今日、未だ解決策は見つかっていない。

To Be Continued

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

Vuetify のアップデート(v1.5.x => v2.x) with TypeScript and Webpack

本記事の立ち位置

ここの拡張版。

VuetifyとはVue.js用のUIフレームワークです。

相違点

  • vue-cli を利用しない
  • packagewebpackを更新してアップデート
  • サンプルプログラムを抜粋して記載

How to

1. packageのアップデート

  • npm updateを利用してもvuetifyなどの一部パッケージが最新版にならない場合があります。これは既存のアプリを破壊(動かなく)してしまう可能性があると判断されているからです。
  • それを抑えてなお破壊的な更新を行いたい場合はncuを利用しましょう!
$ npm update # まずは普通にアップデート
$ ncu # アップデートできるパッケージの確認
$ ncu -u # 破壊的アップデート

2. webpackの更新

  • vuetifyをコンパイルするためにvuetify-loaderをインストールします。
  • webpack-cli
# 以下はやってない人
$ npm i -g webpack webpack-cli 
$ exec $SHELL -l # コマンドプロンプトの再起動(linux, mac)
$ npm i -D vuetify-loader
webpack.config.js
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')

let baseWebpack = {
    /* 略 */
    plugins: [
        new VuetifyLoaderPlugin()
    ],
    /* 略 */
};

module.exports = baseWebpack

3. Vuetify の読み込み

src/pluginsは作成する必要はありませんが、ライブラリは別ファイルで管理しておくと可読性が良くなります。

tsconfig.jsonへの設定も忘れずに

tsconfig.json
{
  ***
  "types": [
    "vuetify"
  ],
  ***
}     
src/plugins/vuetify.ts
import Vue from 'vue'
import 'vuetify/dist/vuetify.min.css'
import Vuetify from 'vuetify/lib'

Vue.use(Vuetify);

/*
// directiveも利用する場合
import { Touch } from 'vuetify/lib/directives'

Vue.use(Vuetify, {
  directives: {
    Touch
  }
});
*/

export default new Vuetify({
  icons: {
    iconfont: 'fa'
  }
});
src/main.ts
import Vuetify from './plugins/vuetify';
import App from './components/App'

new Vue({
    vuetify: Vuetify,
    template: '<App/>',
    components: {
        App
    }
}).$mount('#app')

以上!

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

Vue.js超基本の使い方まとめ

最近やっているプロジェクトでjQueryを使ってたのですが、jQueryの面倒さに発狂したくなってしまい、なんとかならないものかと思っていたところ、Vue.jsに出会いましたので学習をはじめました。

参考

今回udemyを一通り受講し、振り返りをしているため非常に参考とさせていただきました。非常に分かりやすい構成になっているため、初めて学習を始める人はまず受講してみることをオススメ致します。

Vue.jsの基本構文

データバインディング

Mustache構文

Vue.jsではMustache構文を使うことで、簡単にVueインスタンスデータとHTMLを結びつけることができます。
このデータの結びつけのことをデータバインディングといいます。

下記の例を表示すると {{ message }} にはHello Vue.js!が表示されます。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Vue Sample</title>
  </head>
  <body>
    <div id="example">
        {{ message }}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  </body>
</html>
main.js
var app = new 
Vue({
  el: "#example",
  data: {
    message: "Hello Vue.js!"
  }
});

v-bind

簡単なテキストのデータバインディングであればMustache構文を使えましたが、DOM要素の属性をデータバインディングする場合にはMustache構文はエラーとなり使えません。

※以降Vueに関わる部分のみHMTLを記載

index.html
<div id="example-error">
  <a href="{{ url }}">Google</a>
</div>
main.js
var app = new Vue({
  el: "#example-error",
  data: {
    url: "https://google.com"
  }
});

そこで使用するのが v-bindディレクティブ です。
v-bindディレクティブ を使えばDOM要素の属性を動的に切り替えることができます。

例1:href属性のデータバインディング
下記例だとhref属性のurlにGoogleのURLがデータバインディングされます。

index.html
<div id="example1">
  <a v-bind:href="url">Google</a>
</div>
main.js
var app = new Vue({
  el: "#example1",
  data: {
    url: "https://google.com"
  }
});

例2:value属性のデータバインディング
下記例ではinputタグのvalue属性がデフォルトでおはよう!と表示されます。

index.html
<div id="example2">
  <input v-bind:value="message">
  <p>{{ message }}</p>
</div>
main.js
var app = new Vue({
  el: "#example2",
  data: {
    message: "おはよう!"
  }
});

しかし初期値のおはよう!は正しく表示されていましたが、文字を書き換えた時にpタグが書き換わっていなかったことに気づくと思います。

これはv-bindによるデータバインディングは

 ・jsで更新したdataはテンプレートに更新されるが、
 ・テンプレート側で更新してもdataが更新されない

という性質があるためです。そこで登場するのが、v-on メソッドです。

v-on

v-onメソッドはイベント処理をするためのディレクティブです。
input要素のテキストエリアに入力する度にinputイベントが発生するので、v-bindに追加でv-onを追加してdataを更新する処理を追加します。

index.html
<div id="example">
  <input 
    v-bind:value="message"
    v-on:input="message = $event.target.value"
  >
  <p>{{ message }}</p>
</div>
main.js
var app = new Vue({
  el: "#example",
  data: {
    message: "おはよう!"
  }
});
  1. 初期値でmessageにおはよう!が入力されています
  2. そのためMustasche構文でかいているpタグにもおはよう!が表示されます
  3. 入力欄におはよう!の!を削除するとinputイベントが発生し、messageを書き換えます
  4. するとMustasche構文でかいているpタグにもおはように更新されます

このようにjsからテンプレートのデータを更新するだけでなくく、テンプレートからもデータを更新することを 双方向データバインディングといいます。下記の2行は全く同じ表現で、どちらもmessageに対して双方向データバインディングすることができます。

v-model

v-bindとv-onを組み合わせた双方向データバインディングの例を示しましたが、これはv-modelというディレクティブで表現することができます。

  // どちらも同じ
  <input v-model="message">
  <input v-bind:value="message" v-on:input="message = $event.target.value">

v-bindとv-onの省略記法

v-bindとv-onは頻繁に使用するため省略記法があります。

ディレクティブ 完全な構文 省略記法
:v-bind <a v-bind:href="url"> ... </a> <a :href="url"> ... </a>
:v-on <a v-on:click="func"> ... </a> <a @click="func"> ... </a>

繰り返し処理

v-for

v-forを使えば繰り返し文を使うことができます。
またv-for="(user, index) in users"とすることで配列のキーを取り出すことも可能です。

リストの例
<ul id="example">
  <li
   v-for="(user, index) in users"
  >
    {{ member }}
  </li>
</ul>
var app = new Vue({
  el: "#example",
  data: {
    users: ["佐藤", "鈴木", "山田"]
  }
});

表示・非表示の切り替え

v-if, v-if-else, v-else

v-ifディレクティブではブロックを条件に応じて描画することができます。

<div id="app">
 <div v-if="toggle">
    <p>Hello</p>
  <div v-else>
    <p>World</p>
  </div>
</div>
var app = new Vue({
  el: '#app',
  data: {
    toggle: true // 切り替える
  }
})

v-show

v-showディレクティブは属性値がtrueとなる場合に要素を表示します。

<div id="app">
 <div v-show="toggle">
    <p>Hello</p>
  </div>
</div>
var app = new Vue({
  el: '#app',
  data: {
    toggle: true // 切り替える
  }
})

v-ifとv-showの比較と使い分け

比較

v-ifとv-showは表示・非表示の方法に明らかな違いがあります。

v-if v-show
切り替え方法 ブロックごと作成・削除 ブロックを残したまま表示・非表示を切り替え
初期描画コスト 低い 高い
描画切り替えコスト 高い 低い

v-ifは非表示にする時にブロックごと削除します。
スクリーンショット 2019-08-11 12.45.09.png

一方でv-showは非表示にする時にブロックに対してCSSのdisplay:noneを使って切り替えます。
スクリーンショット 2019-08-11 12.44.03.png

使い分け

v-if
 ・ 初期描画コストが低く、描画切り替えのコストが高い
 ・ そのため切り替えの頻度が低いなら、初期描画コストの低いv-ifを使用する
v-show
 ・ 初期描画コストが高く、描画切り替えのコストが低い
 ・ そのため切り替えの頻度が多いなら、描画切り替えコストの低いv-ifを使用する

算出プロパティ

computed

算出プロパティは関数で算出したデータを返すことができるプロパティのことです。要はgetterやsetterの役割だと思ってもらえれば良いかと思います。

<div id="app">
  {{ reverseMessage }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: "Hello world!"
  },
  computed: {
    // data:messageの文字列を反転させる
    reverseMessage: function() {
      return this.message.split('').reverse().join('') 
    }
  } 
})

算出プロパティとメソッドの比較

先ほどの例だと、メソッドを使っても同じことが実現できます。

<div id="app">
  {{ reverseMessage }}
  {{ reverseMessageMethod() }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: "Hello world!"
  },
  computed: {
    reverseMessage: function() {
      return this.message.split('').reverse().join('') 
    }
  },
  methods: {
    reverseMessageMethod: function() {
      return this.message.split('').reverse().join('') 
    }
  } 
})

そこで算出プロパティとメソッドの違いをまとめると3点の違いがあります。

算出プロパティ メソッド
記法 ()不要 ()必要
使い方 getter, setter getter
キャッシュ される されない

算出プロパティのキャッシュ

算出プロパティとメソッドの使い分けとしてキャッシュの有無があります。
Mathのランダム関数を使ったときの例を用いて説明します。

<div id="example">
  メソッド
  <ol>
    <li> {{ getDate() }}</li>
    <li> {{ getDate() }}</li>
    <li> {{ getDate() }}</li>  
  </ol>
  算出プロパティ
  <ol>
    <li>{{ date }}</li>
    <li>{{ date }}</li>
    <li>{{ date }}</li>
  </ol>
</div>
var app = new Vue({
  el: "#example",
  computed: {
    date: function () {
     console.log("computed");
      return Math.random();
    }
  },
  methods: {
    getDate: function () {
     console.log("methods");
      return Math.random();
    }
  }
});
出力結果
メソッド
0.523744236259859
0.29754866940841374
0.3024997149055251

算出プロパティ
0.19494595412700444
0.19494595412700444
0.1949459541270044

このように同じMath.random()を使っても、算出プロパティは全て同じ値に対してメソッドは全てバラバラの値になりました。

スクリーンショット 2019-08-11 14.42.04.png
また算出プロパティは描画は3度していますが、コンソールログには1度しかログが出ていません。

算出プロパティとメソッドの使い分け

算出プロパティ
 ・ getter以外にもsetterを定義したい場合
 ・ キャッシュを効かせて、処理を高速化させたい場合

メソッド
 ・ あえて処理を高速化させたくない場合

さいごに

今回初めてVue.jsを使ってみましたが、jQueryのように直接DOM操作をするわけでなく、少ない量でかつわかりやすくDOMの操作をすることができ非常に期待が高まりました。
使い分けに関する情報など、初学者に少しでも役に立てば幸いです。

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

スロットコンテンツの書き方メモ

3連休だしVueを1から初めて遊んでみようと思った。
その中でスロットコンテンツを使ってコンポーネントの差し替えを行おうとした。
参考書には正しい例しか載っていないので、実験がてら試したことの考察

Vueの定義

vue01.vue
var headerTemplate = `
  <div id="test">
    <slot name="header">No Title</slot>
  </div>  
`
Vue.component('page-header', {
  template: contentTemplate
})

失敗例

index01.html
  <page-header>
    <span><div slot="header">hoge</div></span>
  </page-header>
<div id="test">No Title</div>

成功例

index02.html
  <page-header>
    <span slot="header"><div>hoge</div></span>
  </page-header>
<div id="test"><span><div>hoge</div></span></div>

考察
挙動から読み取れる限り下記のようなロジックがありそう。
① コンポーネントタグはVueで定義したコンポーネントを呼び出すだけで、その間にdiv等のタグを含めてもロストしてしまい、テンプレートがそのまま表示される。
② スロットコンテンツを使えば、コンポーネントタグ内の情報をVue側に渡せるので、それはロストせずテンプレートへの上書きを行う。

失敗例を見る限り、コンポーネントタグの最初のタグがslot属性を持っていなければ、その下のネストのタグがslotを持っていても②のロジックで判断されず、システム側がコンポーネントタグ配下のタグは①のロジックで扱っていいものだと認識して、slotをロストさせてしまっているのではないだろうか。
# わざわざ失敗例のようなタグ構成にする必要は何もないとは思いますが…

Vue有識者の方、もしよろしければアドバイスをいただけると助かります。

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

Vue.jsでサクサク!SVG内に線を引いてみる

はじめに

前回の記事続きです。
SVGとVue.jsを組み合わせて今回は、Pathを使って線を引いてみるという
処理を実装して見たいと思います。

前回の記事と組み合わせることで、
マインドマップのようなWebアプリも作れるかもしれないです。
(その場合はpath要素と図形要素を保持して、線を設定する形になると思います。)

aperture-video-95d3487f-079b-43c8-bca2-7f1eda3f892e (1).gif

早速コードを見ていきましょう!

Path要素を入れる

まず、線を表現するのに必要なPath要素を、
SVGタグ内に記載いたします。
v-model内にリストで表現し、線でつないだ分だけ可変的に生成できるようにします。

線を引いている時(確定前要素)と、線を引いた後(確定後要素)で、
Pathを分けております。
今回オプション要素で、線の太さや、色を全体に可変的に適用する設定もおまけで入れてます。

SVGDemo.vue
<template>
  <div class="container">
    <div class="setting-box">
      <!-- 色を変えるとこ -->
      <p></p>
      <input type="color" v-model="strokeFill" />
      <!-- 線の太さとか -->
      <p>線の太さ</p>
      <input type="range" v-model="strokeWidth" />
    </div>
    <!-- SVG定義 -->
    <svg :width="width" :height="height" :viewBox="viewport" @wheel="zoompan" @mousedown="lineNode($event, idx)"> 
      <!-- 線を引いた後 -->
      <path :d="path"
            fill="none"
            :stroke="strokeFill"
            :opacity="strokeOpacity"
            :stroke-width="strokeWidth"
            stroke-linecap="round"
            v-for="(path, idx) in pathList" :key="idx"
            v-if="pathList.length > 0"/>
      <!-- 線を引いている最中 -->
      <path :d="pathTemp"
            fill="none"
            :stroke="strokeFill"
            :opacity="strokeOpacity"
            :stroke-width="strokeWidth"
            stroke-linecap="round"
            v-if="isLineNode"/>
    </svg>
  </div>
</template>

イベントハンドラを実装

今回は図形からドラッグしていくと
線が生成され、図形同士で繋げた場合に
生成されるようにしていきます。

SVGDemo.vue
<script>
export default {
  name: 'SVGDemo',
  data () {
    return {
      width: 500,
      height: 500,
      ratio: 1,
      dx: 0,
      dy: 0,
      viewport: '0 0 500 500',
      isLineNode: false,
      beforeMouseX: null,
      beforeMouseY: null,
      selectIdx: 0,
      pathList: [],
      pathTemp: '',
      strokeFill: '#525',
      strokeOpacity: 1,
      strokeWidth: '5',
      moveLineX: 0,
      moveLineY: 0,
      startLineX: 0,
      startLineY: 0,
    } 
  },
  // マウス操作関連
  mounted () {
    console.log('MOUNT LISTENER ON')
    document.addEventListener('mouseup', this.mouseUp)
    document.addEventListener('mousemove', this.mouseMakeLine)
  },
  beforeDestroy () {
    console.log('MOUNT LISTENER OFF')
    document.removeEventListener('mouseup', this.mouseUp)
    document.removeEventListener('mousemove', this.mouseMakeLine)
  },
  methods: {
    zoompan (e) {
      var [x, y, w, h] = this.viewport.split(' ').map(v => parseFloat(v))
      if (e.ctrlKey) {
        // 拡大(Y軸が上がる場合) 縮小(Y軸が下がる場合)
        if (e.deltaY > 0) {
           w = w * 1.01
           h = h * 1.01
        } else {
          w = w * 0.99
          h = h * 0.99
        }
        this.makeViewBox(x, y, w, h)
        this.ratio = w / this.width
        e.preventDefault()
      } else {
        // 移動
        if ((this.dx + e.deltaX > -this.width && this.dy + e.deltaY > -this.width) &&
            (this.dx + e.deltaX < this.width * 2 && this.dy + e.deltaY < this.width * 2)) {
          this.makeViewBox(x + e.deltaX, y + e.deltaY, w, h)
          this.dx += e.deltaX
          this.dy += e.deltaY
        }
      }
    },
    // viewboxを作成
    makeViewBox (x, y, w, h) {
      this.viewport = [x, y, w, h].join(' ')
    },
    // マウスのクリックが終わった段階で初期化
    mouseUp (e) {
      this.isLineNode = false
      this.beforeMouseX = null
      this.beforeMouseY = null
      this.pathList.push(this.pathTemp)
      this.pathTemp = null
      e.preventDefault()
    },
    // move中は前回まで動かした差分を取りながら座標を変化させていく
    mouseMove (e) {
      if (!this.isMove) return
      var mouseX = e.offsetX * this.ratio + this.dx
      var mouseY = e.offsetY * this.ratio + this.dy
      var dx = 0
      var dy = 0
      if (this.beforeMouseX && this.beforeMouseY) {
          dx = mouseX - this.beforeMouseX
          dy = mouseY - this.beforeMouseY
      }
      this.beforeMouseX = mouseX
      this.beforeMouseY = mouseY
      var tempX = dx + Number(this.rects[this.selectIdx].x)
      var tempY = dy + Number(this.rects[this.selectIdx].y)
      if (tempX > 0) this.rects[this.selectIdx].x = tempX
      if (tempY > 0) this.rects[this.selectIdx].y = tempY
      e.preventDefault()
    },
    lineNode (e, i) {
      this.startLineX = e.offsetX * this.ratio + this.dx
      this.startLineY = e.offsetY * this.ratio + this.dy
      this.moveLineX = e.offsetX * this.ratio + this.dx
      this.moveLineY = e.offsetY * this.ratio + this.dy
      this.isLineNode = true
      e.preventDefault()
    },
    mouseMakeLine (e) {
      if (!this.isLineNode) return
      var mouseX = e.offsetX * this.ratio + this.dx
      var mouseY = e.offsetY * this.ratio + this.dy
      var dx = 0
      var dy = 0
      if (this.beforeMouseX && this.beforeMouseY) {
          dx = mouseX - this.beforeMouseX
          dy = mouseY - this.beforeMouseY
      }
      this.beforeMouseX = mouseX
      this.beforeMouseY = mouseY
      var tempX = this.moveLineX
      var tempY = this.moveLineY
      tempX += dx
      tempY += dy
      if (tempX > 0) this.moveLineX = tempX
      if (tempY > 0) this.moveLineY = tempY
      this.pathTemp = 'M' + this.startLineX + ',' + this.startLineY + ' L' + this.moveLineX + ',' + this.moveLineY
    }
  }
}
</script>

完成

ここまでロジックを記載していくと
このような形になるかと思います。
aperture-video-65ade8d9-c974-4505-9dfc-b07807b73ed1.gif
ベジェ曲線など組み込んでカーブさせても
面白そうですね!

最後に

いかがでしたでしょうか?
Vue.jsとSVGを使うと色々なことができるので、
また色々記事を書いていけたら、と思います。

現在弊社では、HRモンスターと呼ばれる
採用の新しいスタイルを提供するサービスをローンチいたしました。

ローンチ後のさらなる機能追加、改善などのPDCAサイクルを回すべく、
エンジニアを募集しております。
https://www.wantedly.com/projects/341182

Kubernetes、Vue.js(Javascript)、Django(Python)といったモダンな技術を使って、
開発しておりますので、もしご興味がある方はぜひ、ご応募お待ちしております。

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

Bulma+Vue.js モバイル対応用 Navbarの動作

下記の記事を参考に実装したが、
バーガーメニューが開いてNavメニューをクリックしてもバーガーメニューが閉じてくれない。

「BulmaのハンバーガーメニューをVue.jsで実装する」
https://qiita.com/seki19/items/649b317c10e84fb36e06

ポイントは、下記にメニューなどをクリックした場合を追加

<div id="navbarBasicExample" class="navbar-menu" v-bind:class="{'is-active': isOpen}">  

@click="isOpen = !isOpen"を追加する。

<div id="navbarBasicExample" class="navbar-menu" @click="isOpen = !isOpen" v-bind:class="{'is-active': isOpen}">  

となる。

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

【Vue.js】コンポーネントファイルの命名とタグの扱いをざっくりとまとめてみる

こんにちは、d4teです。

Vueのお作法に関する備忘録です。内容はコンポーネントファイルの命名・SFCでのタグの扱いなどです。
ここで書くことはすべてVue公式のスタイルガイド -Vue.jsに書いてあるので、興味のある方はそちらも読むことをおすすめします。

SFCのファイルの命名

単一ファイルコンポーネントのファイル名はパスカルケース か ケバブケースにすべき

Bad

components/
  |-mycomponent.vue
  |-myComponent.vue

good

components/
  |-MyComponent.vue
  |-my-component.vue

密結合のコンポーネントファイルの命名

親子間の関係が密結合な場合は、親コンポーネントの名前をプリフィックスとして含むべき

bad

components/
  |-TodoList.vue
  |-TodoItem.vue
  |-TodoButton.vue

good

components/
  |-TodoList.vue
  |-TodoListItem.vue
  |-TOdoListItemButton.vue

最初僕はディレクトリでわけることで、理解しやすくしようと思っていましたが、スタイルガイドによればアンチパターンのようです。

components/
  |-TodoList.vue
  |-TodoList/
    |-TodoItem.vue
    |-TodoItem/
      |-Button.vue

たしかにこうしてみるとわかりにくいです。

  • 同じような名前のファイルがたくさんできてしまい、コードエディタ上で素早くファイルを切り替えるのが難しくなります。
  • ネストしたサブディレクトリがたくさんできてしまい、エディタのサイドバーでコンポーネントを参照するのに時間がかかるようになります。

自己終了形式のコンポーネント

単一ファイルコンポーネント・文字列テンプレート・JSXでは自己終了型で書くべきです

しかしDOMテンプレート(HTML)では開始タグと終了タグをセットで書きます。

単一ファイルコンポーネント・文字列テンプレート・JSX

bad

<MyComponent></MyComponent>

good

<MyComponent />

また、単一ファイルコンポーネント内では常にパスカルケースで書くべきです

DOMテンプレート(HTML)

bad

<MyComponent />

good

<my-component><my-component />



DOMテンプレート内では開始タグ・終了タグを書くことに加えてケバブケースで書く必要があります。

単一ファイルコンポーネントのトップレベルの属性の順序

単一ファイルコンポーネントでは template, <script>, <style> タグを一貫した順序で書くべきです。

また、 <style> は最後に書きます。

bad

<style></style>
<template></template>
<script></script>
<!-- A.vue -->
<script></script>
<template></template>
<style></style>

<!-- B.vue -->
<template></template>
<script></script>
<style></style>

good

タグが一貫した順序で、<style> が最後に書かれている。

<!-- A.vue -->
<template></template>
<script></script>
<style></style>

<!-- B.vue -->
<template></template>
<script></script>
<style></style>

参考 スタイルガイド -Vue.js

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