- 投稿日:2020-06-01T22:57:01+09:00
stateを直接参照しないシンプルなVuexサイクル
Vuexとは?
Vue.jsアプリケーションにおける状態管理パターンライブラリです。
コンポーネント間のデータや関数の受け渡しには、propsやemitを使いますが、Vuexは全てのコンポーネントのための集中型のStoreになります。
propsやemitだけでデータの受け渡しをするのは、バケツリレーに似ていて、vueファイルに何度も同じコードを書く必要があったり、無駄にロジックが発生します。Vueコンポーネント層とVuexはどう繋がるのか?
今回の記事のタイトルは「stateを直接参照しないシンプルなVuexサイクル」です。
Vueコンポーネント層からStore内のstateにアクセスするためには
- store.stateやmapStateを使ってstateを直接する方法
- store.gettersやmapGettersを用いて、取得する方法
があります。
ただ、Stateの変更をどこからでも直接、自由に変更、取得可能だと、全コンポーネント共通で参照可能なStateの秩序を保つことが出来ません。
そのため、公式ドキュメントにも掲載されている以下の図の通り、VuexのライフサイクルをルールとしてStateを変更、取得します。stateの変更はMutationsが行い、vueコンポーネント層からはgetterを使用してStateの値を取得します。
そのMutaitionsはVueコンポーネント層から呼ばれるActionsによって実行されます話がややこしくなってきたのでもう少し詳細にしてみます。
Vue.jsやNuxt.jsとVuexを使ってStateを管理したい。
そんなときに登場してくるのは Actions Mutaitions State Getters この4つです。Vuex それぞれの役割
今回は、Firestoreに保存されているItemsを取得し、それをVuexのStateに格納して、コンポーネント層で使用する例を挙げます。
store/items
以下に
- state.js
- action.js
- mutaition.js
- getter.jsを準備します。
State
Stateでは初期値を格納します。
基本的には配列が入る想定であれば[]、文字列を想定すれば''を初期値にします。
state.jsを用意して以下のように記述するとexport const state = () => { allItems: [] }stateの初期値はから配列になります。
Getters
GettersではStateの値をコンポーネント層で取得する際に使います。
例えば、Stateに格納されているallItemsを取得するなら以下のようにしてexport const getters = { getAllItems: state => state. allItems }vueコンポーネントからは
computed: { allItems() { return this.$store.getters['items/getAllItems'] } }のようにして取得できます。
ただこのままだと、allItemsは空配列なのでMutaitionsからStateを更新します。
Mutations
Actionsを定義する前に、Mutationsを用意します。
export const mutations = () => { setAllItems(state, items) { state.allItems = items } }setAllItemsという関数を作ります。
第1引数は値を変更するstateで、第2引数はActionsからもらうitemsとなります。
そのitemsをStateのallItemsの代入することでStateを更新できます。Actions
次にVueコンポーネント層からActionsを呼ぶ必要があるので定義します。
export const actions = () => { async fetch({ commit }) { const docs = await Firestore.collection('items').get() const items = docs.map(doc => doc.data()) commit('setAllItems', items) }, }commit関数の第1引数にMutaitionsの実行したい関数を文字列で指定し、第2引数にFirestoreから取得したitemsを指定します。
コンポーネント層でからは以下のようにActionsを呼ぶことが出来ます。
fetch({store}) { store.dispatch('items/fetchAllItems') }所感
今回はState Getters Mutations Actionsの順で実装しましたが、どこから実装するのかに問題はありません。
名称からどんな役割があってどこからどんなふうに呼び出せば良いのか覚えるまでに時間がかかるかもしれませんが、使っていくうちに分かってきます。ちなみにmentaというコーチングサービスでVue.jsを中心にメンターをしています。
もう少し詳しい内容を聞きたい方や、Vue.jsの勉強に行き詰まっている方など、ぜひmentaでwatsuyo_2にメッセージをしてください!参考
- 投稿日:2020-06-01T19:39:09+09:00
Vue.jsで画像をドラッグ&ドロップでアップロードする枠外にドロップした際、画像を表示させない方法。
ドラック&ドロップで間違って枠外にドロップしてしまうことがあります。
そうなると画像が表示されてしまい良いアップローダーとは、言えません。以下を記述するだけで避けれます。
mounted () { window.ondrop = function(e){ e.preventDefault(); }; window.ondragover = function(e){ e.preventDefault(); }; }参考になったら幸いです。
- 投稿日:2020-06-01T18:33:28+09:00
【Vue/CSSアニメーション】BootstrapVueのダサすぎるCardコンポーネントをstyle修正で100億倍クールにしてみた(コピペOK)
Vueバージョン確認
npm list vueまずは上記コマンドでバージョンの確認
twinzlabo@0.1.0 /Users/twinzlabo ── vue@2.6.11BootstrapVueの導入
BootstrapVueの導入がまだの方のために念のため導入方法書いときますね
とりあえずコピペして環境を整えてください
main.jsimport BootstrapVue from 'bootstrap-vue' import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap-vue/dist/bootstrap-vue.css' Vue.use(BootstrapVue)npm install vue bootstrap-vue bootstrap以上でBootstrapVueの導入は完了です
Cardコンポーネントの白黒画像にhoverすると色がつくCSSアニメーション実装
すでに上の方で確認してもらったかと思いますが、
BootstrapVueのあまりしっくりこないカードコンポーネントをスタイル修正を行うことで
クールなデザインに編集していきましょう
デフォルトの上の画像をhoverしたら下の画像のように色がつくようにカスタマイズしていきます
この感じなかなかクールですよね
では早速コードをコピペしていきましょう
<template> <div> <b-card no-body class="overflow-hidden" style="max-width: 540px;margin: 0 auto"> <b-row no-gutters> <b-col md="6"> <b-card-img src="https://picsum.photos/400/400/?image=20" alt="Image" class="rounded-0"></b-card-img> </b-col> <b-col md="6"> <b-card-body title="Horizontal Card"> <b-card-text> This is a wider card with supporting text as a natural lead-in to additional content. This content is a little bit longer. </b-card-text> </b-card-body> </b-col> </b-row> </b-card> </div> </template>style <style> img { display: inline-block; width: 100%; height: 100%; background-size: contain; background-repeat: no-repeat; cursor: pointer; transition: all 200ms ease-in; filter: grayscale(1) opacity(.8); } img:hover { filter: grayscale(0) opacity(1); } </style>これだけです
いかがでしたでしょうか?
白黒表示の画像をhoverするとカラーになるってなかなかクールですよね
下の記事も参照してより応用的なデザインにも挑戦してみてください
以上です
- 投稿日:2020-06-01T17:56:54+09:00
Vue.js filter関数のはなし
今回のテーマ:子に配列を渡してデータを判別してもらう
Vue.js プロップスの話で親子コンポーネントのプロップスについて説明した。
今回は、プロップスのデータを配列にして、
カテゴリーを分けて、データを受け取りたい。親コンポーネントを準備
今回は子にお使いをしてきてもらうので、買い物リストを作成します。
買い物リストには買うものの名前と、売り場を示すcategory
を追加しました。Parent.vue<template> <Child :list = list></Child> </template> <script> import Child from "../components/Child"; export default { components:{Child}, data(){ return{ list:[ {name:"りんご",category:"果物"}, {name:"砂糖",category: "調味料"}, {name:"いちご",category:"果物"} ] } } } </script>子コンポーネントを準備
複数のものをリストアップされてるので。今回は
Array
で受け取ります。
もらったリストを使って、お店の人に「〇〇ください」と伝えます。Child.vue<!--Parent.vueの子コンポーネント--> <template> <div> {{lists[0].name}}ください </div> </template> <script> export default { components:{}, data:function(){ return{} }, props:{ lists:{type:Array} } } </script>
v-for ="変数 in 配列"
v-for
は、foreach文みたいなもの。v-bind
でkeyを設定しなきゃいけない。
第二引数として配列のインデックスを受け取ることができる。
区切る文字in
はof
でもいい。
v-forが指定した要素の中で、配列の中身をとりだして、それぞれに処理を行う。
繰り返したくないものは要素の外に出してあげる。Child.vueスーパーにて、 <div v-for="list in lists" v-bind:key="list.name"> //v-bindは省略できるので、:key=とも書くことができる。 {{list.name}}ください </div>スーパーなら上記のコードですべてが揃うけど、
今は果物屋にいるので、果物だけをピックアップして、「ください」と言いたい。filter関数
配列の中身を特定の条件で絞り込みを行うメソッド。
何が書いてあるんだかよくわからない。配列.filter (絞り込む関数(変数) //←この変数は配列を一つ一つ取り出した要素が入る {絞り込み処理}) //上の行の「絞り込む関数」でこの行の「絞り込み処理」を呼び出している今回、
category
が"果物"のものだけをピックアップしたい。Child.vue<script> computed:{ fruits:function () { //fruitsがfilter関数でピックアップしたものだけが入る変数 return this.lists.filter( //配列listsからfilter関数で値を返してください function (value) { //valueを使ってfunctionという処理を行う。valueはlists)(配列)を一つ一つバラバラにしたものです return value.category ==="果物" //valueのcategoryが"果物"と完全一致したものだけを返します } </script>上記のコードで配列
lists
の中で、category
が果物になっているものだけがfruits
に入りました。ちなみに
computed(算出プロパティ)はmethodみたいなものだけど、methodは毎回処理される。
computedはデータが変わったら処理される。
- 投稿日:2020-06-01T17:03:13+09:00
[Vuetify] v-selectで[object, object] と表示された時の解決法
現象
items: [{id:1, text: 'テキスト'},{id:2, text: 'テキスト2'},{id:3, text: 'テキスト3'}]上記のようなオブジェクトの配列をv-selectで表示。
<v-select :items="items" />すると以下のように
[object, object]
と表示される。
解決策
vuetifyの公式ページにオブジェクト使用時の記述があった。
items-textを使う
item-text="オブジェクトのプロパティ名"
使うことで解決<v-select item-text="text" :items="items" />items-valueも使って値の指定を行う
あくまで表示はできたが、フィルタリングする時など、表示と裏で保持する値をそれぞれ分けたい場合は、
items-value
を使う。実際に値を確認するために、
@change="changedValue"
を追記し、select選択時にconsole.logで値を出力する。<v-select item-text="text" item-value="id" :items="items" @change="changedValue" />methods: { changedValue(value) { console.log('value', value) } }無事、表示と値を分けることができたので解決。
参考文献
- 投稿日:2020-06-01T15:58:49+09:00
Web Components間のデータ通信について
背景
先日、下の投稿でVueコンポーネントのカスタム要素化を試して、無事に親(index.html)から呼び出し可能であることまでは確認しました。
https://qiita.com/yusuke-ka/items/7a46ff75e125f5659f4a
今回は、カスタム要素化したコンポーネントの親子間、子同士でデータのやり取りをしたい場合はどうするのか、について調べてみました。
準備
前回作った以下の3つのサンプルをそれぞれS3で静的ホスティング。
- シンプルなVueのComponentをカスタム要素化したサンプル
- Element-UIを使っているComponentをカスタム要素化したサンプル
- Vuetifyを使っているComponentをカスタム要素化したサンプル
インデックスドキュメントに「bundle.js」を指定することで、それぞれ以下のような形でwebアクセスすればbundle.jsの中身が返ってくるようにしました。
http://simple-vue.s3-website-ap-northeast-1.amazonaws.com
http://simple-element-ui.s3-website-ap-northeast-1.amazonaws.com
http://simple-vuetify.s3-website-ap-northeast-1.amazonaws.comこれらを↓で調べたnuxt-serverless環境(といっても今回動作させるのはローカルのみ)から呼び出して表示してみます。
https://qiita.com/yusuke-ka/items/048eeaf936e74981f303
nuxt-serverlessのREADMEによると、/pagesにはエイリアスのみ記載したtsファイルを置いて、実際のvueファイルは/services/${serviceName}/pages/に置くみたい。
とりあえず、最初からあるtypescript.vueの中身を編集。
typescript.vue<template> <div class="page-typescript"> <h1>{{ greeting }}</h1> <vce-button></vce-button> <hr /> <vce-table prop1="1" prop2="example text" prop3="true" string-prop="123" boolean-prop="false" number-prop="123" long-prop-name="long name" ></vce-table> <hr /> <vce-tree></vce-tree> </div> </template> <script lang='ts'> import { Component, Vue } from "nuxt-property-decorator"; @Component export default class PageTypescript extends Vue { greeting: string = "Hello, TypeScript!"; head() { return { title: "Hello, TypeScript", script: [ { src: "http://simple-vue.s3-website-ap-northeast-1.amazonaws.com" }, { src: "http://simple-element-ui.s3-website-ap-northeast-1.amazonaws.com" }, { src: "http://simple-vuetify.s3-website-ap-northeast-1.amazonaws.com" } ] }; } } </script>画面はこんな感じ
> yarn dev超シンプルなMicro Frontends環境が出来上がりました。
ちなみに、S3のbundle.jsを差し替えれば、親を再ビルドすることなくブラウザの再読み込みで子要素が更新されます。
要するに、それぞれのコンポーネントが独立してデプロイ(リリース)可能ということです。
データ通信
データ通信は以下の3つのデータのやり取りを確認。
- 親から子にデータを送る
- 子から親にデータを送る
- 子から子にデータを送る
親から子にデータを送る
動的にデータ通信できることを確認したいので、親側のh1要素へのマウスオーバー/マウスリーブで、データが変わるようにし、その情報をボタンコンポーネントに送ってみる。
h1要素にmouseoverとmouseleaveのv-onを追加し、scriptタグ内にそれぞれイベント検知時のメソッドであるmouseover()とmouseleave()を追加。
また、scriptタグ内にmessageを定義し、mouseover()とmouseleave()内で値を変更する。
vce-buttonタグに「:message="message"」を追加し、messageの中身を子要素に送るようにして、親側の変更は完了。
typescript.vue<template> <div class="page-typescript"> <h1 v-on:mouseover="mouseover" v-on:mouseleave="mouseleave">{{ greeting }}</h1> <vce-button :message="message"></vce-button> <hr /> ... </div> </template> <script lang='ts'> ... @Component export default class PageTypescript extends Vue { ... message = "off"; ... mouseover() { this.message = "on"; } mouseleave() { this.message = "off"; } } </script>子要素側は以下。
propsでmessageを受け取り、ボタン内に表示する。
Button.vue<template> <div> <button>{{ text }} {{ message }}</button> </div> </template> <script> export default { name: "button", props: { message: String, }, data() { return { text: "button", }; }, }; </script>小さくて分かりづらいですが、ボタン内の文字列が微妙に変わっているのが分かるかと思います。
子から親にデータを送る
今度は子コンポーネントから親コンポーネントにデータを送ってみる。
子コンポーネントは一カ所だけ修正。
buttonタグにクリックイベントを追加して、親のclickedイベントを呼び出し。Button.vue<template> <div> <button @click="$emit('clicked', 'クリック')">{{ text }} {{ message }}</button> </div> </template> ...親側はvce-button要素に子から呼び出されるclickedのイベントを追加して、その際の処理としてhandlerファンクションを追加。
handler内でmessageを更新しているので、これが子コンポーネントに伝わり、ボタンのラベルが変わるはず。
typescript.vue<template> <div class="page-typescript"> ... <vce-button :message="message" @clicked="handler"></vce-button> ... </template> <script lang='ts'> ... handler(event) { this.message = event.detail[0]; } } </script>子コンポーネントがclickedイベントを呼び出す際に引数として渡したデータの取り出し方がなんか通常と違っているような気がしますが(カスタム要素化したから??)、console.log(event)で確認したら、CustomEvent.detailに配列で入っていたので、上記のような取り出し方をしてみました。
正しいやり方なのかは分かりませんが、データを送るという目的は達成。
子から子にデータを送る
以下のサイトによると、3種類のやり方があるらしい。
https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87
- Use a global EventBus
- Use a simple global store
- Use the flux-like library Vuex
1はあまり推奨されないとのことなので、とりあえず3を試したが、結局上手くいかず。
それぞれのコンポーネントでStoreを使うことはできるが、それぞれ別々のStoreになってしまい、データを共有することができないという状況。
カスタム要素化していることが原因か、他所でデプロイしたものを読み込んでいるのが原因かわからないが、インスタンスがうまく共有できていないことが原因なのであれば、1や2の方法でも厳しいかもしれない。
もう少し時間をかけて調べれば、やり方が見つかるのかもしれないけど、疲れたので、正攻法でのデータ送信は一旦諦めることに。。。
子から子にデータを送る (代替案)
これまでの確認で親を経由すれば実現できるのは明らかなので、親を活用したやり方を考えてみる。
ただ、親コンポーネントが子コンポーネントの実装にいちいち関与してたら、独立してデプロイできるようにした意味がないので、対象データの送信元と利用先だけがそれに関わる実装をすれば良いという状況を作りたい。
イメージはこんな感じ。
- 親コンポーネントでデータを一元管理(枠だけ用意するイメージ)。
- データはkey/valueのオブジェクトで、親コンポーネントは中のkeyやvalueに関与しない。
- 親コンポーネントにデータ更新用のメソッドを作成し、引数でkeyとvalueを受け取ってデータを更新する。
- 子コンポーネントがkeyとvalueを指定して、カスタムイベント経由で親に定義した更新用のメソッドを呼び出す。
- データが更新されるとVueのリアクティブデータの機能で、すべての子コンポーネントに更新後のデータを送信する。
- 子コンポーネントは親コンポーネントから、オブジェクトのデータを受け取り、自分が必要な要素のkeyを指定して値を取り出す。
とりあえず思いついた案を試してみる。
まず親コンポーネントから。
typescript.vue<template> <div class="page-typescript"> ... <vce-button ... :param="getParam" @commit="commit($event)" ></vce-button> ... <vce-table ... :param="getParam" @commit="commit($event)" ></vce-table> ... <vce-tree :param="getParam" @commit="commit($event)"></vce-tree> </div> </template>template内のすべての子コンポーネントのタグに、何も考えずに以下の2つを追加する。
「:param="getParam()"」
「@commit="commit($event)"」上がデータ配布用。
下がデータ更新用。※今回、データはparamという名前で扱うことにした。
次がscript部分。
typescript.vue<script lang="ts"> ... @Component export default class PageTypescript extends Vue { ... param: any = {}; ... get getParam() { return JSON.stringify(this.param); } commit(event) { this.$set(this.param, event.detail[0], event.detail[1]); } } </script>「param: any = {};」でparamという名前でデータを定義。
「get getParam()」はデータを文字列化して返している。
最初はオブジェクトのまま渡そうかと思っていたが、カスタム要素化しているためか、子コンポーネントに渡った時に[object Object]という文字列になってしまうので、仕方なくこうした。
なお、算出プロパティにしないと、無駄に子コンポーネントの数だけ実行されてしまう。「commit(event)」は子コンポーネント側が渡してきたkeyとvalueをparamに登録している。
vueはオブジェクトの要素追加や削除をリアクティブに検知できないので、単純にthis.paramに要素を追加するのではなく、this.$set()を使って要素を追加している。続いて、データを更新する子コンポーネント。
以前、カスタム要素化したツリーのコンポーネントを使って、ツリーの要素が選択されたら、親のイベントを呼んでデータを更新するようにしてみる。
tree.vue<template> <div id="app"> <v-app id="inspire"> <v-treeview ... @input="handler" ></v-treeview> </v-app> </div> </template>vuetifyのツリー要素選択のイベントがinputということだったので、「@input="handler"」でhandler()メソッドを呼ぶようにしている。
続いてscript。
tree.vue<script> ... export default { ... methods: { handler(selected) { this.$emit("commit", "key", selected); }, }, props: { param: String, }, ... }; </script>methodsにhandlerメソッドを追加。引数のselectedには、選択しているツリー要素のIDが配列で入っている(vuetifyが入れてくれる)。
$emit()で親コンポーネントのcommitイベントを呼び出している。propsにparamを追加しているが、今回は使っていないのであまり意味はない。
続いて、データを受け取る側の子コンポーネント。
またボタンコンポーネントを利用。button.vue<template> <div> <button @click="$emit('clicked', 'クリック')"> {{ text }} {{ message }} {{ getParam }} </button> </div> </template>{{ getParam }}を追加しただけ。
script部分。
button.vue<script> export default { name: "button", props: { message: String, param: String, }, data() { return { text: "button", param: "", }; }, computed: { getParam() { var map = JSON.parse(this.param); return map["key"]; }, }, }; </script>propsにparam: Stringを追加して親から受け取れるようにしている。
ちなみに、String部分をObjectにすればオブジェクトのまま受け取れるかと思ったが、駄目だった。dataにもparamを追加。propsにあるからいらねんじゃね?と思ったが、他でthis.paramを呼び出したときに怒られたので、追加した。これは、もっと他に上手いやり方がありそう。
最後、computedにgetParam()を追加。this.paramは文字列として渡ってくるので、オブジェクト化した後に、指定したキーに対応する値を取得して返している。
同様にして、別の子コンポーネントでも同じデータを受け取れるようにした。
(一つ目の子コンポーネントにしかデータが送れないということがありそうな気がしたので。)table.vue<template> <div class="card card--primary"> <h4>{{ message }} {{ getParam }}</h4> ... </div> </template> <script> export default { props: { param: String, ... }, data() { return { message: "Custom Element By Vue + Element UI", param: "", }; }, computed: { getParam() { var map = JSON.parse(this.param); return map["key"]; }, ... }, ... }; </script>説明は省略。
動作確認してみる。
見づらいですが、一応、ツリーで選択した要素のIDが、ボタンのコンポーネント(ボタンのラベル)とテーブルのコンポーネント(タイトル部分の横)に動的に表示されています。
さいごに
今回はWeb Components間のデータ通信について調べてみました。
Custom Element化(さらに呼び出し部分を疎結合に)したことで、通常のVueのコンポーネントと異なる動作をする部分があり、なかなか上手くいきませんでした。
特に子コンポーネント間のデータ通信は、Vuexなどによっていい感じにデータを管理できないか、もうちょっと調べてみたいところです。
最後の「子から子にデータを送る (代替案)」については、Vuexのようにどのコンポーネントからもアクセスできるようにはなっていませんが、親コンポーネント側は最初に今回の実装を入れてしまえば、後はノータッチでいけるので、負担は幾分か減ると思います。
ただ、利用のルールは決めておかないと、キーが重複したりとか問題が発生しそうですね。まあ、わざわざコンポーネントを疎結合にしているわけだから、各コンポーネントで必要な"状態"は各コンポーネント内で持ってもらって、他とのデータのやり取りは最低限にすれば、そんなに大変なことにならない気もしますが…。
あと、親コンポーネントが普通のVueコンポーネントも一緒に抱えるなら、データを一元管理する部分はVuexとか使ってもいいかもです。
- 投稿日:2020-06-01T12:48:38+09:00
Express.jsにPassport.jsで、任意のフォルダ配下のみをBasic認証する
概要
ExpressフレームワークにBasic認証を適用する方法について記載します。
任意のフォルダのみにBasic認証を適用するものとし、そのフォルダにVue-CLIによるSPA(=Single Page Application)を置くことを目的とします。
方針
最小限のBasic認証を認証する場合はExpress.jsプロジェクトにて提供されるbasic-auth-connectライブラリでもよいのですが、他の認証への差し替えの容易性を考慮して、Passport.js を用いることとします。
- basic-auth-connect
- Passport.js
認証を要求する領域と、不要とする領域を共存させたいので、任意のフォルダ配下のみをBasic認証の対象とします。
ついでに、Vue CLIで作成したSPAを任意フォルダへ配置するものとします。
なお、ユーザー名は「user」でパスワードは「pass」とします。本サンプルコードは動作確認が目的なので、パスワード管理はガバガバです。
(補足)Express3とExpress4の違い(basic-auth-coonect)
Express.jsでBasic認証を、で検索すると「
express.basicAuth()
」での実装例が時々ひっかかります。これは、Express3での記法とのことです。現在のExpress4では、「var basicAuth = require('basic-auth-connect');」とする仕組みに変わっています。ref. https://qiita.com/zaru/items/51b415c80245920837ff
前提条件
AzureのWebアプリ(PaaS)を利用して公開する前提で、Express.jsを構築します。
具体的には、次のページの手順に従って作成します。
- Node.js Express アプリをビルドして Azure Cloud Services にデプロイする
作成し終えたファイル一式は以下のようになります(※Mocha.jsでのテストを前提としてtestフォルダも作成しているが、この記事では使わない)。
ref. https://github.com/hoshimado/tdd-vuejs-book/tree/master/appendix/b-azure-express-mocha
先ずは、全体をBasic認証の対象とする方法
Passport.jsの必要なモジュールを次のようにインストールします。
npm install passport passport-http --save続いて、app.jsを次のように変更します。
// +++ ここから +++ var passport = require('passport'); var passportHttp = require('passport-http'); passport.use(new passportHttp.BasicStrategy( function (username, password, done) { if(username=='user' && password=='pass'){ // ユーザー名とパスワードが有効なら true を返却する。 return done(null,true); }else{ // 無効なら、false を返却する(とUnauthorizedを画面表示) return done(null, false); } } )); app.use('/', passport.authenticate('basic',{session: false}) ); // --- ここまで --- app.use(express.static(path.join(__dirname, 'public'))); // この行の前に、↑を追加する。この状態で、サーバーを「
npm run dev
」で起動すると、全体に対してBasic認証が設定されます。次に、任意のフォルダ配下のみをBasic認証の対象とする方法
先の節で追加した部分を元に戻します。
本サンプルでは「/auth/byvue」へのアクセスに対してのみ、Basic認証を掛けるものとします。
任意のフォルダ配下のみにBasic認証を掛けるには「ルーティング設定で対象フォルダに対してpassport.authenticate()のミドルウェアを指定し、認証成功時にexpress.static()で静的ファイルを表示する(か、次のルーティングへ渡す)」ようにします。
具体的には、例えば次のようにします。(※本サンプルでは、対象を静的ファイルとします)。
- app.jsで、publicフォルダ配下の静的ファイルを処理している1行をコメントアウト
- 制御用のファイル
routes/auth.js
を作成して、対象パスへのアクセスをそちらへルーティングapp.jsvar express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); // app.use(express.static(path.join(__dirname, 'public'))); // ★コメントアウトする //app.use('/', indexRouter); // ★利用していないので、ここもコメントアウトする app.use('/auth', require('./routes/auth')); // ★追加する app.use('/users', usersRouter); module.exports = app;
- 作成したファイル
routes/auth.js
に次を記述auth/byvue
フォルダと、auth/simply
フォルダ配下に適当な表示用htmlファイルを置くauth.jsvar express = require('express'); var path = require('path'); var router = express.Router(); var passport = require('passport'); var passportHttp = require('passport-http'); passport.use(new passportHttp.BasicStrategy( function (username, password, done) { if(username=='user' && password=='pass'){ return done(null,true); }else{ return done(null, false); } } )); router.use('/byvue', passport.authenticate('basic',{session: false}), express.static(path.join(__dirname, '../auth/byvue')) ); // ★ここにだけBasic認証を掛ける router.use('/', express.static(path.join(__dirname, '../auth')) ); // ★それ以外はそのまま表示する。 module.exports = router;上記までを終えたら、「
npm run dev
」でhttpサーバーを起動します。下記へアクセスすると、Basic認証を求められます。
(※本サンプルでは、express.static()
でマッピングするフォルダを実際のパスに合わせてますが、異なるフォルダをマッピングしてももちろん構いません。)http://localhost:3000/auth/byvue/
それ以外の、例えば下記へアクセスすると、Basic認証無しで表示されます。
http://localhost:3000/auth/simply/
もちろん、下記へのアクセスもBasic認証無し、となります。
Basic認証については以上です。
オマケとして、任意フォルダ配下へVue CLIでBuildしたファイルを配置する方法
オマケです(当方にとっては、こっちが目的だったので)。
上記の「auth/byvue」のフォルダに、Vue CLIで作成したファイルを配置するには、次のようにします。本サンプルではExpress用の
src
フォルダと同じ階層にVue CLIのプロジェクトをcli-vue
という名称で作成したと仮定します。
cli-vue
フォルダ直下に、vue.config.js
ファイルを次のように配置する(ファイル名は固定)- 「npm run build」でビルドする
vue.config.jsmodule.exports = { // options... outputDir : '../src/auth/byvue', publicPath : './' }ここで、それぞれのオプションは以下を意味します。
outputDir
: ビルドしたファイルの出力先フォルダパス。このフォルダは毎回「削除→再作成」されることに注意。publicPath
: ビルドしたhtmlファイルの「基底URL」(HTMLのbaseタグ)。デフォルトでは絶対パスの「/
」。
- 今回のサンプルでは「サブフォルダ」に配置したかったので、「相対パス(
./
)」で扱うように設定ref. https://cli.vuejs.org/config/#publicpath
上記までを終えたら、「
npm run dev
」でhttpサーバーを起動します。下記へアクセスすると、Basic認証を経たのちにVue CLIで作成されたページが表示されます。
http://localhost:3000/auth/byvue/
以上ー。
- 投稿日:2020-06-01T12:38:08+09:00
Vue.JSで論理演算子を使う
v-ifや-showで複数の条件を指定したい場合。
Javascriptで用意されている物を使うことができる。
論理演算子 - JavaScript | MDNAかつB
v-if = "A && B"
AまたはB
v-if = "A || B"
- 投稿日:2020-06-01T12:08:28+09:00
Lravel MixでVue.js+Bootstrapの簡単なログインフォームを作る。
FIRST PLAN株式会社のフロントエンドエンジニアtakeです。
sampleText
さて、今回はテストでログインフォームを作ったので良ければご覧ください。
こんな事をやりました
環境設定
まずはVue.js。
今回はLaravel内で使っているので、LaravelでVue.jsを使えるようにするには以下のコマンドを実行します。$ composer require laravel/ui $ php artisan ui vue --authすぐにvueを始めたい場合は公式ドキュメントにある通りCDNでも使えますし、初めからwebpackの環境構築がされている「Vue CLI」もあるので活用してみてください。
ただ今回は環境設定の記事ではないのでそこら辺は割愛します。そして次にBootstrapですが、BootstrapVueを使用します。
機能は同じでBootstrapの記法もそのまま使えるのですが、jQuery依存だったコンポーネントがVueのディレクティブが使えるように拡張されています。$ npm install bootstrap-vue bootstrapでインストールし、
以下のコードをメインのjsファイルに追加します。import BootstrapVue from 'bootstrap-vue'; Vue.use(BootstrapVue);後は好みの問題なのですが、FontAwesomeとvue-sweetalert2をインストールします。
vue-sweetalert2もBootstrapVueと同じで機能は元のsweetalert2と変わりませんが、Vueでそちらを使うとアイコン部分で表示崩れが起きる事があるのでそれを修正したものになります。
Bootstrapと同様にこちらもインストールします。FontAwesome
$ npm install --save @fortawesome/fontawesome-freeimport '@fortawesome/fontawesome-free/css/all.css';vue-sweetalert2
$ import VueSweetalert2 from 'vue-sweetalert2';import 'sweetalert2/dist/sweetalert2.min.css'; Vue.use(VueSweetalert2);コード
html
今回は以下のような感じで、
card
とinput-group
を使って実装しました。HTML<template> <section class="login-form d-flex flex-solumn justify-content-center align-items-center w-100 text-center"> <div class="login-form__field card rounded-0 pt-5 pb-3"> <i class="login-form__budge fas fa-user-circle text-primary bg-white rounded-circle"></i> <div class="card-body"> <h1 class="login-form__heading card-title text-primary font-weight-bold mb-0">Sign in</h1> <hr class="bg-primary mt-0 mb-4"> <p class="card-text text-muted mb-4">ユーザー名とパスワードを入力してください。</p> <div class="input-group mx-auto mb-4"> <div class="input-group-prepend"> <span class="input-group-text bg-primary text-white" id="basic-addon1"> <i class="fas fa-user"></i> </span> </div> <input class="form-control" type="text" placeholder="Username" v-model="login.user_name"> </div> <div class="input-group mx-auto mb-4"> <div class="input-group-prepend"> <span class="input-group-text bg-primary text-white" id="basic-addon1"> <i class="fas fa-key"></i> </span> </div> <input class="form-control rouded-0" type="password" placeholder="Password" v-model="login.password"> </div> <button class="login-form__button btn btn-primary w-75 mb-4" type="button" :disabled="checkInput" @click="signIn">Login </button> <div class="login-form__attention d-flex justify-content-between my-o mx-auto"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" id="remember-me" v-model="login.remember"> <label class="custom-control-label" for="remember-me">情報を記憶する</label> </div> <a class="card-link" href="#"> <u>パスワードを忘れた方はこちら</u> </a> </div> </div> </div> </section> </template>Scss
今回はBootstrapベースなので、BEMを採用して影のエフェクト以外は軽く整える程度にしました。
scss.login-form { &__field { position: relative; max-width: 30rem; //影エフェクト &:after { content: ''; position: absolute; bottom: 20px; right: 55px; z-index: -1; width: 50%; height: 100px; box-shadow: 100px 0 10px 15px rgba(0, 0, 0, .3); transform: skew(-40deg); } } //__field &__budge { position: absolute; top: -2rem; left: 50%; font-size: 6rem; transform: translateX(-50%); } //__budge &__attention { font-size: 0.6rem; } //__attention &__button { border-radius: 0!important; } //__button } // .login-formJavaScript
これがデフォルトのVueの記述方法になります。
ざっくり説明するとdata
は変数等を格納する場所・computed
は変更があるとリアルタイムで値を再計算してくれる場所・methods
は文字通りメソッドを格納する場所です。
他にもwatch
やライフサイクルフック等開発に便利な機能が色々とあるので、詳しくは公式ドキュメントをご参照ください。まず
data
内のlogin
は「ユーザーネーム・パスワード・ユーザー情報を記憶するか否か」を保持しています。
html内にv-model="login.user_name"
といった記述があると思いますが、このv-model
によって双方向データバインディングを実現しています。
これによりデータと表示の同期・DOM操作を自力でやる必要がなくなります。次は
computed
ですが、今回はlogin.user_name
とlogin.password
が条件に組み込まれていますが、どちらかの値が変更された段階で自動で値を更新します。
メソッド内にif (!this.checkInput)
と指定されている関数がありますが、このように使用する事で、他の場所で毎回入力を確認する必要がなくなります。最後に
methods
は一番簡単で、関数を格納する場所になります。
とりあえず確認用にv-model
で同期した値をsweetalert2で表示しています。JavaScriptexport default { data() { return { //ログイン情報 login: { user_name: '', password: '', remember: false } } }, computed: { //ユザーネームとパスワードの両者が入力されている事を判定する値、自動で変更を検知し反映してくれる checkInput() { return (!this.login.user_name || !this.login.password) ? true : false; } }, methods: { signIn() { //ユーザーネームとパスワードの両者が入力されていた時のみ発火する。 if (!this.checkInput) { //v-modelで同期した値を出力する this.$swal({ icon: 'info', title: 'Information', text: ` ID: ${this.login.user_name}, Password: ${this.login.password}, Remember: ${this.login.remember} ` }) } return; } //signIn } }最後に
$ npm run dev
でコンパイルして確認すると...
キチンと動いています。
よろしければ参考にしてみて下さい。
- 投稿日:2020-06-01T12:08:28+09:00
Laravel MixでVue.js+Bootstrapの簡単なログインフォームを作る。
FIRST PLAN株式会社のフロントエンドエンジニアtakeです。
sampleText
さて、今回はテストでログインフォームを作ったので良ければご覧ください。
こんな事をやりました
環境設定
まずはVue.js。
今回はLaravel内で使っているので、LaravelでVue.jsを使えるようにするには以下のコマンドを実行します。$ composer require laravel/ui $ php artisan ui vue --authすぐにvueを始めたい場合は公式ドキュメントにある通りCDNでも使えますし、初めからwebpackの環境構築がされている「Vue CLI」もあるので活用してみてください。
ただ今回は環境設定の記事ではないのでそこら辺は割愛します。そして次にBootstrapですが、BootstrapVueを使用します。
機能は同じでBootstrapの記法もそのまま使えるのですが、jQuery依存だったコンポーネントがVueのディレクティブが使えるように拡張されています。$ npm install bootstrap-vue bootstrapでインストールし、
以下のコードをメインのjsファイルに追加します。import BootstrapVue from 'bootstrap-vue'; Vue.use(BootstrapVue);後は好みの問題なのですが、FontAwesomeとvue-sweetalert2をインストールします。
vue-sweetalert2もBootstrapVueと同じで機能は元のsweetalert2と変わりませんが、Vueでそちらを使うとアイコン部分で表示崩れが起きる事があるのでそれを修正したものになります。
Bootstrapと同様にこちらもインストールします。FontAwesome
$ npm install --save @fortawesome/fontawesome-freeimport '@fortawesome/fontawesome-free/css/all.css';vue-sweetalert2
$ import VueSweetalert2 from 'vue-sweetalert2';import 'sweetalert2/dist/sweetalert2.min.css'; Vue.use(VueSweetalert2);コード
HTML
今回は以下のような感じで、
card
とinput-group
を使って実装しました。HTML<template> <section class="login-form d-flex flex-solumn justify-content-center align-items-center w-100 text-center"> <div class="login-form__field card rounded-0 pt-5 pb-3"> <i class="login-form__budge fas fa-user-circle text-primary bg-white rounded-circle"></i> <div class="card-body"> <h1 class="login-form__heading card-title text-primary font-weight-bold mb-0">Sign in</h1> <hr class="bg-primary mt-0 mb-4"> <p class="card-text text-muted mb-4">ユーザー名とパスワードを入力してください。</p> <div class="input-group mx-auto mb-4"> <div class="input-group-prepend"> <span class="input-group-text bg-primary text-white" id="basic-addon1"> <i class="fas fa-user"></i> </span> </div> <input class="form-control" type="text" placeholder="Username" v-model="login.user_name"> </div> <div class="input-group mx-auto mb-4"> <div class="input-group-prepend"> <span class="input-group-text bg-primary text-white" id="basic-addon1"> <i class="fas fa-key"></i> </span> </div> <input class="form-control rouded-0" type="password" placeholder="Password" v-model="login.password"> </div> <button class="login-form__button btn btn-primary w-75 mb-4" type="button" :disabled="checkInput" @click="signIn">Login </button> <div class="login-form__attention d-flex justify-content-between my-o mx-auto"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" id="remember-me" v-model="login.remember"> <label class="custom-control-label" for="remember-me">情報を記憶する</label> </div> <a class="card-link" href="#"> <u>パスワードを忘れた方はこちら</u> </a> </div> </div> </div> </section> </template>CSS
飽くまでBootstrapベースなので、scssでBEMを採用して影のエフェクト以外は軽く整える程度にしました。
scss.login-form { &__field { position: relative; max-width: 30rem; //影エフェクト &:after { content: ''; position: absolute; bottom: 20px; right: 55px; z-index: -1; width: 50%; height: 100px; box-shadow: 100px 0 10px 15px rgba(0, 0, 0, .3); transform: skew(-40deg); } } //__field &__budge { position: absolute; top: -2rem; left: 50%; font-size: 6rem; transform: translateX(-50%); } //__budge &__attention { font-size: 0.6rem; } //__attention &__button { border-radius: 0!important; } //__button } // .login-formJavaScript
以下がデフォルトのVueの記述方法になります。
ざっくり説明すると
data
は変数等を格納する場所・computed
は変更があるとリアルタイムで値を再計算してくれる場所・methods
は文字通りメソッドを格納する場所です。
他にもwatch
やライフサイクルフック等開発に便利な機能が色々とあるので、詳しくは公式ドキュメントをご参照ください。さて、まず
data
内のlogin
は「ユーザーネーム・パスワード・ユーザー情報を記憶するか否か」を保持しています。
html内にv-model="login.user_name"
といった記述があると思いますが、このv-model
によって双方向データバインディングを実現しています。
どちらかが変更された段階でもう片方にも自動で変更が同期されるため、自分で逐一document.querySelector(element)~
をする必要がなくなります。次は
computed
ですが、今回はlogin.user_name
とlogin.password
が条件に組み込まれていますが、どちらかの値が変更された段階で自動で値を更新します。
メソッド内にif (!this.checkInput)
と指定されている関数がありますが、このように使用する事で他の場所で毎回入力を確認する必要がなくなります。
ちなみにbutton
にある:disabled="checkInput"
にも使用されていますが、まずこの:
マークはv-bind
といってhtml内の属性(class
やhref
等)に付ける事で内部でJavaScriptのコードを直接使用可能にするものです。
今回はこれらを利用してdisabled
の値をフォーム入力の有無によって動的に変更しています。最後に
methods
は一番簡単で、関数を格納する場所になります。
とりあえず確認用にv-model
で同期した値をsweetalert2で表示しています。
イベントハンドリングはhtml側で@event="hoge"
のように行います。(今回の場合は@click="signIn"
)
ちなみにこの@click
以下はコンパイル時に削除されるため、htmlを汚染する心配はありません。JavaScriptexport default { data() { return { //ログイン情報 login: { user_name: '', password: '', remember: false } } }, computed: { //ユーザーネームとパスワードの両者が入力されている事を判定する値、自動で変更を検知し反映してくれる checkInput() { return (!this.login.user_name || !this.login.password) ? true : false; } }, methods: { signIn() { //ユーザーネームとパスワードの両者が入力されていた時のみ発火する。 if (!this.checkInput) { //v-modelで同期した値を出力する this.$swal({ icon: 'info', title: 'Information', text: ` ID: ${this.login.user_name}, Password: ${this.login.password}, Remember: ${this.login.remember} ` }) } return; } //signIn } }最後に
$ npm run dev
でコンパイルして確認すると...
キチンと動いています。
よろしければ参考にしてみて下さい。
- 投稿日:2020-06-01T10:39:10+09:00
Vuetifyをインストールした環境でJestを実行する設定
Vuetifyをインストールした環境で、Jestを利用するときにしておいた方がいい設定です。
それぞれの設定は、tests/unitディレクトリに作成したsetup.jsを書きます。tests/unit/setup.jsの設定を反映するには、jest.config.jsに、以下を設定します。
jest.config.js"setupFiles": ["./tests/unit/setup.js"]警告を出力しない設定
[Vue warn]: Unknown custom element:〜
VuetifyのコンポーネントをJestが認識しないために出力される警告です。tests/unit/setup.jsに以下を設定することで出力されなくなります。
tests/unit/setup.jsimport Vue from "vue"; import Vuetify from "vuetify"; Vue.use(Vuetify);Download the Vue Devtools extension for a better development experience:〜
ブラウザの場合にVue Devtoolsの拡張機能をインストールすることを勧めるメッセージのようですが、Jestの環境には意味のないメッセージです。tests/unit/setup.jsに以下の設定をすることで出力されなくなります。
tests/unit/setup.jsVue.config.devtools = false;You are running Vue in development mode.〜
production環境へデプロイするときにproduction modeに切り替えることを注意するメッセージのようですが、これもテストの実行中には意味のないメッセージです。
tests/unit/setup.jsに以下の設定をすることで出力されなくなります。
tests/unit/setup.jsVue.config.productionTip = false;ボタンの置き替え
Vuetifyをインストールした環境では、buttonの見た目が変わってしまいます。下の図のように、ボタンの枠がなくなって、立体的に見えなくなっています。
以下のようにv-btnに置き換えることができます。
<v-btn id="vBtnClick" @click="onVbtnClick">v-btn</v-btn>クリックされた場合に、onVbtnClickメソッドを呼び出すように設定しています。
ブラウザで実行した場合には、onVbtnClickメソッドが呼び出されます。しかし、以下のテストではonVbtnClickメソッドが呼び出されないで失敗します。
describe("v-btnがクリックしたときに、割り当てられたメソッドが呼び出されたことを確認する", () => { it("v-btnがクリックしたときに、割り当てられたメソッドが呼び出されたことを確認する", () => { const wrapper = shallowMount(SampleTextArea); const onVbtnClick = jest.fn(); wrapper.setMethods({onVbtnClick}); wrapper.find("#vBtnClick").trigger("click"); expect(onVbtnClick).toHaveBeenCalledTimes(1); }); });v-btnの設定を以下のように変えることでテストは成功します。
<v-btn id="vBtnClick" @click.native="onVbtnClick">v-btn</v-btn>
- 投稿日:2020-06-01T08:31:09+09:00
ノーバンドルなビルドツール「Vite」を試してみる【no bundle dev enviroment for Vue 3.0】
最近猛烈にスターを集めているビルドツール、Viteを触ってみたので簡単に紹介します。
この記事はVite v0.19.1
時点での情報です。Viteとは?
ViteはVue.jsの作者のEvan You氏が開発中のノーバンドルなビルドツールです。
ネイティブのESモジュールのインポートを利用しバンドル不要で高速に動作するdevサーバーと、Rollup.jsをベースとしたプロダクションビルド機能を提供します。
設定不要で.vue
のSFCをコンパイルできて、さらにデフォルトで今開発中のVue3.0が使えます。
しかも、vue-cliのようにVue.js限定ではなく、React、Preactにも対応しています。注意
Still experimental, but we intend to make it suitable for production.
とある通り、まだ絶賛開発中です。プロダクションで使うのは控えた方が良さそうです。
何が嬉しいの?
VueのSFC(Single File Components)での開発ならVue CLIで良いのでは?と思うかもしれません。
Viteを使う利点は以下の通りです。
- 開発時はバンドル不要で動作するので、大規模プロジェクトでも初回起動が非常に早い
- HMR(画面の再描画無しにファイル変更をブラウザに適用してくれる機能)が、モジュールの総数と切り離されているため一貫して高速に動作する
ざっくり言うと総じて動作が早いです。開発体験が良い。
他詳細な説明はREADMEにも記載されているのでそちらもどうぞ。https://github.com/vitejs/vite#how-and-why
インストールとプロジェクト作成
VueとReactのプロジェクト作成を簡単に説明します。
Vue
以下コマンドでプロジェクトを作成します。
$ npm init vite-app example-vue
これだけでOKです。example-vueにVueのプロジェクトが作成されます。
あとは依存モジュールをinstallして起動するだけです。$ cd example-vue $ npm i $ npm run devReact
--template react
でReactのテンプレートを指定したうえでプロジェクトを作成します。$ npm init vite-app --template react example-reactexample-reactにReactのプロジェクトが作成されます。
あとはvueと同じく起動するだけです。$ cd example-react $ npm i $ npm run devビルド
ViteではRollup.jsを内部的に使ってリソースをバンドルしプロダクションビルドが可能です。
buildコマンドでビルドが実行されます。$ npm run build
dist
配下に成果物が生成されるので、あとはそれを公開するだけです。細かいビルドオプションを設定したい場合は、コマンド引数で指定するか、設定ファイルを作り記述できます。
以下成果物のディレクトリをout
ディレクトリに変更する例です。プロジェクトルートに
vite.config.js
を追加して以下を記述します。vite.config.jsmodule.exports = { outDir: "out" }あとはそのままビルドすれば
out
ディレクトリに成果物が生成されます。他設定可能なオプションはソースコードをみるのが良さそうです。
https://github.com/vitejs/vite/blob/master/src/node/config.tsTypeScriptへの対応
TypeScriptへの対応も設定なしで可能です。Vueの場合は
.vue
で<script lang="ts">
を設定するだけ。
Reactの場合は、.jsx
を.tsx
に変更するだけです。
.ts
ファイルもそのままimportできます。App.vue<template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue + Vite" /> </template> <script lang="ts"> import HelloWorld from './components/HelloWorld.vue' import { defineComponent } from 'vue' // Composition APIなので defineComponent()を利用 export default defineComponent({ name: 'App', components: { HelloWorld }, setup() { return {} } }) </script>注意点
2020/05/31現在、TypeScriptはJSへのトランスパイルのみ対応していて、型チェックは行えません。
基本的にエディタ上での型エラーの確認と、tsconfig.jsonを追加しビルド前にtsc --noEmit
を利用して型チェックを行ってください。PostCSS、Scssへの対応
Viteは、
.vue
ファイルとインポートされた.css
ファイルの全てにPostCSSを自動的に適用します。
必要なPostCSSのプラグインをnpmでインストールして、あとはpostcss.config.js
をプロジェクトルートに追加するだけです。Autoprefixerの利用例。
$ npm i autoprefixer --save-devpostcss.config.jsmodule.exports = { plugins: [ require('autoprefixer')() ] }CSS Pre-Processorsのscssも、
sass
をnpmインストールして、.vue
ファイルで<style lang="scss">
を指定するだけで利用できます。$ npm i sass --save-dev<style lang="scss"> /* scss */ </style>終わりに
とても高速に動作するのでViteかなり良いですね。あと、Vue 3.0のsandbox環境としても最高です。
今回紹介した以外にも機能盛り沢山なので、是非READMEを読んで欲しいです。
https://github.com/vitejs/vite今後もさらに機能が拡張されるようなので引き続き動向を見ていきたいです。
最後に。Evan You氏のVite作成時の投稿がカッコ良すぎる。
こんなこといつか言ってみたいです。As I was going to bed, I had an idea about a no-bundler dev setup (using native browser ES imports), but with support for Vue SFCs with hot reload. Now it's almost 6AM and I have PoC working. The hot reload is so fast it's near instant.
— Evan You (@youyuxi) April 20, 2020参考
- 投稿日:2020-06-01T08:31:09+09:00
ノーバンドルな開発ツール「Vite」を試してみる【no bundle dev enviroment for Vue 3.0】
最近猛烈にスターを集めている開発ツール、Viteを触ってみたので簡単に紹介します。
この記事はVite v0.19.1
時点での情報です。Viteとは?
ViteはVue.jsの作者のEvan You氏が開発中のノーバンドルな開発ツールです。
ネイティブのESモジュールのインポートを利用しバンドル不要で高速に動作するdevサーバーと、Rollup.jsをベースとしたプロダクションビルド機能を提供します。
設定不要で.vue
のSFCをコンパイルできて、さらにデフォルトで今開発中のVue3.0が使えます。
しかも、vue-cliのようにVue.js限定ではなく、React、Preactにも対応しています。注意
Still experimental, but we intend to make it suitable for production.
とある通り、まだ絶賛開発中です。プロダクションで使うのは控えた方が良さそうです。
何が嬉しいの?
VueのSFC(Single File Components)での開発ならVue CLIで良いのでは?と思うかもしれません。
Viteを使う利点は以下の通りです。
- 開発時はバンドル不要で動作するので、大規模プロジェクトでも初回起動が非常に早い
- HMR(画面の再描画無しにファイル変更をブラウザに適用してくれる機能)が、モジュールの総数と切り離されているため一貫して高速に動作する
ざっくり言うと総じて動作が早いです。開発体験が良い。
他詳細な説明はREADMEにも記載されているのでそちらもどうぞ。https://github.com/vitejs/vite#how-and-why
インストールとプロジェクト作成
VueとReactのプロジェクト作成を簡単に説明します。
Vue
以下コマンドでプロジェクトを作成します。
$ npm init vite-app example-vue
これだけでOKです。example-vueにVueのプロジェクトが作成されます。
あとは依存モジュールをinstallして起動するだけです。$ cd example-vue $ npm i $ npm run devReact
--template react
でReactのテンプレートを指定したうえでプロジェクトを作成します。$ npm init vite-app --template react example-reactexample-reactにReactのプロジェクトが作成されます。
あとはvueと同じく起動するだけです。$ cd example-react $ npm i $ npm run devビルド
ViteではRollup.jsを内部的に使ってリソースをバンドルしプロダクションビルドが可能です。
buildコマンドでビルドが実行されます。$ npm run build
dist
配下に成果物が生成されるので、あとはそれを公開するだけです。細かいビルドオプションを設定したい場合は、コマンド引数で指定するか、設定ファイルを作り記述できます。
以下成果物のディレクトリをout
ディレクトリに変更する例です。プロジェクトルートに
vite.config.js
を追加して以下を記述します。vite.config.jsmodule.exports = { outDir: "out" }あとはそのままビルドすれば
out
ディレクトリに成果物が生成されます。他設定可能なオプションはソースコードをみるのが良さそうです。
https://github.com/vitejs/vite/blob/master/src/node/config.tsTypeScriptへの対応
TypeScriptへの対応も設定なしで可能です。Vueの場合は
.vue
で<script lang="ts">
を設定するだけ。
Reactの場合は、.jsx
を.tsx
に変更するだけです。
.ts
ファイルもそのままimportできます。App.vue<template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue + Vite" /> </template> <script lang="ts"> import HelloWorld from './components/HelloWorld.vue' import { defineComponent } from 'vue' // Composition APIなので defineComponent()を利用 export default defineComponent({ name: 'App', components: { HelloWorld }, setup() { return {} } }) </script>注意点
2020/05/31現在、TypeScriptはJSへのトランスパイルのみ対応していて、型チェックは行えません。
基本的にエディタ上での型エラーの確認と、tsconfig.jsonを追加しビルド前にtsc --noEmit
を利用して型チェックを行ってください。PostCSS、Scssへの対応
Viteは、
.vue
ファイルとインポートされた.css
ファイルの全てにPostCSSを自動的に適用します。
必要なPostCSSのプラグインをnpmでインストールして、あとはpostcss.config.js
をプロジェクトルートに追加するだけです。Autoprefixerの利用例。
$ npm i autoprefixer --save-devpostcss.config.jsmodule.exports = { plugins: [ require('autoprefixer')() ] }CSS Pre-Processorsのscssも、
sass
をnpmインストールして、.vue
ファイルで<style lang="scss">
を指定するだけで利用できます。$ npm i sass --save-dev<style lang="scss"> /* scss */ </style>終わりに
とても高速に動作するのでViteかなり良いですね。あと、Vue 3.0のsandbox環境としても最高です。
今回紹介した以外にも機能盛り沢山なので、是非READMEを読んで欲しいです。
https://github.com/vitejs/vite今後もさらに機能が拡張されるようなので引き続き動向を見ていきたいです。
最後に。Evan You氏のVite作成時の投稿がカッコ良すぎる。
こんなこといつか言ってみたいです。As I was going to bed, I had an idea about a no-bundler dev setup (using native browser ES imports), but with support for Vue SFCs with hot reload. Now it's almost 6AM and I have PoC working. The hot reload is so fast it's near instant.
— Evan You (@youyuxi) April 20, 2020参考
- 投稿日:2020-06-01T08:31:09+09:00
ノーバンドルな開発ツール「Vite」を触ってみる 【no bundle dev enviroment for Vue 3.0】
最近猛烈にスターを集めている開発ツール、Viteを触ってみたので簡単に紹介します。
この記事はVite v0.19.1
時点での情報です。Viteとは?
ViteはVue.jsの作者のEvan You氏が開発中のノーバンドルな開発ツールです。
ネイティブのESモジュールのインポートを利用しバンドル不要で高速に動作するdevサーバーと、Rollup.jsをベースとしたプロダクションビルド機能を提供します。
設定不要で.vue
のSFCをコンパイルできて、さらにデフォルトで今開発中のVue3.0が使えます。
しかも、vue-cliのようにVue.js限定ではなく、React、Preactにも対応しています。注意
Still experimental, but we intend to make it suitable for production.
とある通り、まだ絶賛開発中です。プロダクションで使うのは控えた方が良さそうです。
何が嬉しいの?
VueのSFC(Single File Components)での開発ならVue CLIで良いのでは?と思うかもしれません。
Viteを使う利点は以下の通りです。
- 開発時はバンドル不要で動作するので、大規模プロジェクトでも初回起動が非常に早い
- HMR(画面の再描画無しにファイル変更をブラウザに適用してくれる機能)が、モジュールの総数と切り離されているため一貫して高速に動作する
ざっくり言うと総じて動作が早いです。開発体験が良い。
他詳細な説明はREADMEにも記載されているのでそちらもどうぞ。https://github.com/vitejs/vite#how-and-why
インストールとプロジェクト作成
VueとReactのプロジェクト作成を簡単に説明します。
Vue
以下コマンドでプロジェクトを作成します。
$ npm init vite-app example-vue
これだけでOKです。example-vueにVueのプロジェクトが作成されます。
あとは依存モジュールをinstallして起動するだけです。$ cd example-vue $ npm i $ npm run devReact
--template react
でReactのテンプレートを指定したうえでプロジェクトを作成します。$ npm init vite-app --template react example-reactexample-reactにReactのプロジェクトが作成されます。
あとはvueと同じく起動するだけです。$ cd example-react $ npm i $ npm run devビルド
ViteではRollup.jsを内部的に使ってリソースをバンドルしプロダクションビルドが可能です。
buildコマンドでビルドが実行されます。$ npm run build
dist
配下に成果物が生成されるので、あとはそれを公開するだけです。細かいビルドオプションを設定したい場合は、コマンド引数で指定するか、設定ファイルを作り記述できます。
以下成果物のディレクトリをout
ディレクトリに変更する例です。プロジェクトルートに
vite.config.js
を追加して以下を記述します。vite.config.jsmodule.exports = { outDir: "out" }あとはそのままビルドすれば
out
ディレクトリに成果物が生成されます。他設定可能なオプションはソースコードをみるのが良さそうです。
https://github.com/vitejs/vite/blob/master/src/node/config.tsTypeScriptへの対応
TypeScriptへの対応も設定なしで可能です。Vueの場合は
.vue
で<script lang="ts">
を設定するだけ。
Reactの場合は、.jsx
を.tsx
に変更するだけです。
.ts
ファイルもそのままimportできます。App.vue<template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Hello Vue + Vite" /> </template> <script lang="ts"> import HelloWorld from './components/HelloWorld.vue' import { defineComponent } from 'vue' // Composition APIなので defineComponent()を利用 export default defineComponent({ name: 'App', components: { HelloWorld }, setup() { return {} } }) </script>注意点
2020/05/31現在、TypeScriptはJSへのトランスパイルのみ対応していて、型チェックは行えません。
基本的にエディタ上での型エラーの確認と、tsconfig.jsonを追加しビルド前にtsc --noEmit
を利用して型チェックを行ってください。PostCSS、Scssへの対応
Viteは、
.vue
ファイルとインポートされた.css
ファイルの全てにPostCSSを自動的に適用します。
必要なPostCSSのプラグインをnpmでインストールして、あとはpostcss.config.js
をプロジェクトルートに追加するだけです。Autoprefixerの利用例。
$ npm i autoprefixer --save-devpostcss.config.jsmodule.exports = { plugins: [ require('autoprefixer')() ] }CSS Pre-Processorsのscssも、
sass
をnpmインストールして、.vue
ファイルで<style lang="scss">
を指定するだけで利用できます。$ npm i sass --save-dev<style lang="scss"> /* scss */ </style>終わりに
とても高速に動作するのでViteかなり良いですね。あと、Vue 3.0のsandbox環境としても最高です。
今回紹介した以外にも機能盛り沢山なので、是非READMEを読んで欲しいです。
https://github.com/vitejs/vite今後もさらに機能が拡張されるようなので引き続き動向を見ていきたいです。
最後に。Evan You氏のVite作成時の投稿がカッコ良すぎる。
こんなこといつか言ってみたいです。As I was going to bed, I had an idea about a no-bundler dev setup (using native browser ES imports), but with support for Vue SFCs with hot reload. Now it's almost 6AM and I have PoC working. The hot reload is so fast it's near instant.
— Evan You (@youyuxi) April 20, 2020参考
- 投稿日:2020-06-01T07:05:46+09:00
[今度こそ完全に理解した]vueで数字しか入力できないinputタグのカスタムコンポーネントを作る
vue.jsで「数字」しか入力できないinput要素を作る
[Vue.js]小数や整数しか入力できないinputタグのカスタムコンポーネント過去に二度、同じことを試行錯誤して記事を書きました
いずれも動作としては問題ないのですが今読み返すと「これってどうなの?」というポイントがあり
改めて考えて今度こそちゃんとした(つもりの)コードを書いてみました結論
IntegerStringOnlyInput.vue<template> <input type="text" :value="value" @input="sanitizeAndEmit($event.target.value)" > </template> <script> export default { name: 'IntegerStringOnlyInput', props: { value: { type: String, required: true, default: '', }, }, watch: { // 親からの値変更の時もサニタイズするためにwatch value(newValue) { this.sanitizeAndEmit(newValue); }, }, mounted() { this.sanitizeAndEmit(this.value); }, methods: { sanitizeAndEmit(val) { // 数字以外を消す & 全角数字を半角数字へ変換 this.$emit( 'input', val.replace(/[^0-90-9]/g, '').replace(/[0-9]/g, (s) => String.fromCharCode(s.charCodeAt(0) - 0xFEE0)), ); // サニタイズされた値は直前の値と変わらないので再描画されない 強制的に再描画しないと、表示と実際の値がずれる this.$forceUpdate(); }, }, }; </script>使い方としては普通のカスタムコンポーネントと同じで
importしてv-model
で変数をバインドするだけです<IntegerStringOnlyInput v-model="int" />過去の記事のコードがなぜよくなかったのか
vue.jsで「数字」しか入力できないinput要素を作る
v-model
と@input
が同時に存在しているからその記事では
<input @input="validate" v-model="numValue" >というように書いていました
幸か不幸かこのコードは意図した通りの挙動をしてくれます(通常のv-modelの挙動のあとに値を上書きする)ではなぜ
v-model
と@input
が同時に存在するコードがだめなのかそもそも
v-model
はシュガーシンタックス(※1)なので
つまるところ上記のコードは<input @input="numValue = $event.target.value" @input="validate" :value="numValue" >と同じであると解釈できるのでは、と思いました
(@input
の重複はパースエラーになるので実際にはこのように書くことはできませんが。。。)そのため、たまたま動くだけで
v-model
と@input
が共存することは本来は意図されてない使われ方ではないかと考えています
- もしそうならいつかバージョンが上がった時に動かなくなる可能性があるのではないか
- そうでなくともコードとして同じイベントハンドラが2つあるという状況もあまりよいものではない
と思い、「これはよくない」という結論にいたりました
[Vue.js]小数や整数しか入力できないinputタグのカスタムコンポーネント
$refs
を使っているから値が変更されず再描画されないために実際の変数の中身と見た目がずれる問題を解消するために
当時は「再描画されないからずれる」ことに気づかず解決しようと$refs
を使ってしまいましたが
そもそも$refs
は使用が推奨されておらず(※2)、使わずに済むならそれに越したことはないため
今回気づいたずれの原因から再描画が解決の糸口なので$forceUpdate()
の方がいいのではという結論に至りました。単純にコードが冗長
computed
を使ったために無駄にコード量が増え可読性が低下していました
改めて考えてみるとcomputed挟む必要はありませんでした親コンポーネントからの変更にサニタイズがされない
親コンポーネントから数字以外のものが含まれる値に変更されてもその際には数字以外の文字が取り除かれないため
今回新たにwatch
で変更を監視してサニタイズされるようにしました※1
※2
参考ドキュメント
- 特別な問題に対処する好みや要件次第で
全角許容する場合は半角への変換をなくしたり
親からの変更時やmount時にサニタイズがされたくない場合はwatch
やmounted
を消したりでコントロール可能です
- 投稿日:2020-06-01T00:34:10+09:00
Laravel + Vue.jsのかんたん実装
laravel上でVuejsを実装は、大まかに3段階です。
- resources/js/components にvueデータを作成。
- resources/js/app.jsにvueコンポーネントを定義。
- app.jsに定義したvueコンポーネントを resources/views/*.blade.phpにマークアップ。
ExampleComponent.vueを表示してみましょう!
app.jsを以下のように設定します。resources/js/app.jsrequire('./bootstrap'); window.Vue = require('vue'); Vue.component('example-component', require('./components/ExampleComponent.vue').default); const app = new Vue({ el: '#app', });デフォルトのwelcome.blade.phpに記述します。
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ExampleComponent</title> <link rel="stylesheet" href="{{ asset('css/app.css') }}"> </head> <body> <div id="app"> <example-component></example-component> </div> <script src="{{ asset('js/app.js') }}"></script> </body> </html>vueファイルを記述、変更したらターミナルでコンパイルをします。
#コンパイル npm run dev #サーバーを立ち上げて確認 php artisan serve新しくVueを作成してみましょう。
1. vueファイルを作成します。
resources/js/components/LaravelVue.vue<template> <div> <h1>LaravelでVueを実装</h1> <p>Good!!</p> </div> </template>
- resources/js/app.jsにvueコンポーネントを定義します。
resources/js/app.jsrequire('./bootstrap'); window.Vue = require('vue'); Vue.component('example-component', require('./components/ExampleComponent.vue').default); Vue.component('laravelvue', require('./components/LaravelVue.vue').default); const app = new Vue({ el: '#app', });
- app.jsに定義したvueコンポーネントを resources/views/welcome.blade.phpにマークアップ。
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>laravelvue</title> <link rel="stylesheet" href="{{ asset('css/app.css') }}"> </head> <body> <div id="app"> <laravelvue></laravelvue> </div> <script src="{{ asset('js/app.js') }}"></script> </body> </html>コンパイルのたびに
npm run dev
するのは、面倒くさいので
vueファイルの記述が変更されるたびに自動でコンパイルしてくれるnpm run watch
を実行します。#コンパイル npm run watch
command + T
で新しくタブを作ってサーバーを立ち上げます。#サーバーを立ち上げて確認 php artisan serveもう一度vueファイルの記述を変更してみましょう。
resources/js/components/LaravelVue.vue<template> <div> <h1>LaravelでVueを実装できた!!</h1> <p>Yeah!!</p> </div> </template>
npm run watch
で監視されているので、vueファイルの記述に変更があると自動的にコンパイルをしてくれます。
ブラウザを更新してみましょう!
更新されています。かんたんでは、ありますが少しでも参考になれば幸いです。
- 投稿日:2020-06-01T00:31:21+09:00
【Vue/Nuxt】JSXでv-modalを使わずにデータを入力
VueでJSXを利用しているのですが、v-modelを使わずにデータを入力出来たのでメモがてら残します。
2種類の入力方法を検証できたため、2つに分けて書いていこうと思います。
v-modelを使わずにdataを直接書き換える
<script> export default { render(h) { data = { email: '' } return ( {h('input', { type: 'email', name: 'email', domProps: { value: this.email, }, on: { input: (e) => { this.email = e.target.value }, }, })} ) } } </script>以下のドキュメントを参考にしました。
https://www.digitalocean.com/community/tutorials/vuejs-introduction-render-functions
https://jp.vuejs.org/v2/guide/render-function.html#v-model入力用のメソッドを用意してあげる
ただ、上の書き方だと親コンポーネントから受け取ったpropsを直接書き換えてしまう実装のため、入力用のメソッドを用意してみました。
ContainerとPresenterでロジックとUIを分けているため、その書き方でサンプルコードを載せようと思います。
以下の記事を参考にしました。
https://github.com/vuejs/babel-plugin-transform-vue-jsx/issues/36
Container
container.jsimport Presenter from './presenter' const connect = (Presenter) => { return { name: `${Presenter.name}Container`, methods: { handleInput(name, value) { this[name] = value }, }, render(h) { const data = { email: '', password: '', } return h(Presenter, { props: { ...data, handleInput: this.handleInput, }, }) }, } } export default connect(SignUpBody)Presenter
presenter.vue<script> export default { name: 'Presenter', props: { handleInput: { type: Function, default: () => {}, }, email: { type: String, default: '', }, password: { type: String, default: '', }, }, render(h) { return ( <div> <form> {h('input', { type: 'email', name: 'email', domProps: { value: this.email, }, on: { input: (e) => { this.handleInput('email', e.target.value) }, }, })} {h('input', { type: 'password', name: 'password', domProps: { value: this.password, }, on: { input: (e) => { this.handleInput('password', e.target.value) }, }, })} </form> </div> ) } </script>createElementを使いましたが別に以下のような書き方もすることができます。
<input type="email" name="email" value={this.email} onInput={(e) => { this.handleInput('email', e.target.value) }} /> <input type="password" name="password" value={this.password} onInput={(e) => { this.handleInput('password', e.target.value) }} />
- 投稿日:2020-06-01T00:22:31+09:00
【Vue/Nuxt】JSXでspreadでいっぺんにpropsを渡す
VueでJSXを利用しているのですが、Reactみたいにpropsをいっぺんに渡せないかと思って検証しました。
実際にpropsを渡せたので、メモがてらサンプルコードを残します。
ContainerとPresenterでロジック部分とUI部分を分けているのですが、その書き方でそのまま共有させてください。
Container
container.jsimport Presenter from './presenter' const connect = (Presenter) => { return { name: `${Presenter.name}Container`, render(h) { const data = { email: '', password: '', } return h(Presenter, { props: { ...data, }, }) }, } } export default connect(Presenter)Presenter
presenter.vue<script> export default { name: 'Presenter', props: { email: { type: String, default: '', }, password: { type: String, default: '', }, }, render() { return ( <div> {this.email} {this.password} </div> ) }, } </script>
- 投稿日:2020-06-01T00:20:55+09:00
【Vue/最高にかっこいい】hoverすると画像全体がスーッと現れるCSSアニメーション実装
Vueバージョン確認
npm list vueまずは上記コマンドでバージョンの確認
twinzlabo@0.1.0 /Users/twinzlabo ── vue@2.6.11画像一覧をhoverするとスーッと拡大されるアニメーションをコピペだけで実装
すでに上の方で確認してもらったかと思いますが、
特に変哲もない画像コンポーネントにスタイル修正を行うことで
画像をhoverするとスーッと画像の全体像が現れるクールなアニメーション実装をしていきましょう
デフォルトの上の画像をhoverしたら下の画像のように画像の全体像が現れるアニメーションをカスタマイズしていきます
デフォルト時は拡大して見えてるけどhoverするとその全体像が現れる
この感じかなりクールですよね
実装してみたら感動すること間違いなしです!
では早速コードをコピペしていきましょう
<template> <div class="images"> <div class="image"> <img src="https://images.unsplash.com/photo-1551607117-21fa129a211d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1720&q=80"> <span>Hover this Image</span> </div> </div> </template><style> .images { display: flex; width: 100%; padding: 4% 2%; box-sizing: border-box; height: 60vh; } .image { flex: 1; overflow: hidden; transition: .5s; margin: 0 2%; box-shadow: 0 20px 30px rgba(0,0,0,.1); line-height: 0; } .image > img { width: 200%; height: calc(100% - 10vh); object-fit: cover; transition: .5s; } .image > span { font-size: 3.8vh; display: block; text-align: center; height: 10vh; line-height: 2.6; } .image:hover { flex: 1 1 50%; } .image:hover > img { width: 100%; height: 100%; } </style>画像をhoverするとスーッと画像の全体像が現れるクールなアニメーションが実装できましたか?
こういうの実装できるとめっちゃ興奮しますよね
下の記事では別の応用的で面白い画像のデザイン方法を掲載しているので是非挑戦してみてください
以上です
参考記事(この実装ができるとめっちゃ興奮しますよ)
【Vue/CSSアニメーション】画像一覧をhoverするとスーッと拡大されるアニメーションをコピペだけで実装