- 投稿日:2021-01-11T22:46:23+09:00
【速習】vue-property-decorator
これを書いた趣旨
実務でvue-property-decoratorを使ってVueをTypeScriptで記述する必要があり、ざっと理解した内容を個人用メモとしてまとめました。間違っている点があれば、ご指摘頂けますと幸いです。
vue-property-decoratorの前に、Vue Class Componentから
Vue Property DecoratorのREADMEを読むと、最初にこう書かれています。
This library fully depends on vue-class-component, so please read its README before using this library.(このライブラリは vue-class-component に完全に依存しています。)
つまり、vue-property-decoratorを使う前にvue-class-componentが使える状態にしておけよ、ということなので、公式VueからVue Class Componentを確認します。
Vue Class Componentとは
- Vue Class Componentは、Vueのコンポーネントをクラススタイルの構文で作成できるライブラリ
@Componentデコレータ
でクラスにアノテーション(@
)を付けることで、Vueを継承したクラスとしてコンポーネントのデータやメソッドを定義することができる- 基本的な書き方
- data:クラスの変数として書く
- 算出プロパティ:getterとして書く
- メソッド:普通にメソッドとして書く
- ライフサイクルフック:名前を合わせてメソッドとして書く
例:シンプルなカウンタコンポーネント
main.vue<template> <div> <button v-on:click="decrement">-</button> {{ count }} <button v-on:click="increment">+</button> </div> </template> <script> import Vue from 'vue' import Component from 'vue-class-component' // import文でvue-class-componentを呼び出す // Class定義と同じ構文でVueコンポーネントを定義できる @Component export default class Counter extends Vue { // Counterというクラス名でVueコンポーネントを出力する(定義する) // Classプロパティは、コンポーネントのデータになる count = 0 // メソッドはコンポーネントメソッドになる // 2つのメソッドをCounter Class内で定義 increment() { this.count++ } decrement() { this.count-- } } </script>Vue Property Decoratorが提供するデコレータの他に、
@Prop
と@Watch
などもあるので、簡単に紹介。代表的なデコレータの紹介
@Prop:親コンポーネントからデータを受け取るpropsを指定
@Prop(options: (PropOptions | Constructor[] | Constructor) = {}) // Propの引数が、プロパティのオプションか、コンストラクター関数か、コンストラクター関数の配列の値がオブジェクトであるPropsの復習
- 型:
Array<string> | Object
例:[ "childComponent" ]
- propsとは、親コンポーネントからデータを受け取るためにエクスポートされた属性のリスト/ハッシュである
Propsのオプション(
PropOptions
)は、以下が使える
type
:String
,Number
,Boolean
,Array
,Object
,Date
,Function
,Symbol
,カスタムコンストラクタ関数
、またはそれらの配列
が使える
- プロパティは、以下のように、(文字列の配列だけでなく)オブジェクトとして列挙できる
// キーと値には、それぞれプロパティ名と型を設定します props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
default
:any
- プロパティのデフォルト値を指定 - プロパティが渡されない場合は、この値が代わりに使われる - オブジェクトまたは配列のデフォルト値は、ファクトリ関数で返す必要ありrequired
:Boolean
- プロパティが必須かどうかを指定
- 本番環境以外では、この値が真なのにプロパティが渡されないとコンソールに警告が出される
validator
:Function
- プロパティの値を唯一の引数として受け取る、カスタムのバリデーション関数
- 本番環境以外では、この関数が偽を返す (つまり、バリデーションが失敗する) とコンソールに警告が出される
コードの例
<script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; @Component export default class SampleComponent extends Vue { @Prop({ type: String, required: true }) userName: string; @Prop({ type: Boolean, defualt: false }) isVisible: boolean; } </script>は、以下と同義
<script> export default { // プロパティは、オブジェクトとして表現できる // ①userNameがキーで、{ type: String, required: true }が値となっているオブジェクトと、 // ②isVisibleがキーで、{ type: Boolean, default: false }が値となっているオブジェクトのプロパティを持つクラスである props: { userName: { type: String, required: true }, isVisible: { type: Boolean, default: false } } }; </script>@Emit:子コンポーネントから親コンポーネントにデータを渡す$emitを指定
$emit
によって装飾された関数が、その戻り値の後に元の引数が続く- 戻り値がPromiseの値である場合は、emitされる前に解決される
- イベントの名前がイベント引数で与えられていない場合は、代わりに関数名が使用されるが、その場合、キャメルケース名はケバブケースに変換されるのに注意
@Emit(event?: string)Emitの復習
$emit
:子コンポーネントから親コンポーネントにデータを渡すために利用する仕組み(カスタムイベント)で、親コンポーネントに対して*なんらかの変化が起こったこと(イベント)を通知(発火)するもの。その際に関連するデータ(オブジェクト、アクション、値など)を添付可能- 構文:
this.$emit(event [...args])
- event:イベント名(String型)、[...args]:親コンポーネントに引き渡すデータ
- 例:
this.$emit("任意のイベント名", 渡すデータ);
で値を渡し、this.$emit("任意のイベント名")
でイベント発火のみ- 例2:複数の値を渡すなら、
this.$emit( '任意のイベント名', { key1: value1, key2: value2} );
のオブジェクトで渡すコードの例
import { Vue, Component, Emit } from 'vue-property-decorator' @Component export default class YourComponent extends Vue { count = 0 @Emit() addToCount(n: number) { // キャメルケースからケバブケースに変わります this.count += n } @Emit('reset') // イベント発火のみ resetCount() { this.count = 0 } @Emit() returnValue() { // 値そのものを返す場合 return 10 } @Emit() onInputChange(e) { return e.target.value } @Emit() promise() { //Promiseオブジェクトを戻り値として返す場合、emitされる前に解決されます return new Promise((resolve) => { setTimeout(() => { resolve(20) }, 0) }) } }は、以下と同義
export default { data() { return { count: 0, } }, methods: { addToCount(n) { this.count += n this.$emit('add-to-count', n) }, resetCount() { this.count = 0 this.$emit('reset') }, returnValue() { this.$emit('return-value', 10) }, onInputChange(e) { this.$emit('on-input-change', e.target.value, e) }, promise() { const promise = new Promise((resolve) => { setTimeout(() => { resolve(20) }, 0) }) promise.then((value) => { this.$emit('promise', value) }) }, }, }@Watchプロパティを使う
- dataの変更を検知するためのメソッドを定義します。
- optionにwatchオプションのimmediateやdeepもオブジェクトとして渡すことで指定できます。
@Watch(path: string, options: WatchOptions = {})watchオプションの復習
- 型:
{ [key: string]: string | Function | Object | Array}
- WatchOptionsは、キーが評価する式、値は、キーに対応するコールバックをもつオブジェクト
- 値はメソッド名の文字列、または追加のオプションが含まれているオブジェクトが取れる
- Vue インスタンスはインスタンス化の際にオブジェクトの各エントリに対して
$watch()
を呼ぶコード例
import { Vue, Component, Watch } from 'vue-property-decorator' @Component export default class YourComponent extends Vue { @Watch('child') onChildChanged(val: string, oldVal: string) {} @Watch('person', { immediate: true, deep: true }) onPersonChanged1(val: Person, oldVal: Person) {} @Watch('person') onPersonChanged2(val: Person, oldVal: Person) {} }は、以下と同義
export default { watch: { child: [ { handler: 'onChildChanged', immediate: false, deep: false, }, ], person: [ { handler: 'onPersonChanged1', immediate: true, deep: true, }, { handler: 'onPersonChanged2', immediate: false, deep: false, }, ], }, methods: { onChildChanged(val, oldVal) {}, onPersonChanged1(val, oldVal) {}, onPersonChanged2(val, oldVal) {}, }, }参考
- 投稿日:2021-01-11T21:29:55+09:00
vuex-module-decorators(Vuex、store)のjestを書く
初めに
自分のメモ用として記載しています。
端折って書いている場所もある為、ご了承ください。vuex-module-decoratorsを使ってstoreの作成をしていた際に
テスト(jest)をどう書けばわからなかった経緯があった為記載します。*以下の記事などでも書いて下さっているのですが、自分の場合は正常に動作しなかった為
https://qiita.com/azukiazusa/items/8a158913c870bc0c8ba9storeの実装について
src配下に以下のように作成したとします。
store ├── index.ts ├── app.ts └── utils ├── accessor.tsindex.tsimport { Store } from 'vuex'; import { initializeStores } from '@/store/utils/accessor'; const initializer = (store: Store<any>) => initializeStores(store); export const plugins = [initializer]; export * from '@/store/utils/accessor';accessor.tsimport { Store } from 'vuex'; import { getModule } from 'vuex-module-decorators'; import AppModule from '@/store/app'; let appModule: AppModule; function initializeStores(store: Store<any>): void { appModule = getModule(AppModule, store); } export { initializeStores, appModule };*型などは適当に埋めています。こんなstoreを作成していると見てください
app.tsimport { Module, Action, VuexModule, Mutation } from 'vuex-module-decorators'; import { $axios } from '@/store/utils/api.ts'; @Module({ name: 'app', stateFactory: true, namespaced: true }) export default class AppModule extends VuexModule { private app: any = []; @Mutation set〜(app: any) { this.app = app; } @Action({ rawError: true }) async fetchApp(type?: string): Promise<any> { const params = { 〜 }; await $axios .$get('〜', { params }) .then((res) => { this.set~(res.item); }); } get appList(): any { if (!this.app) { return []; } return this.app; } }app.tsのテストを書く
yarn build
を行うと.nuxtが作成されます。
そのstore.jsにあるcreateStore
を使ってstoreのAppModuleを利用できるようにしました。app.spec.tsimport { getModule } from 'vuex-module-decorators'; import AppModule from '@/store/app'; import { createStore } from '@/../.nuxt/store'; jest.mock('@/store/utils/api.ts', () => ({ $axios: { $get: jest.fn(() => Promise.resolve({ item: [{ name: '〜' }], total: 10 }) ) } })); // $axiosのmockです。 let AppModule: any; beforeAll(() => { const store = createStore(); AppModule = getModule(AppModule, store); // こちらです。 }); describe('AppModule', () => { test('appList', async () => { await AppModule.fetch(); expect(AppModule.appList).toStrictEqual([ { name: '〜' } ]); }); });最後に
かなり無理やりなテストの書き方だと思うのですが、他に良い方法があれば教えて頂きたいです。
- 投稿日:2021-01-11T17:59:54+09:00
laraval8 + vue.js2.6で、親子(parent-child)component間の通信について
この本を書いたきっかけは?何についての記事?
「これから始める、Vue.js実践入門」(山田祥寛/著、SBクリエイティブ株式会社/発行)のリスト4-14あたりをlaravel8のプロジェクト内でやりたかったので、コードを少し改変してみた。
参考にさせていただいたサイトなど
https://orkhan.dev/2020/04/14/accessing-parent-and-child-component-in-vuejs/
https://laracasts.com/discuss/channels/vue/vuejs-component-within-component-getting-data-from-child-component
https://vegibit.com/how-to-create-a-child-component-in-vuejs/
https://stackoverflow.com/questions/45387307/define-vue-component-in-laravel前置きはこれくらいにして
app.jsimport App3 from './components/MyComponent3.vue' const app3 = new Vue({ el: '#app3', components: { App3 }, });MyComponent3.vue<template> <div> <div> <p>現在値:{{ current }}</p> </div> <mycomponent4 step="1" v-on:plus="onplus"></mycomponent4> <mycomponent4 step="2" v-on:plus="onplus"></mycomponent4> <mycomponent4 step="-1" v-on:plus="onplus"></mycomponent4> </div> </template> <script> import mycomponent4 from './MyComponent4.vue'; export default { components: {mycomponent4}, data: function () { return { current: 0, } }, methods: { onplus: function (e) { this.current += e; }, }, }; </script>MyComponent3.vue<template> <button type="button" v-on:click="onclick"> {{ step }} </button> </template> <script> export default { data: function () { return { current: 0, } }, props: ['step'], methods: { onclick:function() { this.$emit('plus', Number(this.step)); } } }; </script>index.blade.php<!doctype html> <html lang="ja"> <head> <title>Index</title> <link href="{{ mix('css/app.css') }}" rel="stylesheet" type="text/css"> <meta name="csrf-token" content="{{ csrf_token() }}"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> </head> <body> <div id="app3"> <app3> </app3> </div> <script src="{{ mix('js/app.js')}}"> </script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> </body> </html>HelloController.phpclass HelloController extends Controller { public function index() { return view('hello.index', $data); } }※ボタンを押すと、現在値の数字が、ボタンに書かれている数だけ変動します。
ここまで読んでいただき、ありがとうございました。何かの参考になれば幸いです。
- 投稿日:2021-01-11T17:44:28+09:00
FirebaseとVue.jsでSPAのOGPを作ったらSEOが壊滅したので回避した
はじめまして。
マインドツリーを使って自問自答することで考えを深めたり、周囲の人に質問してもらうことで答えのない問いを問いていくサービスQ&Qを作っているあどにゃーです。ショートサマリ
今回は、Vue.js+CloudFunctionsでOGP対応したら
・TwitterやSlackには任意の動的画像を表示することができるようになった(OK)
・Googleクローラは真っ白で何も読み込まなくなった(NG)
ので対策したよという内容です。
前知識
SPA(Single Page Application)のVue.jsでは、各ページのOGP(Open Graph Protocol)を動的に返せない問題があります。理由は、botがjavascriptを解釈しないからです。でも、TwitterやSlackにリンクを貼る時に全部同じOGP画像というのはイケてない。。
この解決策としては、
・SSR(サーバサイドレンダリング) → 開発量が大きく腰が思い
・Pre-rendering → 動的ページがたくさんある場合向いてない
・CloudFunctions → 一番手軽にOGP生成できる
があると思います。簡単なCloudFunctionsで解決したい!
そこで、ゆきさんの『SNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だった』を参考に、CloudFunction+Vue.jsでOGPを生成しました。丁寧な神解説で非常に助かりました。
簡単に処理の流れを書くとこんな感じです。
1. 動的にOGPを生成したいPathでcloud functionsを発動
2. BotはJavaScriptを理解できずfunctionsで生成したhtmlを読み取って終了
3. UserはJavaScriptを理解するのでリダイレクトされた本来のhtmlに飛んで通常のコンポーネントがマウントされて終了Google検索が壊滅...
OGP対策してめでたしめでたしだったのですが、Googleからの検索が一切なくなっている。。おかしいなと思って、varidationサイトで検証してみるとGoogleはなんと真っ白。。。松崎しげるの歯より白い。一方で、Twitterの検索ではOGPも生成されており、metaデータも正しく読み込めています。一体何が起きているのでしょうか。
元TreeのURL
https://qnqtree.com/tree/iLNP7RAY2KpLNlGFiSQE
Googleの検証用URL
https://search.google.com/test/mobile-friendly
Twitterの検証用URL
https://cards-dev.twitter.com/validator何が起こっている?
Vue.jsでCloud Functionsを使ってOGPを動的に生成したのは、BotがJS(javascript)を解釈できないからでした。
一方でGoogleの検索クローラはJSを解釈します。解釈するため、リダイレクト先に飛び、ロードされたVue本来のindex.htmlを読み取りに来ます。
しかし、Vue側でFirestoreからデータを取得してコンポーネントを更新している場合、検索クローラはその処理を待ってくれません。結果的に、クローラはFirestoreからデータを取得する前の真っ白な画面を取得して帰っていきます。。。
流れを書くと下記になります。
1. 動的にOGPを生成したいPathでcloud functionsを発動
2. JSを理解できないBOTはfunctionsで生成したhtmlを読み取って終了(Twitter Bot)
3. JSを理解できるBOTはリダイレクト先まで飛ぶが、firestoreからのデータ取得を待たずに終了(Google Bot)
4. JSを理解できるUserは、firestoreからデータ取得後の更新されたhtmlを取得して終了(Browser)回避策
JSを中途半端に解釈するBOTと解釈しないBOTの存在が混乱の元なので、BOTの登録をして、BOTの場合は明示的にリダイレクトしないように変更します。存在するBOT全部を登録できるわけではないので漏れが発生しますが、暫定的な対策としてはワークすると思います。
コードはこんな感じで、Botを明示的に記述しています。exports.ogp = functions.https.onRequest(async (req, res) => { // botの判定 const userAgent = req.headers['user-agent'].toLowerCase() const isBot = userAgent.includes('googlebot') || userAgent.includes('yahoou') || userAgent.includes('bingbot') || userAgent.includes('baiduspider') || userAgent.includes('yandex') || userAgent.includes('yeti') || userAgent.includes('yodaobot') || userAgent.includes('gigabot') || userAgent.includes('ia_archiver') || userAgent.includes('facebookexternalhit') || userAgent.includes('twitterbot') || userAgent.includes('developers.google.com') ? true : false // Botならリダイレクトしない, Botじゃなければリダイレクト if (isBot) { res.status(200).send( `<!doctype html> <head> // 更新するmeta dataを記述 </head> <body> // リダイレクトしない <header>${TITLE}</header> <main>${DESCRIPTION}</main> </body> </html>` ) } else { `<!doctype html> <head> // 更新するmeta dataを記述 </head> <body> <script> // クローラーにはメタタグを解釈させて、人間は任意のページに飛ばす location.href = '${SITEURL}/_tree/${treeId}' </script> </body> </html>` }注意
動的pathの先のdataにユーザ権限がある場合は注意が必要です。例えば、筆者のサービスの場合、tree/{treeId}は各ユーザの設定で非公開と公開のデータがあります。 cloud functionsは管理者権限で実行するため、非公開のデータもOGPやmeta用のデータを取得しようとします。
このように権限がある場合は、dataが非公開か、公開かを判定して、cloud functionsでhtmlを更新するかどうかの判断ロジックも追加が必要です。まとめ
BOTを判定してリダイレクトしないようにしたので、Googleのクローラも内容を読み込むことができるようになりました。下図を参照ください。これで検索クローラも各Treeの内容が読み込めるようになったので、SEO的にもまったく検索に引っかからないということはなくなったと思います。
しばらくこれでQnQにGoogle検索から流れてくる人がいるか様子を見てみます。ちょろっとQnQの紹介
ちなみにQnQはふと感じた疑問や感情を深堀りするサービスです。なぜこの映画を面白いとおもうのか、なぜこの料理が嫌いなのか、といったことを考えることで自分の価値観に気づいて脱マニュアル人間化することを目的にしています。興味があったら使ってみてください
https://qnqtree.com/aboutおわりに
最終的にはSSR(Server Side Rendering)をするのが王道かと思いますが開発がヘビーなので暫定でcloud functionsで対策をしました。
もっと良い解決策がありましたらコメントいただけると幸いです。
ご拝読ありがとうございました。
- 投稿日:2021-01-11T16:03:57+09:00
Vue.js のグローバルコンポーネントを使ってみた
初めに
前回に引き続きVue.jsで学んだことのアウトプットとして投稿します。
コンポーネントの中にメソッドを書く
<div id="app"> <new-btn></new-btn> {{ message }} </div>Vue.component('new-btn', { template: ` <button v-on:click='displayDate'> 日付を表示</button> `, methods: { displayDate: function() { date = new Date(); app.message = date.getFullYear() + '/' + ('0' + date.getMonth() + 1).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + '-' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); } } }) var app = new Vue({ el: '#app', data: { message: '' } })これは次のコードと同じ結果になる。
<div id="app"> <button v-on:click="displayDate">日付を表示</button> {{ message }} </div>var app = new Vue({ el: '#app', data: { message: '' }, methods: { displayDate: function() { date = new Date(); this.message = date.getFullYear() + '/' + ('0' + date.getMonth() + 1).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + '-' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); } } })コンポーネントの中にデータオブジェクトを書く
コンポーネントの中に
data
を書く。メソッドが呼ばれるたび、data
の中の count にアクセスして +1 する。Vue.component('new-btn', { template: ` <button v-on:click='displayDate'> 日付を表示{{ count }}回</button> `, methods: { displayDate: function() { date = new Date(); app.message = date.getFullYear() + '/' + ('0' + date.getMonth() + 1).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + '-' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); this.count++; } }, data: function() { return { count: 0 } } }) var app = new Vue({ el: '#app', data: { message: '' } })コンポーネントのプロパティで日付を何回も出現させる
<div id="app"> <button v-on:click="displayDate">ボタン</button> <date-display v-for="dateText in dateList" v-bind:date="dateText" ></date-display> </div>Vue.component('date-display', { props: ['date'], template: '<p>{{ date }}</p>' }) var app = new Vue({ el: '#app', data: { dateList: [] }, methods: { displayDate: function() { dateNow = new Date(); var dateText = dateNow.getFullYear() + '/' + ('0' + dateNow.getMonth() + 1).slice(-2) + '/' + ('0' + dateNow.getDate()).slice(-2) + '-' + ('0' + dateNow.getHours()).slice(-2) + ':' + ('0' + dateNow.getMinutes()).slice(-2) + ':' + ('0' + dateNow.getSeconds()).slice(-2); this.dateList.push(dateText) } } })
- 動作結果
参考記事
- Vue.js 日本語公式ドキュメント
https://jp.vuejs.org/v2/guide/components.html
- 投稿日:2021-01-11T10:21:38+09:00
効率的にサイト作り!Buefyでアイコンを表示しよう!!
僕は今年二十歳の代なので、今日は成人式に行ってきます!
なので、今日は時間がないため少し短めの内容となっております。
タイトルの通り、Buefyでアイコンを表示していきます。
アイコンの表示は他にもVuefyで行うこともできます。そのやり方は僕が書いた記事え、アイコンでまだFontAwesome使ってるの???を参考にして頂くと分かりやすいかと。
また、Buefyなんて聞いたの初めてという方は、こちらの記事初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!を見ていただくと分かると思います!
それでは、Buefyを使ってアイコンを表示してみましょう!
使いたいアイコンを決める
Fontawesomeの公式サイトから使いたいアイコンを検索して決めてください。
今回はTwitterアイコンを例にします。
b-iconタグで設定
使いたいアイコンを決めたら、
<i class="fab fa-twitter"></i>
このような欄があると思います。一番最初に、「fab」の部分をpack
で指定します。App.vue<b-icon pack="fab"></b-icon>次に、「fa-twitter」の部分を
icon
で設定し、適当にサイズを決めますApp.vue<b-icon pack="fab" icon="twitter" size="medium"></b-icon>テキストとの高さを揃える
<b-icon>
は時によっては、テキストと高さが合わない場合があります。なので、以下を参考にしてstyle
を設定してください。App.vue<b-icon pack="fas" icon="user-plus" size="medium" style="margin-right: 0.5rem; margin-bottom: 0.25rem; vertical-align: middle" ></b-icon>めちゃ簡単♪
以上、「効率的にサイト作り!Buefyでアイコンを表示しよう!!」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading
- 投稿日:2021-01-11T09:07:12+09:00
Vue.jsを学習した振り返り
はじめに
Vue.jsを学習した内容についての投稿
目次
・Vue.jsのメリット
・Vue.jsの基本的な使い方
・Vue.jsインスタンスを作る
・バインディングとは
・テイストとデータの(紐付け)バインデジング
・属性とデータの(紐付け)バインディング
・ディレクティブの省略記法
メソッドについてVeu.jsのメリット
・Vue.jsはDOM操作を自動で行うため、データやイベントが多くなってもコードが複雑になりにくい。
Veu.jsの読み込み
Vue.js<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Vue.js Test</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="example">{{ greeting }}</div> <script src="main.js"></script> </body> </html>Vue.jsconst app = new Vue({ el: '#example', data: { greeting: 'Hello Vue.js!', }, });プレビューすると'Hello Vue.js!'と表示される。
Vue.jsを読み込みはhead要素内にいれる
Vue.js<head> <meta charset="utf-8"> <title>Vue.js Test</title> <script 。src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head>head内要素にいれる理由は、ページ表示した最初の一瞬だけHTMLファイル書いた部分が
見えてしまう場合があり、これはHTMLファイルに記述した{.....}をVue.jsが処理するまでに
少し時間がかかり、head要素内に入れる方がbodyの直前に入れるより処理が早く表示を抑えることができる。Vueのインスタンス作成
newは演算子の一つでVue ( { } )はオブジェクトを複製するための特別な関数。特別な関数のことを
new コンストラクタという。複製したオブジェクトのことをVue インスタンスといいます。const app = new Vue( ) ①Vue コンストラクタ ②new Vue コンストラクタの呼び出し ③app インスタンスの格納Vueコンストラクターの引数には、オブジェクトを渡す。Vue.jsでオブジェクト渡すことをオプションオブジェクトという。オプションオブジェクトでプロパティを設定することでVue.jsの機能を使うことができます。
Vue.jsconst app = new Vue({ el: '#example', data: { greeting: 'Hello Vue.js!', }, });elとdataのプロパティをオプションオブジェクトに設定しています。
elオプション
elオプションのelは「element(要素)」の略称。elオプションにはDOM要素を設定します。
上記のmain.jsのコードでは#exampleを設定しています。index.html内のid属性div id="example"を
設定したことになります。設定した要素内のHTMLは、テンプレートというものになります。
テンプレート内ではVue.jsの機能が使えます。また、テンプレートはVue.jsを通してブラウザに表示されます。Vue.js<div id="example">{{ greeting }}</div>Vueの管理下に置かれたことになります。
dataオプション
dataオプションはデータを設定する。Vueインスタンスのプロパティにアクセスできます。Vue.jsdata: { greeting: 'Hello Vue.js!', },データオプションのオブジェクトにはgreetingという名前を設定して、その値に'Hello! vue.js'という文字列を設定。
index.htmlをプラウザで開きデベロッパーで確認すれば
Vue.js>app.grreeting "Hello! Vue.js"と表示されます。
テキストとデータを結びつける
Vueインスタンスのデータを表示させるにはHTML内で{{...}}を使用する。{{...}}の中にはデータオプションで指定したオブジェクトのプロパティを書き込む。
Vue.js<div id="example">{{ greeting }}</div>Vue.jsconst app = new Vue({ el: '#example', data: { greeting: 'Hello Vue.js!', }, });この時dataやappの名前は不要。
{{...}}をmustache(マスタック)構文という。
HTMLでテンプレート{{...}}を使用して、Vueインスタンスのdataオプション内のデータと結びつけることを、
データバインディング(結びつける)という。マスタック構文を使ってVueインスタンスのdataオプションとバインディング(結びつき)させる。
バインディングする事でデータが自動で変更され、Vue.jsによってDOM操作が自動で処理される。属性とデータを結びつける
属性とVueインスタンスのデータをバインディングすることもできる。属性の場合はマスタック構文は使えない。
その場合はv-bind属性を使用する。v-bindなどの特別な属性をディレクティブという。Vue.js<div id="example"> <a v-bind:href="url">Google</a> </div>Vue.jsconst app = new Vue({ el: '#example', data: { url: 'https://www.google.com/', }, });上記の場合はa要素のhref属性値をVueインスタンスのdataオプションurlプロパティと
バインディング(結びつけ)しています。v-bind:のあとに記述されているのがv-bindの引数になります。v-for 配列する処理
v-forは、配列やオブジェクトを繰り返し処理できます。配列は格納した変数 in 配列の構文を使用する。
書き方
Vue.js<ul id="#example"> <li v-for="member in members " v-bind:key="members" > {{ member }} </li> </ul>Vue.jsconst app = new Vue({ el: '#example', data: { members: ['太郎', '山田', '佐藤', '鈴木'], }, });上記の場合はmembersが配列。Vueインスタンスのデータオプション内で設定した配列になります。
v-bind:keyはVueが要素は別々であるとして識別するのに必要になる。その他の書き方
Vue.js<ul id="#example"> <li v-for="(member,index) in members " v-bind:key="index" > {{ index }} : {{ member }} </li> </ul>・0 : 太郎 ・1 : 山田 ・2 : 佐藤 ・3 : 鈴木リスト表示にインデックス(番号)を付け加えることができる。
v-for オブジェクトの繰り返し処理
プロパティの値を格納した変数 in オブジェクトの構文を使用する。
Vue.js<ul id="#example"> <li v-for="character" in characters " v-bind:key="character" > {{ character }} </li> </ul>Vue.jsconst app = new Vue({ el: '#example', data: { characters: { symbol: ミッキー, girlfriend: ミニー, duck: ドナルド, }, });結果
・ミッキー ・ミニー ・ドナルドオブジェクトの繰り返し処理2
(プロパティの値を格納した変数, プロパティ名を格納した変数) in オブジェクトでプロパティ名を利用できる。
オブジェクトの繰り返し処理2
(プロパティの値を格納した変数, プロパティ名を格納した変数) in オブジェクトでプロパティ名を利用できる。
Vue.js<ul id="#example"> <li v-for="(character, key)" in characters " v-bind:key="key" > {{ character }} : {{ character }} </li> </ul>Vue.jsconst app = new Vue({ el: '#example', data: { characters: { symbol: ミッキー, girlfriend: ミニー, duck: ドナルド, }, });結果
Vue.js・symbol: ミッキー ・girlfriend: ミニー ・duck: ドナルドv-on イベント処理
v-onは要素のイベントを処理する。
Vue.js<div id="example"> <input v-bind:value="name" v-on:input="name = $event.target.value" > <p>{ { name } }</p> </div>Vue.jsconst app = new Vue({ el: '#example', data: { name: '太郎', }, });この場合、input要素に入力することで、$event.target.valueの式が実行される。
($event)の$はjQueryで使用したものと関係ない。ディレクティブの省略記法
v-bind、v-onは省略して書くことができる。
下記のコードを
Vue.js<input v-on:input="name = $event.target.value" :value="name">v-on:を@マーク、v-bind:を : に置き換えれる
Vue.js<input @:input="name = $event.target.value" :value="name">※コードを省略する時は必ず統一する。
v-model フォーム入力
複数のフォーム入力はv-modelを使う。(v-bind、v-onを複数の要素に結びつけるとコードが複雑になる)
Vue.js<div id="example"> <input v-model="name"> <p>{{ name }}</p> </div>Vue.jsconst app = new Vue({ el: '#example', v-model: { name: '太郎', }, });※下記の場合はname
Vue.js<input v-model="Vueインスタンスデータのdataオプションに設定したプロパティ名">input要素のイベントが発生したら、指定したデータの値を更新する処理が行われ、
v-bind、v-onを使わなくても双方向のバインディング(結びつけ)ができます。算出プロパティ
計算処理(難しい処理)を行うときは算出プロパティを使う。
※難しい計算処理をテンプレート(HTML)内に記述するとコードが複雑でエラーの原因になります。
算出プロパティでテンプレートも見やすくなり、関数を使うことで複数の式を使用することも
できる。メソッド
メソッドもオプションオブジェクトのプロパティとして書く。オプション名はmethods。
算出プロパティ(computed)とメソッド(methods)の使い分け
算出プロパティはキャッシュ(一時保存)され、データが更新された時だけ再計算される。
index.html
Vue.js<div id="example"> <p> <button v-on:click="countUp()">{{ count }} 回</button> </p> <p>現在時刻(メソッド):{{ getDate() }}</p> <p>現在時刻(算出プロパティ): {{ date }}</p> </div>Vue.jsconst app = new Vue({ el: '#example', data: { count 0, }, computed: { date() { return newDate().tolocalStrings(); }, }, methods() { countUp() { this.count += 1; }, getDate() { return newDate().toLocalStrings(); }, }, });ボタンクリックでcountUp( )メソッドが呼び出され、Vueインスタンスのデータのcountの値、
countと結びついた要素内のテキスト(数字)も変化し、現在時刻(メソッド)も更新される。
メソッド(methods)は再描画されるたびに更新されるが、算出プロパティはVueインスタンス
データの値が変更になった場合のみ再計算される。
- 投稿日:2021-01-11T08:43:27+09:00
単一ファイルコンポーネントの原則をrails6のアプリに組み込んでみた。いまいちよくわからん。
単一ファイルコンポーネントの原則
コンポーネントごとにvueファイルを作成する開発の進め方のこと。つまりパーツ単位でHTMLをまとめてコンポーネントとして扱いどこでも呼び出せるようにすること、を指すと考えて良さそう。
これの反対をグローバルコンポーネント、といい
new Vue({ el: '#container '})といった形でcontainerをターゲットにしたvueファイルを作り、各ページで読み込む。この書き方は小規模開発では優れているが、大規模な開発になるといくつか不具合が生じる。中でも全てのコンポーネントの変数定義が共通になるのはちょっと避けたい。よって使用するHTMLごとにコンポーネント化する単一ファイルコンポーネントの原則に従って開発するのがベター。
単一コンポーネントでvueファイルを書くとこんな感じ
<template> <div class="side_bar">SideBar</div> </template> <script> </script> <style> .side_bar { width: 300px; background-color: green; } </style>templateの部分にHTML、scriptでjs、styleでcssを指定する。これをHTML部品ごとに作る。こうすると部品ごとに一つのファイルで扱えるのでやり直しがしやすい。またこのクラスにあたってるcssどれ?みたいな疑問もなくなる。
作ったコンポーネントをrailsアプリで読み込むわけだが今回は
各コンポーネントを読み込む親のjsがエントリーファイルとなっている。こいつをbuildしてrailsアプリで読み込むと各コンポーネントの設定がアプリに反映される仕組み。
main.js(エントリーファイル)
import Vue from 'vue'; import App from './App.vue'; // App.vueをエントリとしてレンダリング new Vue({ el: '#app', render: h => h(App) })App.vue(親コンポーネント)
<template> <div class="container"> <sidebar></sidebar> <chat-container></chat-container> </div> </template> <script> import Sidebar from './components/Sidebar.vue' import ChatContainer from './components/ChatContainer.vue' export default { components:{ Sidebar, ChatContainer } } </script> <style> .container { display: flex; margin: auto; width: 70%; height: 100vh; } </style>こいつをwebpackでコンパイルする。webpack.config.jsは以下の通り
module: { rules: [ { test: /\.vue$/, exclude: /node_modules/, loader: 'vue-loader' }, { test: /\.css$/, exclude: /node_modules/, use: ['style-loader', 'css-loader'], }, ] }, (省略) resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 } }, plugins: [new VueLoaderPlugin()]重要なのはloaderとresolveオプションかなと思うので関係ないところ
は省略。ちょっとハマったのはcss-loaderとstyle-loaderを読み込む際にはなぜかloaderオプションじゃなくてuseオプションにしないといけなかったこと。複数loaderを読み込むからなのかな?コンパイル後にrailsサーバーを立ち上げると画像のように別々のファイルに書いたhtmlとcssがきっちり反映されて一つのビューにまとまっていることがわかる。
参考
・単一ファイルコンポーネントについての公式ドキュメント
https://jp.vuejs.org/v2/guide/single-file-components.html
・単一ファイルコンポーネントをwebpackを使って実現する方法について
- 投稿日:2021-01-11T01:55:46+09:00
Vue2で[vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function.
表題のエラーに出会いました。
codesandboxにて、Composition APIで遊んでいた時に表題のエラーに出会ったので備忘録。
Vueは2系を使用しています。ソース
[vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function.
該当のエラー箇所はimport下のref
。
でもなぜこれでエラーになるのか・・・// Hello.vue <template> <div class="hello"> <p class="display-nuber">{{ num }}</p> <button @click="increment">increment</button> </div> </template> <script lang='ts'> import { defineComponent, ref } from "@vue/composition-api"; const num = ref(1); // <- ここでエラー export default defineComponent({ name: "test", setup() { const increment = () => { num.value++; }; return { num, increment, }; }, }); </script>// main.js import Vue from "vue"; import App from "./App.vue"; import VueCompositionApi from "@vue/composition-api"; Vue.use(VueCompositionApi); Vue.config.productionTip = false; new Vue({ render: (h) => h(App) }).$mount("#app");とりあえず解決
調べていくうちにこちらのissueを見つけました。
https://www.notion.so/Composition-API-24c25585c44e4559802d7bbf497511b3#eb149d74d549447bbc08cdfd63691926内容を見ていくと、
export default
の中でref
を使えば解決しそうな雰囲気。
ということで中に入れました。// Hello.vue <template> <div class="hello"> <p class="display-nuber">{{ num }}</p> <button @click="increment">increment</button> </div> </template> <script lang='ts'> import { defineComponent, ref } from "@vue/composition-api"; export default defineComponent({ name: "test", setup() { const num = ref(1); // <- export defaultの中に入れた const increment = () => { num.value++; }; return { num, increment, }; }, }); </script>解決。これでエラーは出なくなりました。
少し深堀り
こちらのstack overflowを見てみると
In short you need to create a separate file that will install the composition API plugin and call that file within the router/index.ts file to instantiate the plugin.
It’s because of the composition API not being inside Vue itself.Vue2系が内部にComposition APIを持っていないことが原因のよう。
また読み進めていくとプラグインを読み込むファイルを外に切り出せばうまくいきそうです。// installCompositionApi.js // 新たにこのファイルを作成 import Vue from "vue"; import VueCompositionApi from "@vue/composition-api"; Vue.use(VueCompositionApi);// main.js import "./InstallCompositionApi"; // 追加 import Vue from "vue"; import App from "./App.vue"; // import VueCompositionApi from "@vue/composition-api"; 削除 // Vue.use(VueCompositionApi); 削除 Vue.config.productionTip = false; new Vue({ render: (h) => h(App) }).$mount("#app");// Hello.vue <template> <div class="hello"> <p class="display-nuber">{{ num }}</p> <button @click="increment">increment</button> </div> </template> <script lang='ts'> import { defineComponent, ref } from "@vue/composition-api"; const num = ref(1); //元の位置に戻した(export defaultの外) export default defineComponent({ name: "test", setup() { // const num = ref(1); 削除 const increment = () => { num.value++; }; return { num, increment, }; }, }); </script>これで
export default
の外でref
を使用してもエラーは出なくなりました。Vue3では?
Composition APIがプラグインではなく正式に追加されているため、こちらのエラーは解消されているようです。
実際にcodesandboxで動かしてみましたが、これまでに記述したことをしなくてもref
をexport default
の外に出すことができました。
おわり。