- 投稿日:2019-07-19T19:08:40+09:00
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では@イベントが用意されています!
というわけで、がんばって英語を読んで実装しました。インストール
こちらを参考に。
イベントでカスタマイズ
ページとコンポーネントに分けて実装しました。
この時点でカスタマイズしたのは、フッターのボタンと、それに対する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点。
- モーダルを表示するとき、背景のスクロールを固定する -> showイベント
- モーダルを閉じるとき、背景のスクロール固定を解除する -> hiddenイベント
これでカスタマイズして完ぺ…ちょっと待ったー!!
ここで運命の分かれ道!ファイルの構成を確認します!
- ページとコンポーネントに分けず、1つのファイルに書いている
- ページとコンポーネントに分けている
1の場合は、そのままページに対してスクロールを固定すればOKです。
クラスのバインディングがスマートな書き方だと思います?今回は2のパターンな訳ですが…
他にもモーダルを使う部分が出てくるとなると、その度にスクロール固定のクラスを書かなくてはいけません。
そして、スクロールを固定しなくてはならないファイルに一つ一つ指定するのも大変…。というわけで、今回はscssとjQueryを使ってスクロールを固定・解除する方法を取りました!
(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部分に関してはこちらを参考にしています?
これでiPhoneでもモーダルのスクロールに悩まされなくなりました!
あとはバシバシお好みの見た目にカスタマイズしましょ〜hiddenイベントについて
個人的に面白いな、と思ったのがhiddenイベント。
どこで役立ったかというと、「モーダルの背景をクリックする」という場合。BootstrapVue「モーダルの背景をクリックされたら閉じてあげる。スクロール固定のクラス?知らないよ?」
…つまり、「モーダルを閉じたイベント」に対してスクロール固定を解除する必要が出てくるわけです。
そのイベントこそがhiddenイベント!
他のイベントと同じようにクラス解除の指定をしてあげるだけで無事解決です。まとめ
Vue.jsやNuxt.jsとともにBootstrap4を使って慣れてきたら、ぜひBootstrapVueも使ってみてください。
初心者でもある程度使われた方なら、きっと壁を超える方法が見つかります?
- 投稿日:2019-07-19T17:23:31+09:00
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)
- 投稿日:2019-07-19T16:32:13+09:00
plunkerでvue その4
概要
plunkerでvueやってみた。
babylonやってみた。写真
成果物
https://embed.plnkr.co/N5qs1ACkQaMOsgFlv7Qn/
以上。
- 投稿日:2019-07-19T16:29:40+09:00
plunkerでvue その3
概要
plunkerでvueやってみた。
threeやってみた。写真
成果物
https://embed.plnkr.co/KVp4k2mRqwjgfmvcyTtO/
以上。
- 投稿日:2019-07-19T16:12:42+09:00
plunkerでvue その2
概要
plunkerでvueやってみた。
bootstrapvueやってみた。写真
成果物
https://embed.plnkr.co/DfMZyuLV33akLIUYapig/
以上。
- 投稿日:2019-07-19T16:12:42+09:00
plunkerでvue その2
概要
plunkerでvueやってみた。
bootstrapvueやってみた。写真
成果物
https://embed.plnkr.co/DfMZyuLV33akLIUYapig/
以上。
- 投稿日:2019-07-19T16:09:25+09:00
plunkerでvue
概要
plunkerでvueやってみた。
写真
成果物
https://embed.plnkr.co/BTCGOOTcMPL8w1q4o46C/
以上。
- 投稿日:2019-07-19T14:08:12+09:00
Nuxt.js の導入 -2.8.1ver-
はじめに
ポートフォリオを作るために先輩の記事を見ながら Nuxt.js を導入してみましたが、インストール時に対話形式で設定を質問される内容がバージョン違いで少し変わっていたので
v2.8.1バージョンを書いてみました![]()
ちなみにバージョンは次のコマンドで確認できます。
> npx nuxt -vNuxt.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パッケージマネージャーに
npmとyarnのどちらを使うか聞かれます。Choose UI framework
? Choose UI framework (Use arrow keys) > None Ant Design Vue Bootstrap Vue Buefy Bulma Element iView Tachyons Tailwind CSS Vuetify.jsUI フレームワークを聞かれます。(あとで変更可能です!)
今回は
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
AxiosとPWAを利用するか聞かれます。
Axios1 は最低限チェックしたほうが良くて、PWA2は好みです。(先輩がデスクトップへダウンロードしてくれるそうなので)
PWAも入れることにしました!Choose linting tools
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection) >( ) ESLint ( ) Prettier
ESLintとPrettierを利用するかどうか聞かれます。
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!?なんですって
)
どうやらWebpackのバージョン違いによるものみたい…
エラーの解決方法は別の記事にまとめました!
http://localhost:3000/にアクセス!
ワーイ (/・ω・)/おわりに
Nuxt.js ってなに?なんて読むの?状態からのスタートでしたが、フロントエンド初めての私でも出来ました!
起動してすぐに画面が表示されるとモチベーションがあがりますね。
Nuxt.js インストールする中で知らない単語がたくさん出てきたのでいい勉強にもなりました!!
- 投稿日:2019-07-19T11:00:26+09:00
Vue.jsを勉強する Session�9
フォーム入力バインディング
基本的な使い方
formのinput要素やtextarea要素、select要素に双方向(two-way)データバインディングを作成するには、v-modelディレクティブを使用する事ができます。
v-modelは、自動的に入力要素のタイプに基いて要素を更新するための方法です。
v-modelはユーザの入力イベントにおいてデータを更新するための基本的なお砂糖構文(syntax sugar)で、それに加えて、いくつかのエッジケースに対しては特別な配慮をしてくれます。【注意】
v-modelは任意のform要素にあるvalue、checkedまたは、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-valueとfalse-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の入力ホームに一手間加えるだけですので、慣れてしまえば難しいことはないと思います。参考資料
- 投稿日:2019-07-19T09:17:52+09:00
[Vue.js] オリジナルコンポーネントをnpmに公開する方法
環境
この記事を書くのに使った環境は以下の通りです。
$ npm -version 6.9.0 $ vue --version 3.8.3Vue 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にアクセスします。フォルダ構成
この記事では、最終的に以下のようなフォルダ構成になるように、コンポーネントおよび必要ファイルを作成していきます。
├── 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からアカウントを登録して、サインアップします。
npmにログイン
以下のコマンドを入力して、npmにログインします。コマンドを入力すると、ユーザ名/パスワード/メールアドレスを聞かれますので、サインアップ時に登録した内容を打ち込みます。
$ npm loginビルドをする
package.jsonに追加したライブラリ生成用のコマンドを入力して、ビルドを行います。
$ npm run build-bundlenpm publishで公開する
以下のコマンドで、npmにコンポーネントを公開します。
$ npm publishhttps://www.npmjs.com/にアクセスにして、自分が公開したコンポーネントが検索できるか確認してみましょう。
README.md を書こう
テンプレートで作成された
README.mdをnpmに公開すると、下のように寂しい感じになっているので、セットアップ方法やAPIなどの説明を書くようにしましょう。変更した内容を公開する
バグ修正・新機能追加・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を簡単に公開できて楽です。
- 投稿日:2019-07-19T07:27:13+09:00
予習 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-apipluginの設定
main.tsimport Vue from 'vue' import { plugin } from 'vue-function-api' Vue.use(plugin)以下にvue-function-api & typescript の練習用に作ったTodoアプリがあるので、実際の使い方はそちら参考にしてください。
https://github.com/kawamataryo/sandbox-vue-functional-apicomponetの作成 -
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;参考




