20210415のvue.jsに関する記事は6件です。

Vue.js+Vuetifyでロードバイクのブランド辞典を作る② ~実装編~

はじめに この記事は「Vue.js+Vuetifyでロードバイクのブランド辞典を作る① ~Vuetify導入編~」の続きとなっております。 前回の記事では、VueプロジェクトにVuetifyを導入して、公式ドキュメントのカードコンポーネントからよさげなサンプルを見つけてくるところまでやりました。今回は、その続きからやっていきたいと思います。 先に全体のソースコードが見たいという方は、「Listコンポーネントを作成して効率よくコードを書く」の項目へ飛んでください。 カードを作る では、前回見つけたサンプル2つを組み合わせて1枚のカードを作る所から始めましょう。 左のカードからは ・EXPLOREを押すと、隠れていたテキストが表示される機能 右のカードからは ・画像にテキストを重ねるデザイン ・SAHRE、EXPLOREのボタン(今回はこれを公式サイトへのリンクにする) を採用しました。 結果、こうなりました。 見やすいように、ブランド名のところに背景色を設定しています。 template部分のソースコードはこちらになります。 自転車の画像やブランド名といった情報はscriptのdataに格納しデータバインディングしています。(今はわかりやすさのために日本語で書いています) <v-card class="mx-auto" max-width="350" > <v-img class="white--text align-end" height="200px" src=ロードバイクの画像URL > <!-- ブランド名の背景色を灰色に設定 --> <v-card-title style="background-color: rgba(125, 125, 125, 0.6); padding: 10px 20px;">{{ ブランド名 }}</v-card-title> </v-img> <v-card-subtitle class="pb-0"> {{ モデル名 }}<br> {{ 価格 }} </v-card-subtitle> <v-card-actions> <v-btn color="orange lighten-2" text > Explore </v-btn> <v-spacer></v-spacer> <v-btn icon @click="show = !show" > <v-icon>{{ show ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon> </v-btn> </v-card-actions> <v-expand-transition> <div v-show="show"> <v-divider></v-divider> <v-card-text class="text--primary"> {{ ブランドの説明 }} </v-card-text> </div> </v-expand-transition> <v-card-actions> <a href=公式サイトへのリンク style="text-decoration: none;"> <v-btn color="orange" text > 公式サイトへ </v-btn> </a> </v-card-actions> </v-card> リストレンダリングで国別に描画する さて、前項でカードの見た目は完成しました。今度はこれをリストレンダリングを用いて国ごとにデータを描画していきましょう。今回は例として、アメリカに本社を置くブランドを集めた"USbikes"という配列にデータを格納していきたいと思います。 配列に格納するデータは以下の通りです。 ・国名(英語) ・国名(日本語) ・ロードバイクの画像URL ・ブランド名 ・フラグシップモデル名 ・フラグシップモデルの価格 ・ブランドの説明(公式サイトから適当に引っ張ってきました) ・公式サイトのURL ・EXPLOREが開かれているかどうかを判定するフラグ リストレンダリングするには、v-forディレクティブを使用して"bike in USbikes"の形式で要素を描画していけばいいですね。v-imgとv-btnを囲むaタグにはv-bindを用いてURLを動的に渡している点には注意してください。(ただし、今回は":"を用いた省略記法で書いています) v-rowやv-colといったVuetifyのグリッドシステムについては公式ドキュメントをご参照ください。→Vuetify公式ドキュメントGridSystem App.vue <template> <v-container> <div class="country"> <hr /> <h1>{{ this.USbikes[0].country }}</h1> <p>{{ this.USbikes[0].countryJa }}</p> </div> <v-row justify="start"> <v-col v-for="bike in USbikes" :key="bike.brand" md="4"> <v-card class="mx-auto" max-width="350"> <v-img class="white--text align-end" height="200px" :src="bike.img"> <v-card-title style=" background-color: rgba(125, 125, 125, 0.6); padding: 10px 20px; " >{{ bike.name }}</v-card-title > </v-img> <v-card-subtitle class="pb-0"> {{ bike.model }}<br /> {{ bike.price }} </v-card-subtitle> <v-card-actions> <v-btn color="orange lighten-2" text> Explore </v-btn> <v-spacer></v-spacer> <v-btn icon @click="bike.show = !bike.show"> <v-icon>{{ bike.show ? "mdi-chevron-up" : "mdi-chevron-down" }}</v-icon> </v-btn> </v-card-actions> <v-expand-transition> <div v-show="bike.show"> <v-divider></v-divider> <v-card-text class="text--primary"> {{ bike.example }} </v-card-text> </div> </v-expand-transition> <v-card-actions> <a :href="bike.url" style="text-decoration: none"> <v-btn color="orange" text> 公式サイトへ </v-btn> </a> </v-card-actions> </v-card> </v-col> </v-row> </v-container> </template> <script> export default { name: "App", data: ()=> ({ USbikes:[ { country:'USA', countryJa:'アメリカ', img: 'https://embed.widencdn.net/img/dorelrl/b3aolw3spj/2000px@1x/C21_C11251M_SuperSix_EVO_HM_LAV_PD.png', name:'cannondale', model:'SuperSix EVO Hi-MOD Disc Red eTap AXS', price:'¥1,155,000', example:'cannondaleは本社をアメリカに置くバイクブランドです。', url:'https://www.cannondale.com/ja-jp', show: false, }, { country:'USA', countryJa:'アメリカ', img:'https://s7d5.scene7.com/is/image/Specialized/?layer=0&wid=1920&hei=640&fmt=jpg&src=is{Specialized/pdp-product-bg-dark?wid=1920&hei=640}&layer=1&src=is{Specialized/90620-05_TARMAC-SL7-SW-DI2-SAGAN-COLL-DECON-GRN-YEL_HERO?wid=920&hei=600&$hybris-pdp-hero$}', name:'SPECIALIZED', model:'S-Works Tarmac SL7 - Sagan Collection', price:'¥1,507,000', example:'スペシャライズドは先進技術を駆使したサイクリング機材を提案し、トップレーサーから情熱的なホビー・サイクリストまで、ライダーの生活を向上し、革新するアメリカの自転車ブランドです。', url:'https://www.specialized.com/jp/ja', show: false, }, { country:'USA', countryJa:'アメリカ', img:'https://trek.scene7.com/is/image/TrekBicycleProducts/EmondaSLR9Disc_21_33141_A_Primary?$responsive-pjpg$&cache=on,on&wid=1920&hei=1440', name:'TREK', model:'Émonda SLR 9', price:'¥1,316,700', example:'TREKです', url:'https://www.trekbikes.com/jp/ja_JP/', show: false, } ] }) }; </script> <style scoped> /*国名の英語と日本語表記を横並びにするためにdisplay:inline-block;を指定*/ .country h1 { display: inline-block; } .country p { display: inline-block; } hr { margin: 10px 0; } </style> Listコンポーネントを作成して効率よくコードを書く 前項でアメリカブランドについては一覧を作ることが出来ました。他の国のブランドについても同様に作成していけばよいのですが、イチイチ国ごとにtemplateを書いていてはコード量が膨大になり大変です。なので、リストレンダリングを行うListコンポーネントを作成し、それを再利用して効率よくコードを書いていきましょう。イメージとしてはこんな感じです。 App.vueに国ごとに配列を作成し、そのデータを子コンポーネントであるList.vueに渡してあげます。親コンポーネントから子コンポーネントにデータを渡す方法を軽くおさらいしましょう。 1.子コンポーネントを親コンポーネントにimportする。 2.親コンポーネントのcomponents:{}に子コンポーネントを指定する。 3.子コンポーネントのタグを用意して、送りたいdataをv-bindで指定する。 4.子コンポーネントのpropsに受け取り口を作成する。 5.子コンポーネントでdataのように使用する。 です。 詳しくは公式ドキュメントをご参照ください。 そのようにして今回は、App.vueにJPbikes,TWbikes...といったように国ごとに配列を作成し、Listコンポーネントにv-bindを用いて動的にデータを渡しました。そしてListコンポーネントでそれを受け取り、thisを用いてそのデータにアクセスして描画しています。簡略化のために、下のコードではブランドの情報は省略しています。 App.vue <template> <v-app> <v-main> <div class="contents"> <List :bikes="this.JPbikes"></List> <List :bikes="this.TWbikes"></List> <List :bikes="this.USbikes"></List> <List :bikes="this.CAbikes"></List> <List :bikes="this.UKbikes"></List> <List :bikes="this.ITAbikes"></List> <List :bikes="this.FRbikes"></List> <List :bikes="this.ESbikes"></List> <List :bikes="this.BELbikes"></List> <List :bikes="this.SWbikes"></List> <List :bikes="this.GEbikes"></List> <List :bikes="this.NZbikes"></List> </div> </v-main> </v-app> </template> <script> import List from './components/List'; export default { name: 'App', components: { List, }, data: () => ({ JPbikes:[], TWbikes:[], USbikes:[], CAbikes:[], UKbikes:[], ITAbikes:[], FRbikes:[], ESbikes:[], BELbikes:[], SWbikes:[] GEbikes:[], NZbikes:[], }) }; </script> <style> .contents{ background-color: rgb(250, 250, 250); } h1{ margin-top: 50px; margin-left: 10px; font-size: 45px; font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif } p{ font-size: 15px; margin-left: 10px; color: rgb(107, 107, 107); } </style> List.vue <template> <v-container> <hr> <div class="country"> <h1>{{ this.bikes[0].country }}</h1> <p>{{ this.bikes[0].countryJa }}</p> </div> <v-row justify="start"> <v-col v-for="bike in bikes" :key="bike.title" md="4"> <v-card class="mx-auto" max-width="350"> <v-img class="white--text align-end" height="200px" :src="bike.img"> <v-card-title style=" background-color: rgba(125, 125, 125, 0.6); padding: 10px 20px; " >{{ bike.name }}</v-card-title > </v-img> <v-card-subtitle class="pb-0"> {{ bike.model }}<br /> {{ bike.price }} </v-card-subtitle> <v-card-actions> <v-btn color="orange lighten-2" text> Explore </v-btn> <v-spacer></v-spacer> <v-btn icon @click="bike.show = !bike.show"> <v-icon>{{ bike.show ? "mdi-chevron-up" : "mdi-chevron-down" }}</v-icon> </v-btn> </v-card-actions> <v-expand-transition> <div v-show="bike.show"> <v-divider></v-divider> <v-card-text class="text--primary"> {{ bike.example }} </v-card-text> </div> </v-expand-transition> <v-card-actions> <a :href="bike.url" style="text-decoration: none"> <v-btn color="orange" text> 公式サイトへ </v-btn> </a> </v-card-actions> </v-card> </v-col> </v-row> </v-container> </template> <script> export default { name: "List", props: ["bikes"], }; </script> <style scoped> .country h1 { display: inline-block; } .country p { display: inline-block; } hr { margin: 10px 0; } </style> これでサイトは完成です。 おわりに 「Vue.js+Vuetifyでロードバイク辞典を作る」をここまで読んでいただきありがとうございました。 私もこのサイトの作成を通じて初めてVuetifyに触れたので、もし間違っている点などがございましたらコメントで指摘していただけると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js プロパティとライフサイクルフックの早見表

Vue プロパティ プロパティ名 役割 el VueインスタンスをどのHTML要素に結び付けるを定義 data Vueインスタンスが保持するデータ(状態)を定義する methods Vueインスタンスのメソッド(振る舞い)を定義する filters Vueインスタンスが持つフィルターを定義する computed Vueインスタンスが持つ算出プロパティを定義する watch Vueインスタンスが持つウォッチャを定義する components Vueインスタンスが持つコンポーネントを定義する props コンポーネントが持つプロパティを定義する emits コンポーネントが発行するイベントを定義する Vue ライフサイクルフック フック名 発生のタイミング beforeCreate Vueインスタンスが生成された直後に発生 created リアクティブデータを監視する準備が終わった時 beforeMount VueインスタンスがDOMと結びつく直前 mounted VueインスタンスがDOMと結びついた直後 beforeUpdate リアクティブデータが更新され、DOMに反映される直前 updated リアクティブデータが更新され、DOMに反映された直後 beforeUnmount Vueインスタンスのマウントが解除される直前 unmounted Vueインスタンスのマウントが解除された直後 beforeDestroy Vueインスタンスが破棄される直前 destroyed Vueインスタンスが破棄された直後
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js × Firestore でデータを追加&取得してみた

post.vue <button class="btn" @click.prevent="postItem">投稿</button> <script> methods: { postItem() { firebase .firestore() .collection("posts") .add({ title: this.title, description: this.description, image: this.image, genre: this.genre, time: firebase.firestore.FieldValue.serverTimestamp() }) .then(() => { confirm("投稿しますか?"); }); } } }; </script> postItem()が押下されたら、dbインスタンスを初期化して"posts"という名前のコレクションを参照し、add()でそれぞれのデータを格納する。 time: firebase.firestore.FieldValue.serverTimestamp() 作成・編集時刻につきましては、自動で取得してくれる便利なものがありますので、そちらを使用してます。 ◇参考 Firestoreでドキュメントの作成/編集時刻をサーバ側に任せる方法 .then(() => { confirm("投稿しますか?"); }); 上記で確認ダイアログを表示させています。 mounted() { this.db = firebase.firestore().collection("posts"); }, ※何度も使う場合は、リファクタリングで関数で定義しておくとGOOD!! board.vue / 親コンポーネント <List v-for="(list,index) in allData" :index="index" :list="list" :key="list.id" /> allDataのデータをlistとindexにそれぞれ格納。 v-for="(list,index) in allData" / v-for="(値, キー) in 配列orオブジェクト" では、下記のようなイメージ。 ◇参考 Vue.jsのリストレンダリングを勉強する allData1 0.title ~ 1.genre ~ 2.image ~ 3.time  ~ 4.description ~ allData2 0.title ~ 1.genre ~ 2.image ~ 3.time  ~ 4.description ~ :index="index" / v-bind:属性名="値"では、下記のようなイメージ。 list: [title.genre.image.time.description] index: [0.1.2.3.4] <script> export default { data() { return { allData: [] }; }, created() { firebase .firestore() .collection("posts") .get() .then(snapshot => { //"posts"(参照先)のスナップショットを得る snapshot.forEach(doc => { //上記で得たデータをforEachでドキュメントの数だけ"doc"データに格納 this.allData.push(doc.data()); //更にallDataの空箱に格納した"doc"データを格納 }); }); } }; </script> "posts"コレクションの全ドキュメントを取得し、get()でdoc"データに格納後、push()でallDataに格納してます。 list.vue / 子コンポーネント <a href="#" class="small-font">{{user.name}}</a> </div> <div class="list-ttl">{{list.title}}</div> <div class="post-container"> <p>{{list.description}}</p> </div> <div class="post-genre">{{list.genre}}</div> <div class="btn-inner"> <button class="btn">ルームへ参加</button> propsを使って親コンポーネントから値を取得したデータがlist内にデータが格納させているので、{{}}を使用してlist内のそれぞれの値を取得。 <script> props: { list: { type: Object //propsにより親コンポーネントから受け取ったデータをlist内にObject型で格納されてる }, index: { type: Number //propsにより親コンポーネントから受け取ったデータをindex内にNumber型で格納されてる } } </script> 親コンポーネントから取得したデータが下記になります。 list: [title.genre.image.time.description] index: [0.1.2.3.4] ◇参考 Vueのpropsの使い方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのCLI利用

Vue.jsでの開発を学んでいるので、その時の過程をまとめておきます。 Nodeとnpmの導入を済ませた状態から記述しています。 VueCLIのインストール 下記のコマンドでVue.jsの開発ツール@vue/cliのインストールを行います。 % npm install -g @vue/cli 次のコマンドでバージョンの確認を行います。 % vue --version @vue/cli 4.5.12 新規のプロジェクト % vue create my-project ? Your connection to the default yarn registry seems to be slow. Use https://registry.npm.taobao.org for faster installation? (Y/n) 今回はnで継続。 ? Please pick a preset: (Use arrow keys) ❯ Default ([Vue 2] babel, eslint) Default (Vue 3 Preview) ([Vue 3] babel, eslint) Manually select features 「Default ([Vue 2] babel, eslint) 」を選択したままEnter。 ? Please pick a preset: Default ([Vue 2] babel, eslint) ? Pick the package manager to use when installing dependencies: (Use arrow keys) ❯ Use Yarn Use NPM 今回はyarnを使うのでそのまま継続。 $ cd my-first-vue-app $ yarn serve 上記の2つを実行して、ローカル環境の - Local: http://localhost:8080/ にアクセスできて、Welcome to Your Vue.js Appが表示されると環境構築完了です。 ありがとうございました。 参考 https://it-web-life.com/vue_environment_setup/ https://qiita.com/C_HERO/items/318fe65871f8e53e8c80
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

highlight.js + Vue で行番号(line-numbers)を加えてhilight表示する。

hilight + 行番号を表示したものをゴールとして目指します。 参考にした記事、コードは下記です highlight.jsで行ごとに区切りつつハイライトする highlightjs/vue-plugin 行番号 + ハイライト を行う関数を作る highlight.jsで行ごとに区切りつつハイライトする を参考にすると、 単純に行区切りにhilightを適用しても、うまくいかないことがわかります。 そこで let state = null const result = hljs.highlight('markdown', row, true, state) state = result.top とし、 result.top を継承することで行違いのhilightを働かせることができます。 これを利用し、 lineByLineHighilght function lineByLineHighilght (language, body) { let state = null const output = [] const bodySplit = body.split('\n') let maxLength = String(bodySplit.length).length for (let line = 0; line < bodySplit.length; line++) { const row = bodySplit[line] const result = hljs.highlight(language, row, true, state) let setLineNumber = `<div style="float: left; width: ${maxLength}7px;"><span style="float: right; padding-right: 5px;">${line}:</span></div>` let setLine = `<div class="lineNumber">${setLineNumber}<span class="line-value">${result.value}</span></div>` if (result.value.length === 0) { setLine = `<div class="lineNumber">${setLineNumber}</div></br>` } state = result.top output.push(setLine) } return output.join('') } とすることで、行番号を付与することができます。 ちなみに、 if (result.value.length === 0) { setLine = `<div class="lineNumber">${setLineNumber}</div></br>` } の部分ですが、これはresult.value.length === 0の時、改行されない問題が合ったので行ってます。 ( もっと上手い解決策があれば教えてください:) ) VueJs上でhilight + 行番号表示を行う vueのコンポーネントとして、hilightJsを扱えれば一番良く、 また公式側でvueでhilight.jsを扱えるようになっています。 hilight.js ただ、今回はこのままでは扱えないので、 highlightjs/vue-pluginの中身を参考にし、 hilightVue.js import hljs from 'highlight.js' function hasValueOrEmptyAttribute (value) { return Boolean(value || value === '') } function lineByLineHighilght (language, body) { let state = null const output = [] const bodySplit = body.split('\n') let maxLength = String(bodySplit.length).length for (let line = 0; line < bodySplit.length; line++) { const row = bodySplit[line] const result = hljs.highlight(language, row, true, state) let setLineNumber = `<div style="float: left; width: ${maxLength}7px;"><span style="float: right; padding-right: 5px;">${line}:</span></div>` let setLine = `<div class="lineNumber">${setLineNumber}<span class="line-value">${result.value}</span></div>` if (result.value.length === 0) { setLine = `<div class="lineNumber">${setLineNumber}</div></br>` } state = result.top output.push(setLine) } return output.join('') } const Component = { props: ['language', 'code'], data: function () { return { detectedLanguage: '', unknownLanguage: false } }, computed: { className () { if (this.unknownLanguage) return '' return 'hljs ' + this.detectedLanguage }, highlighted () { return lineByLineHighilght(this.language, this.code) }, ignoreIllegals () { return true } }, render (createElement) { return createElement('pre', {}, [ createElement('code', { class: this.className, domProps: { innerHTML: this.highlighted } }) ]) } } export default { install (Vue) { Vue.component('highlightjs', Component) }, component: Component } このように行うことで、任意なvueファイルで <highlightjs language="HTML" :code="code"/> とすると、 下記のような結果にhilight + 行番号表示が行えます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Veturがうまく動かない。files.associations正しく書きましたか?

VSCodeでVeturがちゃんと動かなくてVueファイルの中でTS書くと Type annotations can only be used in TypeScript files. とか出るんですよ。「なんでだ、なんでだ」とさんざん調べた結果、原因は拡張子vueのファイルがhtmlとして認識されていたからでした。 VSCodeのsettings.jsonがこうなってた。↓ "files.associations": { "*.vue": "html" } こうしたらなおった。↓ "files.associations": { "*.vue": "vue" } なんだよhtmlって。そんなの書いた覚えないんだけど…。(書いたんだろうけど) settings.jsonを手書きしなくてもVSCodeのSettings[⌘,]からCommonly UsedのFiles: Associationsでも設定できます。 いくらググってもこの情報が出てこなかったのはそんなところでハマる人がいないからなんだろうけど、もしもどこかに同じ内容で困っている人がいたら是非ここに辿り着いてほしい。ではでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む