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

iPhoneでもBootstrap4のモーダルを使いたい

今回記事を担当しているのは、新米プログラマーその2?です!
Nuxt.jsやBootstrap4を使い始めて約2ヶ月?
初心者ながら、ぶつかった壁を乗り越えたので書き残しておきます。

何が起こったか

Bootstrap4のモーダルがiPhoneでうまく動かない。
何かと言うと、

  • モーダルの背景がスイッとスクロールされてしまう!
  • 肝心のモーダルは止まっちゃったりガタガタとスクロールされたりする!

モーダルの縦の長さが画面の縦サイズより大きくなったとき、とても困ります?
ちなみに、「iOSでモーダルの背景が動いちゃう〜><」という現象はよくあるみたいです。なんだと。

iOSでモーダルが上手くスクロールできない問題への対応
iOS Safari で絶対位置指定された要素のスクロールが出来ない件
iphone – ModalでスクロールするとSafariで背景がスクロールする

...などなど

原因わからず

「Safariがダメ」「GoogleChromeがダメ」「iPhoneだけがダメ」…etc.
色々と原因は挙げられていましたが、サイトによっては他のサイトと真逆のことを言っていることもありました。
はっきりした原因はわからないまま、ひとまず日本語で書かれた解決法は試しました。全滅。
英語で書かれているものも読みました。全滅。
この時点でBootstrap4のモーダルは諦めました。

BootstrapVueへ

引っ越しました。
モーダルやらプルダウンやら使ってみましたが、「Vue.js向けのBootstrap4!便利!」という印象です。
Bootstrap4はイベントをjQueryで指定しなきゃいけないことがあるんですね。そう、モーダルとかね。
対してBootstrapVueでは@イベントが用意されています!
というわけで、がんばって英語を読んで実装しました。

BootstrapVue公式ドキュメント

インストール

こちらを参考に。

Bootstrap を Vue.js で使ってみよう

イベントでカスタマイズ

ページとコンポーネントに分けて実装しました。
この時点でカスタマイズしたのは、フッターのボタンと、それに対するclickイベントです。
フッターについては、<footer slot="modal-footer"></footer>でサクッと変更できます?

components/modal.vue
<template>
  <div>
    <!-- モーダルを表示するボタン クリックされたらモーダルを表示する関数を呼ぶ -->
    <b-button @click="showModal">
      open!
    </b-button>
    <!-- モーダル部分 タイトルはここで指定する -->
    <b-modal ref="cardNameModal" title="CARD NAME">
      <!-- ボディ部分に表示 -->
      <div class="d-block text-center">
        <h3>白魔道士ピケル</h3>
      </div>
      <!-- フッター部分をカスタマイズ -->
      <footer slot="modal-footer">
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="secondary" @click="hideModal">
          閉じる
        </b-button>
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="primary" @click="hideModal">
          完了
        </b-button>
      </footer>
    </b-modal>
  </div>
</template>

<script>
export default {
  methods: {
    // モーダルを表示する関数
    showModal() {
      this.$refs.cardNameModal.show()
    },
    // モーダルを閉じる関数
    hideModal() {
      this.$refs.cardNameModal.hide()
    }
  }
}
</script>
pages/index.vue
<template>
  <div>
    <modalComponent></modalComponent>
  </div>
</template>

<script>
import modalComponent from '~/components/modal.vue'

export default {
  components: {
    modalComponent
  }
}
</script>

これをベースにイベントを追加し、スクロールを固定していきます。
必要なのは次の2点。

  1. モーダルを表示するとき、背景のスクロールを固定する -> showイベント
  2. モーダルを閉じるとき、背景のスクロール固定を解除する -> hiddenイベント

これでカスタマイズして完ぺ…ちょっと待ったー!!
ここで運命の分かれ道!ファイルの構成を確認します!

  1. ページとコンポーネントに分けず、1つのファイルに書いている
  2. ページとコンポーネントに分けている

1の場合は、そのままページに対してスクロールを固定すればOKです。
クラスのバインディングがスマートな書き方だと思います?

Vue.js公式 クラスとスタイルのバインディング

今回は2のパターンな訳ですが…
他にもモーダルを使う部分が出てくるとなると、その度にスクロール固定のクラスを書かなくてはいけません。
そして、スクロールを固定しなくてはならないファイルに一つ一つ指定するのも大変…。

というわけで、今回はscssjQueryを使ってスクロールを固定・解除する方法を取りました!
(Nuxt.jsを使っている以上、できる限りjQueryは避けたいところですが妥協しました。)

components/modal.vue
<template>
  <div>
    <!-- モーダルを表示するボタン クリックされたらモーダルを表示する関数を呼ぶ -->
    <b-button @click="showModal">
      open!
    </b-button>
    <!-- モーダル部分 タイトルはここで指定する -->
    <!-- show,close,hiddenイベントを追加 -->
    <b-modal
      ref="cardNameModal"
      title="CARD NAME"
      @show="showModal"
      @close="hideModal"
      @hidden="hideModal">
      <!-- ボディ部分に表示 -->
      <div class="d-block text-center">
        <h3>白魔道士ピケル</h3>
      </div>
      <!-- フッター部分をカスタマイズ -->
      <footer slot="modal-footer">
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="secondary" @click="hideModal">
          閉じる
        </b-button>
        <!-- クリックされたらモーダルを閉じる関数を呼ぶ -->
        <b-button variant="primary" @click="hideModal">
          完了
        </b-button>
      </footer>
    </b-modal>
  </div>
</template>

<script>
export default {
  methods: {
    // モーダルを表示する関数
    showModal() {
      this.$refs.cardNameModal.show()
      // スクロールを固定するクラスを追加する
      $('body').addClass('showing_modal')
    },
    // モーダルを閉じる関数
    hideModal() {
      this.$refs.cardNameModal.hide()
      // スクロールを固定するクラスを解除する
      $('body').removeClass('showing_modal')
    }
  }
}
</script>
pages/index.vue
<template>
  <div>
    <modalComponent></modalComponent>
  </div>
</template>

<script>
import modalComponent from '~/components/modal.vue'

export default {
  components: {
    modalComponent
  }
}
</script>
assets/scss/custom.scss
// モーダルを表示したとき、背景のスクロールを固定する
.showing_modal {
  position: fixed;
  overflow: hidden;
}

@import 'bootstrap/scss/bootstrap.scss';
@import 'bootstrap-vue/src/index.scss';

scss部分に関してはこちらを参考にしています?

iOSでモーダルが上手くスクロールできない問題への対応

これでiPhoneでもモーダルのスクロールに悩まされなくなりました!
あとはバシバシお好みの見た目にカスタマイズしましょ〜

hiddenイベントについて

個人的に面白いな、と思ったのがhiddenイベント
どこで役立ったかというと、「モーダルの背景をクリックする」という場合。

BootstrapVue「モーダルの背景をクリックされたら閉じてあげる。スクロール固定のクラス?知らないよ?」

…つまり、「モーダルを閉じたイベント」に対してスクロール固定を解除する必要が出てくるわけです。
そのイベントこそがhiddenイベント!
他のイベントと同じようにクラス解除の指定をしてあげるだけで無事解決です。

まとめ

Vue.jsやNuxt.jsとともにBootstrap4を使って慣れてきたら、ぜひBootstrapVueも使ってみてください。
初心者でもある程度使われた方なら、きっと壁を超える方法が見つかります?

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

Vue関連の小ネタ

Vuex

値をwatchしたい

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state : {
    name : 'Nozomi',
    changed : false,
  },
  commit : {
    transform: ( state ) => {
      // のぞみはキュアドリームに変身するココ
      state.name = "CureDream"
    },
    cancel: ( state ) => {
      // キュアドリームの変身を解除するナツ
      state.name = "Nozomi"
    }
  }
});


store.watch(
  state => state.changed,
  (newVal, oldVal) => {
    if(newVal){
      store.commit('transform')
    } else {
      store.commit('cancel')
    }
  }
);

export default store;

Vue-Router

uriは異なるけれどだいたい同じような画面にはしておきたい

◎childrenというプロパティを使う

import Vue from 'vue';
import Router from 'vue-router';
import PrecureFive from '@PrecureFive'
import PrecureFiveGoGo from '@PrecureFiveGoGo'
import Ch01 from '@Ch01'
import Ch02 from '@Ch02'
import Ch01ex from '@Ch01ex'
import Ch02ex from '@Ch02ex'

Vue.use(Router);

export default new Router({
    mode: 'history',
    base: '/precure-five',
    routes: [
        {
            // プリキュア5のページ
            path: '/',
            component: PrecureFive,
            props: true,
            children: [
                {
                    // /precure-five/ch-01 にアクセスしたら表示されるページ
                    path: 'ch-01',
                    component: Ch01
                },
                {
                    // /precure-five/ch-02 にアクセスしたら表示されるページ
                    path: 'ch-02',
                    component: Ch02
                }
            ]
        },
        {
            // プリキュア5GoGo用に新しくページが追加された
            path: '/go-go',
            component: PrecureFiveGoGo,
            children: [
                {
                    // /precure-five/go-go/ch-01 にアクセスしたら表示されるページ
                    path: 'ch-01',
                    component: Ch01ex
                },
                {
                    // /precure-five/go-go/ch-02 にアクセスしたら表示されるページ
                    path: 'ch-02',
                    component: Ch02ex
                }
            ]
        }
    ]
});

これだけ書いてもイメージはわきにくいのですが、uriの切り替えにより、共通の部分はそのままにしながら、uriをみて中身のコンテンツだけ差し替わる感じです。

... 以降もちょいちょい書く予定。(2019-07-20 updated)

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

plunkerでvue その4

概要

plunkerでvueやってみた。
babylonやってみた。

写真

image.png

成果物

https://embed.plnkr.co/N5qs1ACkQaMOsgFlv7Qn/

以上。

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

plunkerでvue その3

概要

plunkerでvueやってみた。
threeやってみた。

写真

image.png

成果物

https://embed.plnkr.co/KVp4k2mRqwjgfmvcyTtO/

以上。

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

plunkerでvue その2

概要

plunkerでvueやってみた。
bootstrapvueやってみた。

写真

image.png

成果物

https://embed.plnkr.co/DfMZyuLV33akLIUYapig/

以上。

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

plunkerでvue その2

概要

plunkerでvueやってみた。
bootstrapvueやってみた。

写真

image.png

成果物

https://embed.plnkr.co/DfMZyuLV33akLIUYapig/

以上。

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

plunkerでvue

概要

plunkerでvueやってみた。

写真

image.png

成果物

https://embed.plnkr.co/BTCGOOTcMPL8w1q4o46C/

以上。

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

Nuxt.js の導入 -2.8.1ver-

はじめに

ポートフォリオを作るために先輩の記事を見ながら Nuxt.js を導入してみましたが、インストール時に対話形式で設定を質問される内容がバージョン違いで少し変わっていたので v2.8.1 バージョンを書いてみました :pencil:

ちなみにバージョンは次のコマンドで確認できます。

> npx nuxt -v

Nuxt.js をインストール

コマンドラインから次のコマンドでインストールします。

  • Yarn を使った場合
> yarn create nuxt-app <my-project>
  • npx を使った場合
> npx create-nuxt-app <my-project>

このあといくつか質問がありますので、それぞれ解説します。

Project name

? Project name (my-project)

プロジェクト名を聞かれます。

デフォルトはインストール時にパラメーターで渡したものが表示されますので、大抵の場合はそのまま [Enter] で良いと思います。

Project description

? Project description (My stylish Nuxt.js project)

プロジェクトの説明文を聞かれます。

デフォルトの文はインストールするたびに変わるみたいです!(他にも lovely とか sweet とかあるみたい)

プロジェクト作成時に自動生成される README.md に記載される内容ですが、あとで変更できます。

Author name

? Author name (name)

作成者名を聞かれます。

Choose a package manager

? Choose a package manager (Use arrow keys)
> npm
  yarn

パッケージマネージャーに npmyarn のどちらを使うか聞かれます。

Choose UI framework

? Choose UI framework (Use arrow keys)
> None
  Ant Design Vue
  Bootstrap Vue
  Buefy
  Bulma
  Element
  iView
  Tachyons
  Tailwind CSS
  Vuetify.js

UI フレームワークを聞かれます。(あとで変更可能です!)

今回は bulma にしてみました!

UIフレームワークについてはこちらの記事が分かりやすかったです!

Choose custom server framework

? Choose custom server framework (Use arrow keys)
> None (Recommended)
  AdonisJs
  Express
  Fastify
  Feathers
  hapi
  Koa
  Micro

サーバーサイドのフレームワークを聞かれます。

Netlify との組み合わせを考えているので、今回は使用しないことにしました。

もしサーバーサイドフレームワークを利用する場合は Express が多いみたいです。

Choose Nuxt.js modules

? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
> ( ) Axios
  ( ) Progressive Web App (PWA) Support

AxiosPWA を利用するか聞かれます。

Axios1 は最低限チェックしたほうが良くて、PWA 2は好みです。

(先輩がデスクトップへダウンロードしてくれるそうなので)PWA も入れることにしました!

Choose linting tools

? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
>( ) ESLint
 ( ) Prettier

ESLintPrettier を利用するかどうか聞かれます。

ESLint はコーディングスタイルを統一するのに便利らしく、 Prettier はコードフォーマッター部分を連動させたりすることも出来るらしいので、今回は両方入れてみました!

Choose test framework

? Choose test framework (Use arrow keys)
> None
  Jest
  AVA

テストフレームワークを聞かれます。

今回作ろうとしているものが静的コンテンツに近いので入れませんでしたが、最近は Jest を利用しているのをよく見かけます。

Choose rendering mode

? Choose rendering mode (Use arrow keys)
> Universal (SSR)
  Single Page App

ユニバーサルアプリケーションとシングルページアプリケーションのどちらで作るか聞かれます。

作るアプリに合わせて選択してください。私は今回SPAで作ることにしました!

Please wait a minute...

この後、プロジェクト作成が行われ、

  To get started:

        cd my-project
        npm run dev

  To build & start for production:

        cd my-project
        npm run build
        npm run start

とメッセージが出たら作成完了です!

起動してみよう!

完了メッセージにあったように、作成されたプロジェクトに移動して、起動コマンドを入力してみましょう。

  • Yarn を使った場合
> cd my-project
> yarn run dev
  • npm を使った場合
> cd my-project
> npm run dev

起動……

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

i Preparing project for development                                                                           09:54:15
i Initial build may take a while                                                                              09:54:15
√ Builder initialized                                                                                         09:54:15
√ Nuxt files generated                                                                                        09:54:15

√ Client
  Compiled successfully in 4.83s


 ERROR  (node:22276) DeprecationWarning: Tapable.plugin is deprecated. Use new API on .hooks instead          09:54:17

(ERROR!?なんですって:confused:

どうやらWebpackのバージョン違いによるものみたい…

エラーの解決方法は別の記事にまとめました!

http://localhost:3000/ にアクセス!
1563342471515.png
ワーイ (/・ω・)/

おわりに

Nuxt.js ってなに?なんて読むの?状態からのスタートでしたが、フロントエンド初めての私でも出来ました!

起動してすぐに画面が表示されるとモチベーションがあがりますね。

Nuxt.js インストールする中で知らない単語がたくさん出てきたのでいい勉強にもなりました!!


  1. HTTP通信を簡単に扱えることができるJavaScriptライブラリです。ブラウザとnode.jsに最適化された、PromiseベースのHTTPクライアントです。 

  2. PWAとは「Progressive Web Apps」を略した言葉で、モバイルサイト上でネイティブアプリのようなユーザー体験を提供する技術です。ウェブとアプリの両方の良さを兼ね備えています。インストールの必要はないのに、ホーム画面へのアイコン追加やプッシュ通知も可能で、ユーザーとの接触機会を増やすことができます。また読み込み速度や表示の高速化、オフラインでの閲覧可など、さまざまなメリットが得られます。 

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

Vue.jsを勉強する Session�9

フォーム入力バインディング

基本的な使い方

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

【注意】

v-modelは任意のform要素にあるvaluecheckedまたは、selected属性の初期値を無視します。
inputまたはtextareaは常に信頼できる情報としてVueインスタンスを扱います。
コンポーネントのdataオプションで初期値を宣言する必要があります。

v-modelは、内部的にはinput要素に応じて異なるプロパティを使用し、異なるイベントを使用する。

選択フィールドは、valueプロパティとchangeイベントを使用します。

【注意】

IME(中国語、日本語、韓国語、その他)が必須な言語において、v-modelではIMEでテキストが確定するまでは更新されないことに注意してください。これからの更新に対して対応した場合は、inputイベントを代わりに使用します。

テキスト・複数行テキスト

テキストと複数行テキストは、valueプロパティとinputイベントを使用します。
```html



テキスト:{{ message }}


複数行テキスト:


{{ comment }}



```
let app = new Vue({
  el: '#example',
  data() {
    return {
      message: '',
      comment: ''
    }
  }
})

v-modelで使用したdataプロパティにあらかじめ値をセットしておくことでテキストや複数行テキストにテキストを挿入しておくことができます。

チェックボックス

単体のチェックボックスは、boolean値です。
チェックボックスは、checkedプロパティとchangeイベントを使用します。
html
<div id="example">
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
</div>

let app = new Vue({
  el: '#example',
  data() {
    return {
      checked: false
    }
  }
})

複数のチェックボックスは、同じ配列に束縛します。

<div id="example">
  <input type="checkbox" id="apple" value="りんご" v-model="fruits">
  <label for="apple">りんご</label>
  <input type="checkbox" id="banana" value="バナナ" v-model="fruits">
  <label for="banana">バナナ</label>
  <input type="checkbox" id="pineapple" value="パイナップル" v-model="fruits">
  <label for="pineapple">パイナップル</label>
  <input type="checkbox" id="peach" value="桃" v-model="fruits">
  <label for="peach"></label>

  <p>選択したフルーツ:{{ fruits }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      fruits: []
    }
  },
})

ラジオボタン

<div id="example">
  <input type="radio" id="male" value="男性" v-model="gender">
  <label for="one">男性</label>
  <br>
  <input type="radio" id="female" value="女性" v-model="gender">
  <label for="two">女性</label>
  <br>
  <p>性別:{{ gender }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      gender:''
    }
  },
})

選択

<div id="example">
  <select v-model="selected">
    <option disabled value="">選択してください</option>
    <option>斎藤工</option>
    <option>福山雅治</option>
    <option>木村拓哉</option>
  </select>
  <br>
  <span>抱かれたい男: {{ selected }}</span>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      selected:''
    }
  },
})

複数選択(配列に束縛)

selectのmultipleを使い複数選択可能にし
選択されたオプションは配列に束縛する

<div id="example">
  <select v-model="selected" multiple>
    <option disabled value="">選択してください</option>
    <option>斎藤工</option>
    <option>福山雅治</option>
    <option>木村拓哉</option>
  </select>
  <br>
  <p>複数選択可(shift + クリック)</p>
  <span>抱かれたい男: {{ selected }}</span>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      selected:[]
    }
  },
})

動的オプションはv-forで描画できる

<div id="example">
  <select v-model="selected">
    <option disabled value="">選択してください</option>
    <option v-for="option in options" v-bind:value="option.value">
      {{ option.text }}
    </option>
  </select>
  <br>
  <span>抱かれたい男: {{ selected }}</span>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      selected: '',
      options: [
        { text: '斎藤工', value: '斎藤工' },
        { text: '福山雅治', value: '福山雅治' },
        { text: '木村拓哉', value: '木村拓哉' }
      ]
    }
  },
})

 値のバインディング

v-modelでバインディングされる値は通常は静的な文字列(チェックボックスならboolean)です

チェックボックス

true-value属性:trueだった場合にtoggleに「同意しました」で更新false-value属性:falseだった場合にtoggleに「同意してません」で更新

<div id="example">
  <input
    type="checkbox"
    v-model="toggle"
    true-value="同意しました"
    false-value="同意してません"
    id="check"
  >
  <label for="check">利用同意</label>
  <p>{{ toggle }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      toggle: ''
    }
  },
})

【注意】

true-valuefalse-value属性は入力フォームのvalue属性には影響を及ぼしません。
なぜならブラウザはフォームの送信の中にチェックされていないチェックボックスを含めないからです。
2つの値(たとえば”同意しました”/“同意してません”)のどちらかが必ずフォームで送信されることを保証するには、代わりにラジオを使用してください。

ラジオボタン

<div id="example">
  <input type="radio" id="male" v-model="gender" v-bind:value="male">
  <label for="male">男性</label>
  <br>
  <input type="radio" id="female" v-model="gender" v-bind:value="female">
  <label for="female">女性</label>
  <br>
  <p>性別 v-model:{{ gender }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      gender:'',
      male: '男性',
      female: '女性'
    }
  },
})

ラジオボタンがチェックされた時、値は下記のようになります。

// 男性をチェックした場合
app.gender === app.male

// 女性をチェックした場合
app.gender === app.femaile

選択オプション

<div id="example">
  <select v-model="selected">
    <option disabled value="">選択してください</option>
    <option v-bind:value="{name:'斎藤工', age:38}">斎藤工</option>
    <option v-bind:value="{name:'福山雅治', age:49}">福山雅治</option>
    <option v-bind:value="{name:'木村拓哉', age:45}">木村拓哉</option>
  </select>
  <br>
  <span>抱かれたい男 名前: {{ selected.name }}</span>
  <br>
  <span>抱かれたい男 歳: {{ selected.age }}</span>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      selected:{}
    }
  },
})

修飾子

.lazy

デフォルトでは、v-modelは各inputイベント(IME確定前を除く)の後に、データと入力を同期します。
changeイベント後に同期したい場合にlazy修飾子を追加する。

<div id="example">
  <div>メッセージ入力:<input v-model.lazy="message"></div>
  <p>入力が確定した時点で表示</p>
  <p>メッセージ表示:{{ message }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      message:''
    }
  },
})

.number

ユーザの入力を数値として自動的に型変換したいとき、v-modelで束縛した入力にnumberを追加する

<div id="example">
  <div>年齢入力(文字列型):<input v-model="age" type="number"></div>
  <div>年齢入力(数値型):<input v-model.number="age" type="number"></div>
  <p>年齢表示:{{ age }}</p>
  <p>年齢表示:{{ typeof age }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      age: null
    }
  },
})

.trim

ユーザの入力から空白を自動的に取り除きたいときは、 v-modelに束縛された入力にtrimを追加する

<div id="example">
  <div>メッセージ入力:<input v-model.trim="message"></div>
  <p>メッセージ表示:{{ message }}</p>
</div>
let app = new Vue({
  el: '#example',
  data() {
    return {
      message:''
    }
  },
})

あとがき

コードを書いて理解してくださいとう表記が多くなってしまった感じもします。
入力フォームはhtmlの入力ホームに一手間加えるだけですので、慣れてしまえば難しいことはないと思います。

参考資料

公式リファレンス

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

[Vue.js] オリジナルコンポーネントをnpmに公開する方法

環境

この記事を書くのに使った環境は以下の通りです。

$ npm -version
6.9.0

$ vue --version
3.8.3

Vue CLI のインストール

Vue CLIをグローバルインストールします。

$ npm install -g vue-cli

~~中略~~
+ @vue/cli@3.8.3
added 877 packages from 554 contributors in 213.92s

この記事で紹介しているnpmへの公開方法は、Vue CLI 3以上のバージョンが必要なので、イントールされたバージョンを確認してください。もし2.x系のVue CLIがインストールされている場合、一度アンインストールしてからインストールしてください。

公開するコンポーネントのVueプロジェクトを作成

Vueのプロジェクトを作成するフォルダに移動します。

$ cd path/to/dir

次に、プロジェクトを作成します。コマンド入力後、質問形式で設定を聞かれるので答えます。

$ vue create <component-name>

Vue CLI v3.8.3
? Please pick a preset: default (babel, eslint)
? Pick the package manager to use when installing dependencies: NPM

プロジェクトの作成が完了したら、とりあえず動かしてみましょう。

$ cd <component-name>
$ npm install
$ npm run serve

ブラウザでhttp://localhost:8080にアクセスします。

Vueテンプレートプロジェクとを起動した画面

フォルダ構成

この記事では、最終的に以下のようなフォルダ構成になるように、コンポーネントおよび必要ファイルを作成していきます。

├── src
   ├── components
      └── MyComponent.vue  //(1)公開するコンポーネント
   ├── library-main.js      //(2)使われる時に自動でvue.useする為のwrapper
├── package.json             //(3) プロジェクトの情報
~~中略~~

vue createでプロジェクトを作成すると、上で書いた必要なファイル以外にも、色々とファイルが作成されますが、消す必要はなく、ほっておいても大丈夫です。

インストールラッパーを作成する

コンポーネント使用時に自動的にVue.use()を行うインストールラッパーを作成します。以下はVueの公式サイトに掲載されているサンプルコードです。基本的にそのまま使えますが、(1)(2)の部分だけは、公開するコンポーネント名に置き換えてください。

library-main.js
// コンポーネントのインポート
import component from './components/MyComponent.vue'; //(1)Vueのファイル名に変更

// Vue.use() によって実行される install 関数を定義
export function install(Vue) {
    if (install.installed) return;
    install.installed = true;
    Vue.component('MyComponent', component); //(2)コンポーネント名を変更
}

// Vue.use() のためのモジュール定義を作成
// Create module definition for Vue.use()
const plugin = {
    install,
};

// vue が見つかった場合に自動インストールする (ブラウザで <script> タグを用いた場合等)
let GlobalVue = null;
if (typeof window !== 'undefined') {
    GlobalVue = window.Vue;
} else if (typeof global !== 'undefined') {
    GlobalVue = global.Vue;
}
if (GlobalVue) {
    GlobalVue.use(plugin);
}

// (npm/webpack 等で) モジュールとして利用させるためコンポーネントを export する
export default component;

公開するコンポーネントを実装する

npmに公開するコンポーネントを実装します。公開するからと言って変わった点はなく、普通にコンポーネントを実装していきます。

MyComponent.vue
<template>
  ~~コンポーネントのテンプレート~~
</template>
<script>
export default {
  ~~コンポーネントの実装~~
}
</script>
<style scoped>
  ~~コンポーネントのスタイル~~
</sytle>

package.jsonを編集

{
  "name": "my-component",  //(1)npmに公開するコンポーネント名
  "version": "0.1.0", 
  "private": false,        //(2)npmに公開する為、falseに変更
  "main": "dist/my-component.common.js",          //(3)エントリポイント
  "unpkg": "./dist/my-component.umd.min.js",      //(4)unpkg公開用
  "jsdelivr": "./dist/my-component.umd.min.js",   //(5)jsdelivr公開用
  "typings": "dist/types/index.d.ts",  
  "license": "MIT",        //(6)ライセンス
  "author": "<作成者>",     //(7)作成者
  "files": [ "dist" ],     //(8)npmに公開するフォルダやファイル
  "keywords": ["Vue", "Vue.js", "xxxx", "xxxx"],  //(9)パッケージマネージャー用のキーワード
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "build-bundle": "vue-cli-service build --target lib --name my-component ./src/library-main.js" 
    //(10)npmに公開するライブラリ生成用のコマンド
  },
  "dependencies": {
    "core-js": "^2.6.5",
    "vue": "^2.6.10"
  },
  ~~中略~~
}

npmのサインアップ

npmのアカウントを持っていない場合は、以下のURLからアカウントを登録して、サインアップします。

https://www.npmjs.com/signup

npmのサインアップ画面

npmにログイン

以下のコマンドを入力して、npmにログインします。コマンドを入力すると、ユーザ名/パスワード/メールアドレスを聞かれますので、サインアップ時に登録した内容を打ち込みます。

$ npm login

ビルドをする

package.jsonに追加したライブラリ生成用のコマンドを入力して、ビルドを行います。

$ npm run build-bundle

npm publishで公開する

以下のコマンドで、npmにコンポーネントを公開します。

$ npm publish

https://www.npmjs.com/にアクセスにして、自分が公開したコンポーネントが検索できるか確認してみましょう。

README.md を書こう

テンプレートで作成されたREADME.mdをnpmに公開すると、下のように寂しい感じになっているので、セットアップ方法やAPIなどの説明を書くようにしましょう。

テンプレートで作成されたnpmファイル

変更した内容を公開する

バグ修正・新機能追加・README.mdの修正など、作成したコンポーネントを変更した場合、npm publishで再度公開します。npmでは同じバージョンでpublishするとエラーになるので、バージョンを上げてからnpm publishをする必要があります。

バージョンを上げる方法には、以下の2つのやり方があります。

[1] npm versionコマンドを使う

npmには、package.jsonのバージョンを上げるコマンドが容易されています。

npm version [option]

[option]には以下を指定します。指定するオプションによって、バージョンの上がり方が違います。

option 実行後のバージョン
major 2.0.0
minor 1.1.0
patch 1.0.1
premajor 2.0.0-0
preminor 1.1.0-0
prepatch 1.0.1-0
prerelease 1.0.1-0

[2] package.jsonを直接編集する

上のコマンドでバージョンを上げるのは必須ではない為、package.jsonのversionを手動で書き換えてもOKです。

package.json
{
  "version": "0.1.1", 
}

npmに公開したコンポーネントの使い方

npmに公開すれば、あとは普通にnpmのコマンドで、コンポーネントがインストールできます。

コンポーネントのインストール

npm i my-component --save

コンポーネントを使う

importにコンポーネントとCSSを入れてあげると、作成したコンポーネントが使えます。

<template>
  <my-component/>
</template>
<script>
import MyComponent from 'my-component'
import 'my-component/dist/my-component.css'

export default {
  components: {
    MyComponent
  }
}
</script>

さいごに

Vue.jsでオリジナルコンポーネントをnpmに公開する方法と、公開した後に、実際にコンポーネントを使う手順を紹介してきました。VUE-CLI3を簡単に公開できて楽です。

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

予習 Vue 3.x vue-function-api の 書き方一覧 with TypeScript

最近のVue.jsの話題といえば、次期メジャーバージョンアップのVue3.xで採用が予定され、現在RFCとなっているvue-function-api ですね。親しみがある今のオブジェクトベースのAPIとはかなり違うので賛否両論あるようですが、実際に使ってみるとTypeScriptで書きやすく個人的には好みです。

この記事ではTypeScriptでのvue-function-apiの書き方を紹介します。(随時追加予定)
自分もまだまだ勉強段階なので間違ってる箇所あれば、気軽にコメントで指摘お願いします。

※ サンプルコードでは、明示的に型アノテーションつけていますが、実際には自動的に型推論される箇所が多いです。
※ Vue3.x では従来のオブジェクトベースのAPIはサポートされるので、バージョンアップの際に置き換えが必ず必要というわけではないです。

vue-function-apiの導入

vue-function-apiはpluginとして提供されているので、vue2.x でも使用可能です。
https://github.com/vuejs/vue-function-api

パッケージの追加

$ yarn add vue-function-api

pluginの設定

main.ts
import Vue from 'vue'
import { plugin } from 'vue-function-api'

Vue.use(plugin)

以下にvue-function-api & typescript の練習用に作ったTodoアプリがあるので、実際の使い方はそちら参考にしてください。
https://github.com/kawamataryo/sandbox-vue-functional-api

componetの作成 - createComponent(), setup()

vueのSFCの基本単位となるcomponentの作成は、createComponent<T>()を使います。
そして慣れ親しんだvueのリアクティブなデータや、メソッド定義はcreateComponent<T>()の引数オブジェクト内でsetup()メソッドを定義して記述します。
また、template側で使う関数、変数はsetup()内で変数に代入し、オブジェクトとしてreturnする必要があります。

従来

export default {
  methods: {
    greet: function() {
      return "hello world"
    }
  }
}

vue-function-api(TypeScript)

export default createComponent({
  setup() {
    const greet: string = () => {
      return "hello world";
    };
    return {
      greet
    };
  }
});

型定義

declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & {
    props?: Props;
    setup?: (this: undefined, props: {
        [K in keyof Props]: Props[K];
    }, context: Context) => object | null | undefined | void;
};
export declare function createComponent<Props>(compOpions: ComponentOptionsWithSetup<Props>): ComponentOptions<Vue>;

リアクティブなデータの保持 - value()

オブジェクトベースのAPIでのdata(){return {}}に相当するのがvalue()です。
vue-function-apiではオブジェクトでまとめず要素ごとに個別に定義します。そしてTypeScriptでは、Wrapper<T>を使って型定義します。
また、template側で使う変数は関数と同様setup()内で変数に代入し、オブジェクトとしてreturnする必要があります。

従来

export default {
  data() {
    return {
      name: "taro",
      address: {
        postCode: 1234,
        city: "tokyo"
      }
    };
  }
};

vue-function-api(TypeScript)

type Address = {
  postCode: number;
  city: string;
};

export default createComponent({
  setup() {
    const name: Wrapper<string> = value("taro");
    const address: Wrapper<Address> = value({
      postCode: 1234,
      city: "tokyo"
    });

    return {
      name,
      address
    };
  }
});

型定義

export declare function value<T>(value: T): Wrapper<T>;

算出プロパティ - computed

リアクティブな依存関係にもとづきキャッシュされる算出プロパティは、computed()関数で定義します。

従来

export default {
  data() {
    return {
      message: "Hello world";
    }
  },
  computed: {
    reversedMessage: function () {
      return this.message.split("").reverse().join("")
    }
  }
}

vue-function-api(TypeScript)

export default createComponent({
  setup() {
    const message: Wrapper<string> = value("hello");

    const reverseMessage: Wrapper<string> = computed(() => {
      return message.value.split("").reverse().join("")
    });

    return {
      message,
      reverseMessage
    };
  }
});

型定義

export declare function computed<T>(getter: () => T, setter?: (x: T) => void): Wrapper<T>;

親から子への値の受け渡し - props

親から子コンポーネントに値を受け渡すpropsは、`createComponentのジェネリクスとしてporpsの型を渡して、引数オブジェクトのpropsのプロパティの値として文字列配列をanyにupキャストした上で型定義が必要です。
型定義見ると、ジェネリクスと Mapped typesを使っていて難解ですね。

従来

export default {
  props: {
    name: String,
    age: Number
  },
  methods: {
    userProfile: function {
      return `名前: ${name}, 年齢: ${age}`
    }
  }
}

vue-function-api(TypeScript)

type Props = {
  name: string;
  age: string;
};

export default createComponent<Props>({
  props: (["name", "age"] as any) as PropType<Props>,
  setup(props) {
    const userProfile = (): string  => {
      return `名前: ${props.name}, 年齢: ${props.age}`
    }

    return {
      userProfile
    };
  }
});

型定義

export declare type PropType<T> = T;
declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & {
    props?: Props;
    setup?: (this: undefined, props: {
        [K in keyof Props]: Props[K];
    }, context: Context) => object | null | undefined | void;
};

context関数 - emit, refs, slot...

子から親へのデータ通信の際のイベント発火に使うemitなどのインスタンスメソッドは、setup()メソッドが第2引数で受け取る、contextオブジェクトに定義されています。それを従来と同様の書き方で使えば大丈夫です。
例 emit

従来

export default {
  methods: {
    emitGreet: function() {
      this.$emit('greet', 'Hello')
    }
  }
}

vue-function-api(TypeScript)

export default createComponent({
  setup(props, context: Context) {
    const emitGreet = () => {
      context.emit("greet", 'Hello');
    };
  }
});

型定義

export interface Context {
    readonly parent: Vue;
    readonly root: Vue;
    readonly refs: {
        [key: string]: Vue | Element | Vue[] | Element[];
    };
    readonly slots: {
        [key: string]: VNode[] | undefined;
    };
    readonly attrs: Record<string, string>;
    emit(event: string, ...args: any[]): void;
}

変更の監視 - watch()

カスタムウォッチャのwatch()の定義は、setup()メソッド内のwatch関数で定義します。
value側で適切に型定義をしていれば、特に型アノテーションは必要なく、型推論されます。

従来

export default {
  data() {
    return {
      count: 0
    }
  },
  watch: {
    count: function (newVal, oldVal) {
      if (newVal > oldVal) {
        console.log("カウントアップ");
      } else {
        console.log("カウントダウン");
      }
    }
  },
}

vue-function-api(TypeScript)

export default createComponent<Props>({
  setup() {
    const count: Wrapper<number> = () => value(0);

    watch(count, (newVal, oldVal) => {
      if (newVal.value > oldVal.value) {
        console.log("カウントアップ");
      } else {
        console.log("カウントダウン");
      }
    });

    return {
      count
    };
  }
});

型定義

declare type watcherCallBack<T> = (newVal: T, oldVal: T) => void;
declare type watchedValue<T> = Wrapper<T> | (() => T);
declare type FlushMode = 'pre' | 'post' | 'sync';
interface WatcherOption {
    lazy: boolean;
    deep: boolean;
    flush: FlushMode;
}
export declare function watch<T>(source: watchedValue<T>, cb: watcherCallBack<T>, options?: Partial<WatcherOption>): () => void;
export declare function watch<T>(source: Array<watchedValue<T>>, cb: watcherCallBack<T[]>, options?: Partial<WatcherOption>): () => void;

Lifecycle Hooks

vueのライフサイクルに合わせて処理を行いたい場合に使うライフサイクルフックは、setup()メソッド内で関数として呼び出します。
処理はコールバックで渡します。

従来

export default {
  created: function() {
   // 何らかの処理
  },
  mouted: function() {
   // 何らかの処理
  },
  beforeDestroy: funciton() {
   // 何らかの処理
  }
}

vue-function-api(TypeScript)

export default createComponent({
  setup() {
    onCreated(() => { 
      //何らかの処理}
    })
    onMounted(() => { 
      //何らかの処理}
    })
    onBeforeDestroy(() => { 
      //何らかの処理}
    })
  }
});

型定義

export declare const onCreated: (callback: Function) => void;
export declare const onBeforeMount: (callback: Function) => void;
export declare const onMounted: (callback: Function) => void;
export declare const onBeforeUpdate: (callback: Function) => void;
export declare const onUpdated: (callback: Function) => void;
export declare const onActivated: (callback: Function) => void;
export declare const onDeactivated: (callback: Function) => void;
export declare const onBeforeDestroy: (callback: Function) => void;
export declare const onDestroyed: (callback: Function) => void;
export declare const onErrorCaptured: (callback: Function) => void;
export declare const onUnmounted: (callback: Function) => void;

参考

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