- 投稿日:2020-02-14T23:47:52+09:00
vueで出会う「$」について
vue.jsを扱っていると、時々「$」に出会います。
例えばdb.collection("twits").doc(this.$props.id),これは何なのかわからなかったので調べました。
https://tutorialmore.com/questions-266496.htm
その他でも説明されていますが、これはvueが元々持っているネイティブのプロパティを使う場合に必要となるものです。
props
というプロパティは元々vueに備わっているものなので、$
が必要ということです。ちなみに
main.js
にある$mount
は、elプロパティと同じことを行うことができるメソッドです。
$mount
は引数を取り、テンプレートをマウントしたい要素を決めることが可能です。また一つ学びました。
- 投稿日:2020-02-14T23:36:59+09:00
vue-routerで親子間でのイベント処理
概要
vue-router使用時の「子→親」あるいは「子1→子2」のイベント渡しの記述方法について自分用メモ
ソースコードは簡略化したもので、ちゃんとテストしていないので動かないかも
サンプルコード
this.$router.app.$on
を扱うのがポイント。それ以外は通常のイベントバスのやり方と同じ。主要な部分
app.js//(前略) const app = new Vue({ router, // これが「ルーターインスタンスを root インスタンスに router オプションとして」かな? el: '#app', render: h => h(App) });親コンポーネント
parent.vue<template> <div> <child1></child1> <child2></child2> </div> </template> <script> // 子2つを配置するのみ import component_child1 from './child1.vue' import component_child2 from './child2.vue' export default { components: { 'child1': component_child1, 'child2': component_child2 } }; </script>子コンポーネント(2つ)
目標とする動作は下記。
- child1でボタンを押すと
test_method_child1
が実行される。test_method_child1
内でtest_event_global
がスローされ、child1は非表示。- child2でイベントの解除(重要、後述。参考資料を参照。)
- child2ではイベントを
test_event_global
をtest_event_child2
としておく。- child2でイベントを受け取ったら、
v-on:test_event_child2
でtest_method_child2
が実行。- child2が表示され、Hello Worldが見える。
child1.vue<template> <div v-show="visible_child1"> <button v-on:click="test_method_child1">Run Test</button> </div> </template> <script> export default { data() { return { visible_child1 : true // 表示/非表示制御 }; }, methods: { test_method_child1() { this.visible_child1 = false; // イベントバスを this.$router.app として、イベントをスロー this.$router.app.$emit('test_event_global'); } } } </script>child2.vue<template> <div v-on:test_event_child2="test_method_child2" v-show="visible_child2"> Hello world !! </div> </template> <script> export default { data() { return { visible_child2 : false // 表示/非表示制御 }; }, mounted() { // イベントバスを this.$router.app として、コンポーネントのイベントに紐づけている this.$router.app.$off('test_event_global'); this.$router.app.$on('test_event_global', this.test_event_child2); }, methods: { test_method_child2() { this.visible_child2 = true; } } } </script>注意点
解除が行われない、というより .$off をしないと蓄積するようです
ページ遷移してcreatedやmountedが何度も呼ばれると、そのたびに蓄積されてしまうらしい。
( beforeDestroyかdestroyedの中で解除すればいいのかな )
上記は例。$onでメソッドを直接叩くこともできるはず。
表示/非表示みたいなことを実現するだけならv-onは取ってしまってchild2.vue<div v-show="visible_child2"> (中略) this.$router.app.$on('test_event_global', this.test_method_child2);とすることも。
参考資料
- 投稿日:2020-02-14T19:31:11+09:00
ユーザー目線のVueコンポーネント実装
この記事について
Vueを開発・運用してアプリケーションの規模が段々増えてきました。
チームの人員も増やしていく中で慣れていないエンジニアがコンポーネントの実装に関わるというケースが増えてきて、どんなデータ構造やアクション定義を作るのが良いのか悩んでいるケースを見受けるようになりました。
今回は自分がコンポーネントを実装する時に考えていることを簡単に紹介しておきます。
コンポーネントはユーザーに最も近いコード
コンポーネント思考で実装していくと、ユーザーと近い距離に居るコードは各コンポーネント郡になります。言い換えるとコンポーネントとユーザーは密接な関係にあり、お互いの共通認識が合っていることが望ましいということになります。
利用するユーザーの思い浮かべるデータ構造や行動と実装されたコードの変数や関数が同じであるということです。つまりコンポーネントを使うユーザー側の気持ちがわからなければコンポーネントの実装も困難ということです。
サンプルです。
<template> <div> ...何かしらのコンテンツ <button @click="toggleFavorite"> <template v-if="favorite"> いいね!を解除 </template> <template v-else> いいね!する </template> </button> </div> </template> <script lang="ts"> import Vue from 'vue' export default Vue.extend({ data() { return { favorite: false } }, methods: { toggleFavorite() { this.favorite = !this.favorite } } }) </script>何かしらのコンテンツに対していいね!したりするサンプルです。この実装には2つの問題点があります。
- ユーザーにとってはいいね!なのにコード上ではお気に入りという変数名になっている
- いいね!を変更する処理がコード上では同一の関数で定義されている
どちらも動作上は問題ありません。バグもありません。ですが後々の機能追加や変更などで破壊的変更をする必要が出てしまうコードで良いコードとは言えません。何故ならユーザーの行動原理から外れている実装だからです。
ユーザー側の名称でデータ構造を定義
favorite
をいいね!として扱う場合、機能追加で「お気に入りを追加したい」と言われた時にどうでしょうか?favorite2
と定義してやり過ごすことはできますが、今後コードを見た人が混乱してしまうと思います。古くから運用しているサービスになると歴史上データベースやAPIが
favorite
になっているケースがあります。サンプルではバックエンドと連携していませんが、こういったアプリケーションのサービス内の名称とユーザー側の名称が違う問題はそのままコンポーネントの実装にまで影響してしまうケースが多くあります。統一することが理想ですがデータベースごと名称の変更を全て反映するのは規模によっては難しいです。でも差異を無くしておくことはできます。
フロントエンドは比較的変更がやりやすい分類なのでこういったものは先にフロントエンド側で吸収しておくことで、機能追加や改善・インフラ整備の時に役立つことになるでしょう。
ユーザーの行動に沿った関数を定義
toggleFavorite
という関数は効率的です。ですがユーザーの意思とは連動していません。極端な例ですが、例えば「誤動作を避ける為にいいねする時だけ確認のアラートをして欲しい」といったユーザーの要望を反映する時にどうでしょうか?両方の機能を内包している為、片方のアクションに対して何か付加価値を付け足す時に拡張しにくいと感じませんか?
ユーザーにとって「いいね!する」という行動と「いいね!を解除する」という行動は全く別の意思から行われるアクションです。こういったユーザーの行動ごとに特殊な処理を挟むケースはフロントエンドではとても多いです。経験上Vuexでステート管理すると更に複雑化していきます。
「関数は最低限の方が...」「HTML上では同じ場所にあるボタンなのに...」と思うこともあるかもしれません。ですが、あくまでアプリケーションは利用するユーザーの為に動くべきであり、機能追加や変更もユーザーの行動結果から発生していくものです。
修正後のコードです。
<template> <div> ...何かしらのコンテンツ <button v-if="like" @click="unlikeContent"> いいね!を解除 </button> <button v-else @click="likeContent"> いいね! </button> </div> </template> <script lang="ts"> import Vue from 'vue' export default Vue.extend({ data() { return { like: false } }, methods: { likeContent() { this.like = true }, unlikeContent() { this.like = false } } }) </script>コンポーネントの実装 = ユーザーの意思
この考え方はテストケースも作成しやすい利点があります。
toggleFavorite
は現実のユーザーの行動と反しています。likePost
やunlikePost
を実行してデータ構造をチェックする方がよりユーザーの行動に近いテストケースになると思います。同じようにバグの発生源を特定する上でも有効です。問い合わせなどのユーザーの行った行動がそのまま再現手順に近くなり、関連するデータやメソッドに対してデバッグを行えば見つかりやすくなります。
実装上ではとても小さなことですが、こういった考えが規模の大きくなったアプリケーションにおいて効果的になっていきます。まずはユーザーの気持ちになって脳内でコンポーネントを操作するイメージを掴むことが大事です。
- 投稿日:2020-02-14T18:08:12+09:00
【Nuxt.js】Vuex基礎編:コードをスッキリさせよう
前置き
Happy Valentine❤️?
ということで(?)本日はVuex!
3つに分けて書きます✍️
・vuexとは何か
・メリットは
・簡単な使い方公開予定日を過ぎ、
申し訳ございません?♀️
自社開発アプリを進めておりました?Vuexとは
状態管理のライブラリです。
って言ってもイメージ沸かないので…簡単に言うとデータ保存ができるもの
・ログイン情報が
ページ遷移後も保持されたり、
・APIのデータ引っ張ってきて保存とか、
・methodsに何度も同じ処理書かずに済む
とかとか。
$emitも何回も書かなくていいし楽!
とにかく楽!!!そんな感じです。状態保持をするのでstoreに書きます✍️
メリット
上記の内容がそのままメリット
コードもまとまって楽ちんなのは
このあとの未使用・使用の比較
を見ていただければ分かります?使い方
とにかくまずインストール
ターミナルnpm install vuex --savehttps://vuex.vuejs.org/ja/installation.html
使い方(超簡単ver.)
まずは分かりやすく
components使わず
pagesだけで完結させます。store/index.jsexport const state = () => ({ counter: 0 }) export const mutations = { increase(state) { state.counter++ }, decrease(state) { state.counter-- } }index.vue<template> <div class="container"> <button @click="increase">increase</button> <button @click="decrease">decrease</button> {{ $store.state.counter }} </div> </template> <script> export default { methods: { increase () { this.$store.commit('increase') }, decrease () { this.$store.commit('decrease') }, }, } </script>??
今度はvuexを使用しない場合
それぞれの対応箇所が分かりますね。
・dataがstate
・methodsがmutationsindex.vue<template> <div class="container"> <button @click="increase">increase</button> <button @click="decrease">decrease</button> {{ counter }} </div> </template> <script> export default { data () { return { counter: 0, } }, methods: { increase () { this.counter++; }, decrease () { this.counter--; }, }, } </script>使い方(Components使用ver.)
作るものは全く同じです。
コンポーネント化するだけです!?Point
・index.jsはバグるので新しいjsファイルを作成。
・そのため呼び出し方が変わります。
・gettersでstateの状態を取得する必要あり。counter.jsexport const state = () => ({ counter: 0 }) export const mutations = { increase(state) { state.counter++ }, decrease(state) { state.counter-- } } export const getters = { counter: state => { return state.counter } }Counter.vue(子コンポーネント)
ここで呼び出し方が
'ファイル名/定義した物' に変わります?Counter.vue<template> <div class="container"> <button @click="$store.commit('counter/increase')">increase</button> <button @click="$store.commit('counter/decrease')">decrease</button> {{ counter }} </div> </template> <script> export default { computed: { counter () { return this.$store.getters['counter/counter'] }, }, } </script>index.vue(親ページ)
親には何も書かず超スッキリ??index.vue<template> <div class="container"> <Counter /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, } </script>??
vuexを使わないと
$emitとか親でどう処理するかとか
いちいち分けたり何度も書く必要が出てきます。Counter.vue(子コンポーネント)<template> <div class="container"> <button @click="$emit('increase')">increase</button> <button @click="$emit('decrease')">decrease</button> {{ counter }} </div> </template> <script> export default { props: { counter: Number }, } </script>index.vue(親ページ)<template> <div class="container"> <Counter :counter="counter" @increase="increase" @decrease="decrease" /> </div> </template> <script> import Counter from '~/components/Counter.vue'; export default { components: { Counter, }, data () { return { counter: 0 } }, methods: { increase () { this.counter++; }, decrease () { this.counter--; } }, } </script>記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?
https://twitter.com/aLizlab
- 投稿日:2020-02-14T15:19:08+09:00
Vue v2.6.11のバグを発見
Vueのバグを発見しました。
PR: https://github.com/vuejs/vue/pull/11107
事象
async componentでv-if="false"を使うと、vueのvdom処理でエラーになる。
詳細はPRのテストコード参照。
原因
vdomでは、async componentはasyncPlaceholder(コメントノード + 補助データ)として扱われる。
v-if="false"はコメントノードとして扱われる (参考: https://github.com/vuejs/vue/issues/5117 )。patch.jsのsameVNodeでは、async componentとコメントノードが同じと判定されてしまう(これが誤り)。
sameVNodeで同じと判定されると、patchVNode処理に入り、その中のasyncFactory.resolvedで、asyncFactoryがundefinedでエラーになる。
修正
sameVNodeでasync componentとコメントノードが同じと判定されないようにする。
メモ
data-server-renderedはルートにのみつくらしい。
hydrationするときに取り除くので、hydrationは一回のみ実行される。
hydrationのときにロードできないasync componentはasyncPlaceholderにされる(つまり、async component内部はhydrationの対象外になる)。コードを見た感じ、hydrationのときにすでにresolveされているasync componentは通常のコンポーネントとほぼ同様に処理されるみたい。
感想
nuxtでパフォチューをやっているときに遭遇した。
セキュリティ懸念(誤ってキャッシュされるとか、リクエスト間で情報が漏れるとか)から、SSRでは認証が必要なデータは取得しないようにしている。
非ログインユーザーはSSRで完結するのでクライアント側でデータ取得しなくて良い。
ログインユーザーはクライアント側でデータ取得するが、今まではmount後にリクエストを開始していて遅かったので、mount前にリクエストを開始するようにした。
それでこのバグが発生。最初、hydrationが行われる前にリクエストが返ってきて、SSRとクライアントのdom不整合かなと思ったが、データ更新を強制的にmount後や、mount後のnextTick後にしても発生する。
vue内部のresolvedでエラーになっていることがわかったので、vueのコードを検索したらpatch.jsを発見。
async componentがresolveされる前に、v-if="false"でvdom更新されるとエラーになることを発見。
結局、パフォチューで速くなったせいで、async componentのロードとデータ更新のタイミングが逆転してバグが発生したみたい。
自分のコードが原因だと思って1日くらいかかった。
これでvueのコントリビューターの肩書を得た。
- 投稿日:2020-02-14T15:19:08+09:00
Vue v2.6.11のバグを発見 (勘違いの可能性もあり)
Vueのバグを発見しました。 (勘違いの可能性もあり)
PR: https://github.com/vuejs/vue/pull/11107
事象
async componentでv-if="false"を使うと、vueのvdom処理でエラーになる。
詳細はPRのテストコード参照。
原因
vdomでは、async componentはasyncPlaceholder(コメントノード + 補助データ)として扱われる。
v-if="false"はコメントノードとして扱われる (参考: https://github.com/vuejs/vue/issues/5117)。patch.jsのsameVNodeでは、async componentとコメントノードが同じと判定されてしまう(これが誤り)。
sameVNodeで同じと判定されると、patchVNode処理に入り、その中のasyncFactory.resolvedで、asyncFactoryがundefinedでエラーになる。
修正
sameVNodeでasync componentとコメントノードが同じと判定されないようにする。
感想
nuxtでパフォチューをやっているときに遭遇した。
セキュリティ懸念(誤ってキャッシュされるとか、リクエスト間で情報が漏れるとか)から、SSRでは認証が必要なデータは取得しないようにしている。
非ログインユーザーはSSRで完結するのでクライアント側でデータ取得しなくて良い。
ログインユーザーはクライアント側でデータ取得するが、今まではmount後にリクエストを開始していて遅かったので、mount前にリクエストを開始するようにした。
それでこのバグが発生。最初、hydrationが行われる前にリクエストが返ってきて、SSRとクライアントのdom不整合かなと思ったが、データ更新を強制的にmount後や、mount後のnextTick後にしても発生する。
vue内部のresolvedでエラーになっていることがわかったので、vueのコードを検索したらpatch.jsを発見。
async componentがresolveされる前に、v-if="false"でvdom更新されるとエラーになることを発見。
結局、パフォチューで速くなったせいで、async componentのロードとデータ更新のタイミングが逆転してバグが発生したみたい。
自分のコードが原因だと思って1日くらいかかった。
これでvueのコントリビューターの肩書を得た。
- 投稿日:2020-02-14T14:12:22+09:00
Nuxt.js - Firebaseへのデプロイ & 独自ドメインの設定
Nuxt.jsで作成したSPAアプリケーションをFirebaseにデプロイする方法をいつも忘れるので、メモしておきます。
プロジェクトの追加
Firebaseへ行き、Googleアカウントでログイン後、コンソールに移動します。
"プロジェクトを追加"から、プロジェクトの追加を行います。
・Googleアナリティクスの設定
ここでは、とりあえず無効にしておきます。
有効にすると、アナリティクスアカウントの設定が必要です。
これで完了です。
firebase-tools
次に、firebase-toolsのインストールを行います。
$ npm install -g firebase-toolsインストール後、ログインします。
ブラウザが開くので、GoogleアカウントでログインすればOKです ;)
$ firebase loginデプロイしたいプロジェクトのディレクトリに移動し、init
$ firebase initいくつかの質問があります。
今回はHostingを利用するので、スペースで選択し、Enter。
次に、"Use an existing project" を選択します。
冒頭の「プロジェクトを追加」で追加した、プロジェクト名を選択します。
"dist"と入力。
SPAとして設定するかを聞かれます。
私は、SPAで作成しているので、yを入力します。
これで設定は完了です!
Deploy
$ npm run generate $ firebase deploy完了すると、最後にURLが表示されるので、アクセスしてみます。
無事、Firebaseにデプロイすることができました :)
コードを追加したり、修正した場合には、再び以下のコマンドを打てばOKです ;)
$ npm run generate $ firebase deploy独自ドメインの設定
Firebaseのコンソールページに行き、独自ドメインを設定したいプロジェクトを選択します。
次に、左サイドバーのHostingを選択。
"カスタムドメインを追加"をクリック。
あとは、お名前.comなどで取得したドメインを入力し、従えばOKです ;)
反映するのに、少し時間がかかると思います。
説明不足だと感じた点や、変更点が出てきましたら、随時更新していきたいと思います。
この記事が参考になりましたら幸いです。
Thanks,
@ShogoMurakami
- 投稿日:2020-02-14T13:34:17+09:00
ハッシュタグで検索するWEBデザイン検索サービス「Lplove」をリリースしました
初めて投稿します。普段はQiitaをよく見ていて、つまづいた時にとても助かっています。ありがとうございます。
先ほどハッシュタグで検索するWEBデザイン検索サービス「LPlove」をリリースしました。リンクはこちらです(https://lp-love.com/)
昨年11月から本格的に作り始めて、ほぼ3ヶ月で作りました。メインはFirebaseとNuxt。SSRで作っています。ドメインがroute53なのを除いて、残りは全部FIrebaseでできています。
仕事の合間を縫って一人で作りましたが、正直大変でした。たまに一人で全部作る方を見かけますが、こんなに大変なのかと思い知らされました。
このサービスは、普段デザインの仕事をしていて「デザインまとめサイトじゃ探すのが大変!」「かといってPinterestじゃ誰がこのデザインを良いと思っているのか分かりづらい」「もっとデザイン共有できないかな」と思って、ピンタレストとインスタの間を目指して作りました。
これから大きくしていく中で、デザイナーさんが探しやすい、共有しやすいサービスになれば良いと思います。
とりあえず、リリース直後なので不具合がたくさん出るだろうな、と不安でいっぱいですが、不具合見つけたらコメントでどんどん言ってもらえると本当に助かります!またデザイナーさんも「こんな機能が欲しい」などあったらどんどんください。めちゃくちゃ励みになります!
またちょっと休んだらこのサービスの作った過程やソースなどを随時公開していけたらと思います。
とりあえず、
Lplove(https://lp-love.com/)
をよろしくお願いします!
- 投稿日:2020-02-14T12:29:59+09:00
Vue/Nuxtで`is-active`みたいなclassを条件によって追加しようとしたら'v-bind' directives require an attribute value.と出る件
小ネタ。特に
- NuxtでESLintを有効にし
- CSSフレームワークをBulmaにして(
is-active
とかのclass名をよく使うため)- ハイフン付きの
is-active
とかのclassを 条件によって追加しようとすると遭遇しやすいです。
require an attribute value.
って言われてもわかりづらいよ!Vue/Nuxtで条件によってHTMLのclassを動的に変更したい場合、 Vue.jsのドキュメント で紹介されていますが、
「HTMLクラスのバインディング」の例<div v-bind:class="{ active: isActive }"></div>そのとおりに
is-active
などのclassを追加しようとする次のエラーが発生します。'v-bind' directives require an attribute value.
v-bindで失敗する例<template> <div :class="{ is-active: isDropdownShown }" class="dropdown" > ... </div> </template>「attribute valueを設定したはずなのにどうして…?」と考えてしまうとハマり始めます。
解決法: 追加しようとするクラス名をシングルクォーテーションで囲む
これだけです。class名に
-
が含まれている場合、そのままだとattribute valueとして認識されません。追加しようとするクラス名をシングルクォーテーションで囲む<template> <div :class="{ 'is-active': isDropdownShown }" class="dropdown" > ... </div> </template>JavaScriptの識別子…Objectのkey…と考えると気づきやすいですね!
- 投稿日:2020-02-14T11:25:01+09:00
【JavaScript】Vue.js(Element UI)を使用している際に「async-validator」単体で使う場合のカスタムバリデーション実装方法
概要
Vue.js(Element (Element UI))を使用している場合に「async-validator」単体で使う実装方法例を少々
インストール(npm)
npm i async-validator※筆者はElement (Element UI)(Vue.jsのコンポーネントライブラリ)を使用しているので別途インストールしなくても使える状況ですので単体でインストールしたことはないです
実装例
簡単なアカウント登録画面の例として以下のように実装してみました
form.vue<template> <div class="form"> <div class="text-center">アカウント登録</div> <input type="email" v-model="ruleForm.email" placeholder="メールアドレス*"> <input type="password" v-model="ruleForm.password" placeholder="パスワード*"> <input type="password" v-model="ruleForm.passwordConf" placeholder="パスワード(確認)*"> <div class="text-center"> <input type="button" @click="beforeSubmit" value="登録"> </div> </div> </template> <script> import schema from 'async-validator'; export default { data() { // カスタムバリデーション定義する場合はここに記述 var validatePasswordConf = (rule, value, callback) => { if (value === '') { callback(new Error('パスワード(確認)を入力してください。')); } else if (value !== this.ruleForm.password) { callback(new Error('二つのパスワードが異なります。')); } else { callback(); } }; return { // フォーム用変数 ruleForm: { email: '', password: '', passwordConf: '', }, // バリデーションルール定義(項目毎) rules: { email: [ { required: true, message: 'メールアドレスを入力してください。', trigger: 'blur' }, { type: "email", message: '正しいメールアドレスを入力してください。', trigger: 'blur' }, { min: 1, max: 255, message: '1文字以上255文字以内で入力してください。', trigger: 'blur' } ], password: [ { required: true, message: 'パスワードを入力してください。', trigger: 'blur' }, { min: 1, max: 50, message: '1文字以上50文字以内で入力してください。', trigger: 'blur' } ], passwordConf: [ // 上記で定義したカスタムバリデーションを使用 { validator: validatePasswordConf, trigger: 'blur' } ], }, } }, methods: { beforeSubmit() { var validator = new schema(this.rules); validator.validate(this.ruleForm, (errors, fields) => { if(errors) { // バリデーションエラー時 return alert(errors[0]["message"]); } // バリデーションパス return alert("登録しました"); }); }, } } </script>バリデーションのルール詳細は「async-validator」のREADMEとかを参考にしてください。
参考URL
- 投稿日:2020-02-14T10:19:09+09:00
Dark Sky APIで値を取ってきたらエラーがでる
問題
Vue.jsとFirebaseを使ってお天気アプリ作成中にDark Sky APIから値を取ってこようとしたら下記エラーがでた
解決
urlの前に
https://cors-anywhere.herokuapp.com
をつけるweather-mixin.jsgetTemperature() { this.$axios .get( "https://cors-anywhere.herokuapp.com/https://api.darksky.net/forecast/APIキー/37.8267,-122.4233", {} ) .then(result => { console.log("結果", result); this.temperature = Temperature.create(result); console.log("temp", this.temperature); }) .catch(error => { console.log(error); }); }
- 投稿日:2020-02-14T02:11:38+09:00
[JavaScript] スプレッド構文
See the Pen リストにリストの追加 by mykysyk (@mykysyk) on CodePen.