20200110のvue.jsに関する記事は7件です。

vueにおいて関数のレスポンシブ化をした話

会津大学でプログラミングを書いてる学生です。

モバイルのときとPCのときで別の挙動をする関数を作った話をします。

もくじ


がいよう

授業でwebアプリ開発をしていて、その中でモバイルのときとPCのときで別の挙動をするようにしたい状況がありました。

  • あるリストがあり、それをクリックするとモーダルウィンドウが開き詳細が見ることができる。
  • そのリストはそれぞれ固有のIDを持っている。
  • 検索フォームがあり、IDと検索文字が部分一致するリストを返す(?)

てな感じ。ですが、使用する状況がモバイルが多く、IDと検索文字が完全一致した時に直接モーダルウィンドウを開いたほうが使いやすいという結論に至りました

これを関数のレスポンシブ化と呼びます(???)。

というわけでいっちょやってみます

しくみ

if(this.$el.offsetWidth < 670){
   ...
}

結構力技かもしれないですけどこんな感じで作れました。

this.$elで現在の画面の情報とかいろんなものがごっちゃになっているelement型?なるものを使えるらしい。

それのoffsetWidthを見るとウィンドウサイズが取れるっぽく、コレを使用してcssのレスポンシブ対応的なノリで記述した。

もっと他に良い方法があるかもしれないけど、今回は力技で対応。

参考リンク API - Vue.js

こんかいまなんだこと

力技っぽいけど、関数のレスポンシブ化ができた。

offsetWidthとかoffsetHeightとか、this.$elの中身を見て長さ高さを取得するとか無理やり色々いじっていたので、普段触れない部分に触れられたのは良かった点だと思う。

今後これを使って何か面白いものを作れそう。

多分。

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

【Nuxt.js】filter実践編:文末を…で省略しよう!

前置き

スクリーンショット 2020-01-08 14.30.24.png

filterを使って
文末を…(三点リーダー)で省略してみます?
コードの後にそれぞれの
各ポイントで解説をしていきます。

【考え方】
文字の長さlengthが
・10文字まで(0〜9)はそのまま表示
・11文字目以降(10〜)も続くなら
  10文字目まではそのまま表示
  11文字目を…にする

CSSでもできるよね?

できます!
が、
基準が字数ではなく、行数や幅になります。
https://www.yoheim.net/blog.php?q=20180702

コード(ローカルver)

sliceが分かりやすいよう
data内テキストは数字の0〜にしました。
では後ろの解説をどうぞ。

index.vue
<template>
 <div class="page">
   {{ text | omittedText }}
 </div>
</template>

<script>
export default {
 data () {
   return {
     text: "0123456789101112",
   }
 },
  filters: {
    omittedText(text) {
     return text.length > 10 ? text.slice(0, 10) + "" : text;
    },
  },
}
</script>

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

filter

実践編〜〜〜〜で
基本的な使い方をご確認ください!

前回には記載していない点があります。
filterにはthisを書けず
dataに直接アクセスできないことです。

そのためthis.textとは書けません。
その代わりに引数にtextを渡しています。

slice

sliceを使って、
指定文字を抜き出します。
今回は10文字目まで(0〜9)を表示?
slice(0, 10) でOKです♪

sliceの意味は…
指定番号〜後ろの指定番号より前を抜き出す?

slice(2) = 2番目〜を抜き出す
slice(0, 10) = 0番目〜9文字目までを抜き出す

MDNが参考になります!
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#Examples

三項演算

◾️式1 ? 式2 : 式3
  式1が
  ・trueなら式2
  ・falseなら式3

今回は文字の長さによって条件分岐させます。

文字の長さ ? 10文字まではそのまま表示 + 11文字目以降は"…" : 10文字まではそのまま表示 ;

これをコードにすると

text.length > 10 ? text.slice(0, 10) + "…" : text;

◾️式1:文字の長さlengthが10文字より多い
(より、なので10は含まず11〜)
・trueなら式2 10文字まではそのまま表示
        11文字目を…にする
・falseなら式3 10文字目まではそのまま表示

20文字目を…にしたい場合

【index.vue】
filterの数字を変更しましょう!
sliceが慣れてくれば簡単ですね?

index.vue
// 変更前
return text.length > 10 ? text.slice(0, 10) + "…" : text;

// 変更後
return text.length > 19 ? text.slice(0, 19) + "…" : text;

コード(グローバルver)

plugin/filter.js
import Vue from 'vue'

Vue.filter('toLowercase', function (text) {
 return text.length > 10 ? text.slice(0, 10) + "" : text;
})
nuxt.config.js
export default {
 plugins: [
   '~/plugins/filter.js'
 ],
}
index.vue
<template>
 <div class="page">
   {{ text | omittedText }}
 </div>
</template>

<script>
export default {
 data () {
   return {
     text: "0123456789101112",
   }
 },
}
</script>

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

このアカウントでは
Nuxt.js、Vue.jsを誰でも分かるよう、
超簡単に解説しています??
これからも発信していくので、
ぜひフォローしてください♪

https://twitter.com/aLizlab

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

vue create my-project でversion mismatchが出た時。

vue create my-project

vue cliにてvueのプロジェクトを作成しようとしたら、

Vue packages version mismatch:

- vue@2.6.10 (/usr/local/lib/node_modules/vue/dist/vue.runtime.common.js)
- vue-template-compiler@2.6.11 (/usr/local/lib/node_modules/@vue/cli/node_modules/vue-template-compiler/package.json)

This may cause things to work incorrectly. Make sure to use the same version for both.
If you are using vue-loader@>=10.0, simply update vue-template-compiler.
If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump vue-template-compiler to the latest.

要は、vue@2.6.10vue-template-compiler@2.6.11のバージョンが違うので合わせれば良い。
今回はvue@2.6.10vue@2.6.11にアップデートする。

npm i -g vue@2.6.11

その後、

vue create my-project

をしてあげるとプロジェクトの作成が始まります。

というメモ。

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

Vue.jsのカスタムコンポーネントを拡張する

Vue.jsのカスタムコンポーネントを拡張する

既存のコンポーネントの機能を生かしつつ、機能追加する方法のまとめ。
VuetifyのVTextFieldを拡張して、数値をカンマ区切りで表示する入力フィールドを作ります。

仕様

  • 入力(focus)時はtype="number"としてカンマなしの数値を入力する
  • 表示(非focus)時はtype="text"として3桁のカンマ区切りで表示する
  • valueの型はNumber|nullとする
  • v-modelでの双方向バインディングが出来るようにする

ラッパーコンポーネントの作成

まずプロパティ、イベント、スロットを透過的にVTextFieldに渡すコンポーネントを作成します。

プロパティ

プロパティをVTextFieldに受け渡すために属性の継承を無効化して、v-bindを指定します。

デフォルトの挙動はテンプレートのルート要素に属性が継承されますが、$propsに受け渡されるわけではなく、$attrsに直接渡されてしまうため、期待した動作をしません。
※この辺りの詳細は参考の記事に分かりやすくまとめられています

また、VTextFieldをルート以外の要素にしたい場合はデフォルトの継承では出来ないのでv-bindで渡す必要があります。

<template>
  <v-text-field
    v-bind="$attrs"
  >
  </v-text-field>
</template>

<script>
export default {
  inheritAttrs: false,
}
</script>

参考

イベント

v-onにイベントリスナーを受け渡します。
後のv-modelの実装を分かりやすくするため:value props listeners()を実装していますが、子がカスタムコンポーネントなので、inputイベントの内容を変更する必要が無ければv-on="$listeners"を追加するだけでも動作します。

<template>
  <v-text-field
    v-bind="$attrs"
    :value="value"
    v-on="listeners"
  >
  </v-text-field>
</template>

<script>
export default {
  inheritAttrs: false,
  props: ['value'],
  computed: {
    listeners() {
      const vm = this;
      return {
        ...vm.$listenres,
        // 子がカスタムイベントなのでeventに直接値が入っている
        input: event => vm.$emit('input', event)
      };
    },
  }
}
</script>

参考

スロット

$slotsからスロット名を取り出して、VTextFieldに渡します。

<template>
  <v-text-field
  >
    <template v-for="(value, name) in $slots" v-slot:[name]>
      <slot :name="name"/>
    </template>
  </v-text-field>
</template>

参考

これで作成したコンポーネントがVTextFieldの機能をすべて利用できるようになりました。

実装

続いて追加機能を実装します。

入力時はtype="number"、表示時はtype="text"にする。

dataに入力/表示の状態を保持するeditingを追加して、@focusin @focusoutで制御します。

<template>
  <v-text-field

    :type="type"
    @focusin="edit"
    @focusout="display"
  >

  </v-text-field>
</template>

<script>
export default {

  data() {
    return {
      editing: false
    };
  },
  computed: {

    type() {
      if (this.editing) {
        return "number";
      } else {
        return "text";
      }
    }
  },
  methods: {
    display() {
      this.editing = false;
    },
    edit() {
      this.editing = true;
    }
  }
};
</script>

valueの型をNumberに。入力時はカンマ無し、表示時はカンマ区切り。

valueをNumberにして、このコンポーネントが受け取る場合はNumber、VTextFieldに渡す場合はstringにします。
受け渡し用に算出プロパティのinnerValueを追加して、getterでNumber -> stringとカンマ区切りの変換、setterでstring -> Number の変換を行います。
算出プロパティを使うことでその中で使用している変数(value、editing)が変更された場合に算出プロパティに反映されます。

<template>
  <v-text-field
    v-bind="$attrs"
    :value="innerValue" // VTextFieldには算出プロパティを渡す
    v-on="listeners"

  >

  </v-text-field>
</template>

<script>
export default {
  inheritAttrs: false,
  props: {
    value: {
      type: Number,
      default: null
    }
  },
  data() {
    return {
      editing: false
    };
  },
  computed: {
    listeners() {
      const vm = this;
      return {
        ...vm.$listenres,
        input: event => vm.innerValue = event // 直接emitせずにinnerValueを通すように変更
      };
    },
    innerValue: {
      get() {
        if (this.editing) {
          return this.value != null ? this.value.toString() : "";
        } else {
          return this.value != null ? this.value.toLocaleString() : "";
        }
      },
      set(newValue) {
        const value = newValue.length > 0 ? Number(newValue) : null;
        this.$emit("input", value);
      }
    },
  },

};
</script>

これで、v-modelに値を渡せば双方向バインディングでリアルタイムに書き換わるようになります。

呼び出し元コンポーネント

<template>
  <div id="app">
    <my-number-field v-model="val" label="Number Field">
      <template v-slot:append></template>
    </my-number-field>
  </div>
</template>

<script>
import MyNumberField from "./components/MyNumberField.vue";

export default {
  name: "App",
  components: {
    MyNumberField
  },
  data() {
    return {
      val: 1000
    };
  },
}
</script>

コード

全体コードはこちら

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

SPA案件(Vue.js)を1ヶ月ほどやってみて感じた違和感と知見(個人メモ)

※この記事は、Vue.jsを批判するものではございません。

最近とあるSPA案件(Laravel, Vue)で少しの期間Vue.jsの実装をしたのですが、
そのとき、「その案件でSPAを採用するメリットはあまりない、むしろコスト増なのではないか」
と思ったので、そのアウトプットをしておこうと思います。
なお、内容は薄めなのと、読まれることをあまり意識しておりません。

案件状況

  • 納期まであまり時間がない。
  • でもSPAで構築することは強く心に決めている。

「強く心に決めている」...なぜ

  • 早いと聞いてる。
  • 既存アプリ(Laravel Blade)で、描画にめちゃくちゃ時間のかかるものがある。
  • 既存アプリの一部にVueを組み込んでみた感触が良い感じだった。(実際にメリットがあった。)

SPAのメリット/デメリット

ちゃんと教えてくれる記事たちです。
「それってSPAでやる必要ある?」第1章:パフォーマンスチューニング
シングルページアプリケーション(SPA)の導入メリット&デメリット
SPA(シングルページアプリケーション)ってそもそも何?メリット・デメリットも踏まえて解説!

メリットを自分の言葉で言うと
画面に表示されている部分にのみコストを割くができる
例えば
- データ通信が限定的
- ライフサイクルでレンダリングのタイミングを簡単に調整できる
- 必要部分のみ再描画する

デメリットは、
ビジネスインパクトがあるほど早くならないのでは?
その割に、(Laravel Blade構成と比較して)実装コストが高くない?
開発者少ないよね。(運用/保守コスト高くなるよね。)

自分が考えるVue.jsの実装コストの高さ

フロントに設計の概念が入ることがもっともコストが高いと感じます。
ルーティングやデータの持ち方、コンポーネント設計などを考えなければならないことは、
明らかにこれまでと比べて実装スピードを落とす要因になると考えます。
(逆に楽しさでもありますが。)

感じた違和感

メリット、デメリットを把握せず、また、既存アプリの描画が遅い原因を考えず、
SPAにすれば早くなる!というのは違うと思います。

実際、当案件についても、
工期が短い。
Vue.jsを使える開発者が外部エンジニアのみ。
既存アプリが遅いのは、5年前くらいに書かれ、誰のレビュー受けていない、何万行のJSコードの問題である可能性が高く、SPAうんぬんの問題ではない。

という部分から、Vue.jsでの開発に違和感を感じました。

メリットを享受できる部分に絞って、考えて採用すべき

デメリットもあるので、ちゃんと考えて導入すべき。
また部分的にVueの導入を検討する場合でも、jQueryでサクッと片付く場合はそっちの方が実装/保守コストが低い可能性もあると思います。
Laravelではhtmlを返すこともできますしね。

参考: jQueryとAjaxとLaravelでページネーションを実装する

一方で、得られた知見

Vue.jsはコードをシンプルに保てるようになっており、その点はすごく良いと感じました。

開発者的メリット

  • HTML, JavaScript, CSSを1つのファイルに記述するというのがシンプルで良い
  • scopedが便利
  • コードが複雑化しにくい

そして思ったのです。
これって普通のアプリケーションにある程度応用できそう。

実際に考えてみる

ちょっと考えてみました。方針としては、

  • 仕様に応じてBladeのディレクトリ構成をシンプルに考える
  • JavaScript, CSSはBladeのディレクトリ構成に合わせる。
  • 影響範囲は対応するBladeファイル内にとどまるように気をつける。

Bladeファイルのディレクトリ構成

サンプルです。実際には画面仕様などにより異なるはずです。

  • ルール
    • views/直下は、layouts/および、メニューに対応するディレクトリ(menus/を作ろうと思ったが階層が深くなりすぎるのが嫌なので却下)
    • メニューごとのディレクトリには、ページに対応するbladeファイルがある
    • メニューごとのディレクトリには、components/があり、ページを構成する部品を格納する。
    • formやbuttonなどの部品はparts/に格納する。
    • ページに対応するファイルからは、components/配下のファイルをincludeする。
    • components/配下のファイルからは、parts/配下のファイルををincludeする
    • ページに対応するファイルから、parts/配下の部品をincludeしない
    • 同階層のファイルをincludeしない
resources/
- views/
    - layouts/
        - app
        - components/
            - header
            - footer
    - home/
        - index
        - components/
            - eye_catching
            - new_product
    - product/
        - index
        - show
        - edit
        - components/
            - product
            - details
    - account/
        - show
        - edit
        - components/
            - table
    - parts/
        - forms/
            - radio
            - checkbox
        - buttons/
            - search
            - submit
            - delete
        - flash_message

JavaScriptとCSS(Sass)は、Bladeファイルと同じディレクトリ構成

JavaScript, CSS(Sass)はBladeファイルと同じディレクトリ構成とし、
sectionディレクティブを利用しつつ、対応するBladeファイルに記述するようにします。

resources/
- js
    - modules/
        - confirm
    - layouts/
        - app
        - components/
            - header

- sass
    - layouts/
        - app
        - components/
            - header

!!!この辺りの説明が不十分なので今夜修正します。!!!

home/index.blade.php
@section('css')
  <script src={{ mix('/js/home/index.js') }}>
@endsection

@section('js')
  <script src={{ mix('/js/home/index.js') }}>
@endsection

なお、CSSは原則そのファイルにしかスタイルが適用されないよう、
スコープを限定するように記載します。

home/index.scss
// home/index.blade.php
// <div class="home-index">
//   <div class="test-class">
//     コンテンツ
//  </div>
// </div>

.home-index { // スコープを限定する
  .test-class {

  }
}

こんな感じでしょうか。

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

Vue.jsでhoverしたら要素を出現させる方法

Vue.jsでマウスのhoverアクションで何か要素を出現させたい時があるかと思います。
そんな時は、mouseoverとmouseleaveを使います。

index.html
<p v-on:mouseover="mouseOverAction" v-on:mouseleave="mouseLemoveAction">{{message}}</p>

mouseoverはマウスが乗った時、mouseleaveはマウスが外れた時にイベントが発火します。
これとv-ifを組み合わせるとマウスが乗った時に要素を出現させることができます。

hoverAction.gif

index.html
    <div id="app">
        <div class="container">
            <p v-on:mouseover="mouseOverAction" v-on:mouseleave="mouseLemoveAction">{{message}}</p>
            <p v-if="hoverFlag">hoverされました</p>
     </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data:{
                message: 'hoverしてください',
                hoverFlag: false,
            },
            methods: {
                mouseOverAction(){
                    this.hoverFlag = true
                },
                mouseLemoveAction(){
                    this.hoverFlag = false
                }
            }
        })
    </script>

mouseover、mouseleaveでdataのhoverFlagを更新しています。それによりif文のtrueとfalseが入れ替わります。
v-showでも可能です。

また、引数をとることができるので、for文で複数の要素がある場合でも特定の要素だけを対象にすることができます。

index.html
<div id="app">
        <div class="container">
            <div v-for="(index,list) in 5">
                <p v-on:mouseover="mouseOverAction(index)" v-on:mouseleave="mouseLemoveAction(index)">{{message}}</p>
                <p v-show="hoverFlag && index === hoverIndex ">hoverされました</p>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data:{
                message: 'hoverしてください',
                hoverFlag: false,
                hoverIndex: null,
            },
            methods: {
                mouseOverAction(index){
                    this.hoverFlag = true
                    this.hoverIndex = index
                },
                mouseLemoveAction(){
                    this.hoverFlag = false
                }
            }
        })
    </script>

hoverlist.gif

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

【Vue.js】オブジェクトのリストをコンポーネントで表示するときのベストプラクティス

こんなデータを表示したい

以下のリスト一つ一つをvueコンポーネントとして表示

list: [
  {
    id: 1,
    name: "リスト1"
  },
  {
    id: 2,
    name: "リスト2"
  }
]

子コンポーネント

propsで親コンポーネントから渡された値を画面に表示するだけのコンポーネントです。

ListComponent.vue
<template>
  <div>{{ target }}</div>
</template>

<script>
export default {
  props: {
    target: {
      type: Object,   // 型がObjectである
      required: true  // 必須のプロパティである
    }
  }
};
</script>

ポイント

何も考えずにpropsに値を渡すと、以下のようになると思います。

<script>
export default {
  props: [target]
};
</script>

この場合、以下のようなデメリットがあります。

  • 値がundefinedのときに気づきにくい
  • 変数にどんな値が入るか推測しづらい

propsには、基本的にバリデーションを行いましょう。

バリデーションには

  • 必須かどうか
  • デフォルト値

などを指定することができます。
ここである程度バリデーションをかけておくことで、エラーの発生原因などを特定しやすくなります。

親コンポーネント

オブジェクトのリストをfor文で全て表示します。

Parent.vue
<template>
  <div>
    <list-component v-for="item in list" :key="item.id" :target="item" />
  </div>
</template>

<script>
export default {
  components: {
    ListComponent
  },
  data: function() {
    return {
      list: [{ id: 1, name: "リスト1" }, { id: 2, name: "リスト2" }]
    };
  }
};
</script>

ポイント

  1. v-forはコンポーネントタグの中で書ける
  2. listの中身を一意に判定できるkeyを指定する
  3. オブジェクトのまま子コンポーネントに渡すことができる

1. v-forはコンポーネントタグの中で書ける

<template>
  <div>
    <div v-for="item in list"> <!-- v-forのためだけのタグで囲う必要はない -->
      <list-component :target="item" />
    </div>
  </div>
</template>

2. listの中身を一意に判定できるkeyを指定する

keyが未指定でもエラー出ませんが、公式ドキュメントでも推奨されています。

リストに変更があった場合

  • 'key`未指定:リストが全件更新、再描画してしまう
  • 'key`指定:変更があったリストの中身を検知し、それだけを更新

3. オブジェクトのまま子コンポーネントに渡すことができる

オブジェクトの中身を以下のようにそれぞれ渡すことができますが、
オブジェクト内部の情報を全て使うのであれば、オブジェクトごと子コンポーネントに渡しましょう。

<template>
  <div>
    <list-component v-for="item in list" :key="item.id"
      :id="item.id"
      :name="item.name"
    />
  </div>
</template>

あとがき

よくあるケース&忘れそうなのでまとめておきました。

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