- 投稿日:2021-01-27T23:24:36+09:00
コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義(セットアップ&実装偏)
大分タイトルが長くなりました。
皆さんこんにちは!
今や第4次産業革命とも呼ばれている「サブスクリプション」。
知らない人はいませんよね???
僕自身アマゾンプライム、ネットフリックス、U-NEXTのサブスクリプションに加入しております。(洋画と海外ドラマめちゃ好き。一番好きなのはプリズンブレイクです。好きな俳優はジェイソンステイサムです。)
元々、サブスクリプションというものは、企業の利益をより確実に財務表などに反映させるために作られたサービスです。これをすることで、リスクをある程度回避したうえで、株主や投資家といったお偉いさんたちにお話をできるわけです。
今やあの世界的に有名な「ポルシェ」もサブスクリプションを始めています。
まぁ、サブスクリプションの話はこの辺にしておきます。
今回はVue.js、Firebase、Stripeを使って、サブスクリプションの記事を書いていこうかなと思います。
まず初めに、@mogmetさんが書いた記事
Firebase ExtensionsのRun Subscription Payments with Stripeを使ってサブスク課金をコードを書かずに実装する
を参考にさせて頂きました。
とても分かりやすかったです!!
まずは、こちらの記事を見て下さい。
こちらの記事を見た艇で話を進めていきます。
なぜこの記事を書こうと思ったかと言うと、Vue.jsで実行するとなると色々変えなければいけないポイントがあったからです。
記事は見たはいいものの、実装できない!
とか
これから導入していきたい!
と考えている方に丁寧に解説していくので、肩の力を抜いてご覧ください。
ただし、1つ1つの動作が何を意味をしているかはキリがないので説明致しません。
それでは一緒に説明を見ていきましょう!!!
1, スクリプトファイルの読み込み
headタグにStripeのスクリプトファイルを読み込みます。index.html<script src="https://js.stripe.com/v3"></script>また、特定のページだけ読み込みたいという方は、こちらの記事にやり方が書いてあるのでぜひご覧ください!
Vue.jsでページごとにhead,meta,titleを変える方法
2, HTML要素、template要素の配置
こちら、最初だからと言って舐めない方が良いです。
僕は、このHTML要素について十分に理解していなかったので中々時間取られました。
最初に言っておきます。
template要素は必ず
index.htmlに書いてください!絶対に
.vueファイルのtemplate内に書かないでください!!!index.html<body> <template id="product"> <div class="product"> <img src="" alt="" /> <h2>name</h2> <p class="description">description</p> <form class="product-form"> <label for="price">Choose pricing plan</label> <select id="price" name="price"></select> <button type="submit">Subscribe</button> </form> </div> </template> </body>credit.vue<template> <button id="signout" type="button">Sign out</button> <div id="subscribe"> <h2>Subscribe</h2> <div class="test-card-notice"> Use any of the <a href="https://stripe.com/docs/testing#cards" target="_blank" rel="noopener noreferrer">Stripe test cards </a> for this demo, e.g. <div class="card-number">4242<span></span>4242<span></span>4242<span></span>4242</div> </div> <section class="products"></section> </div> <section id="my-subscription"> <h2>My subscription</h2> <p></p> <h3>View invoices, update subscription & payment methods</h3> <button id="billing-portal-button" @click="subscribeApply">Access customer portal</button> </section> <footer> <p> Made with ❤️ and ☕️ by Stripe & Firebase | View <a href="https://github.com/stripe-samples/firebase-subscription-payments" target="_blank" rel="noopener noreferrer" >source </a> </p> </footer> </template>もう一回言います。
<template id="product">はindex.htmlに書かないと後々沼にハマるのでご注意ください。3, dataプロパティに変数を宣言
stripeとdbとcurrentUserと言う名前の変数をdataプロパティに宣言します。credit.vue<script> export default { data() { return { stripe: null, db: null, currentUser: null } }, </script>4, コンテンツの表示
次に、Stripeで追加したコンテンツ(商品)を表示していきます。
Stripeに商品を表示すると、以下のような
productsテーブルが作成されると思います。現時点で
customersテーブルはあっても無くても構いません。無くても焦らずに読み進めて下さい。
productsテーブルが作成されない場合は、設定段階でなんらかの記入ミスがあるので、見返してみてください。そして、
startDataListenersと言う名前の関数をmethods内に作ります。credit.vue<script> export default { data() { return { stripe: null, db: null, currentUser: null } }, methods: { async startDataListeners() { const products = document.querySelector('.products') const template = document.querySelector('#product') await this.db .collection('products') .where('active', '==', true) .get() .then((querySnapshot) => { querySnapshot.forEach((doc) => { doc.ref.collection('prices').orderBy('unit_amount').get().then((priceSnap) => { if (!('content' in document.createElement('template'))) { console.error('Your browser doesn’t support HTML template elements.') } const product = doc.data() const container = template.content.cloneNode(true) container.querySelector('h2').innerText = product.name.toUpperCase() container.querySelector('.description').innerText = product.description?.toUpperCase() || '' // Prices dropdown priceSnap.docs.forEach((doc) => { const priceId = doc.id const priceData = doc.data() const content = document.createTextNode(`${priceData.unit_amount}円`) const option = document.createElement('option') option.value = priceId option.appendChild(content) container.querySelector('#price').appendChild(option) }) if (product.images.length) { const img = container.querySelector('img') img.src = product.images[0] img.alt = product.name } const form = container.querySelector('form') form.addEventListener('submit', this.createStripeCustomers) products.appendChild(container) }) }) }) // Get all subscriptions for the customer this.db .collection('customers') .doc(this.currentUser) .collection('subscriptions') .where('status', 'in', ['trialing', 'active']) .onSnapshot(async (snapshot) => { if (snapshot.empty) { // Show products document.querySelector('#subscribe').style.display = 'block' return } }) }, }, } </script>ここで、
const template = document.querySelector('#product')に注目。先ほど口うるさく言った、「
<template id="product">はindex.html内に書いて!」とはこのことです。これをindex.html内に書かないと、
document.querySelector('#product')がnullの値を返します。正直なぜかは分かりません。
分かる方がいたら、ぜひコメント欄にてご教えて下さると幸いです。
5, customersテーブルの作成、およびカード情報登録画面へ遷移
次に、customersテーブルを作成していきます。
下記のボタンを押したときの処理になります。
createStripeCustomersと言う名前の関数をmethods内に作成します。コードが長くなるので、これまでのコードは書かないので、ご了承ください。
間違って消さないように。
credit.vueasync createStripeCustomers(event) { event.preventDefault() document.querySelectorAll('button').forEach((b) => (b.disabled = true)) const formData = new FormData(event.target) const docRef = await this.db .collection('customers') .doc(this.currentUser) .collection('checkout_sessions') .add({ price: formData.get('price'), allow_promotion_codes: true, tax_rates: [process.env.VUE_APP_STRIPE_TAX_RATES], success_url: process.env.VUE_APP_BASIC_URL + '/user/account?isActive=0', cancel_url: process.env.VUE_APP_BASIC_URL + '/creditcardregistration', metadata: { tax_rate: '10% sales tax exclusive' } }) // Wait for the CheckoutSession to get attached by the extension docRef.onSnapshot((snap) => { const { error, sessionId } = snap.data() if (error) { // Show an error to your customer and then inspect your function logs. alert(`An error occured: ${error.message}`) document.querySelectorAll('button').forEach((b) => (b.disabled = false)) } if (sessionId) { // We have a session, let's redirect to Checkout // Init Stripe const stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY) stripe.redirectToCheckout({ sessionId }) } return false }) },ここで、注意すべき点は
tax_ratesです。何を注意するのかと言うと、
[process.env.VUE_APP_STRIPE_TAX_RATES]を配列として書いていることです。なぜかは、憶測に過ぎないのですが、国によって税率が違うからだと思います。
悪魔で憶測です。鵜呑みにはしないでくださいね。
また、
process.envなんて初めて見たという方は、こちらの記事初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!
をご覧ください!
環境変数(process.env)は非常に便利なので、ぜひこの機会にご利用ください!!!
6, 請求書ページに遷移
最後に、下記のボタンを押したときの処理を書いてきます。
当然のように、クレジットカードを利用している人が請求書を見れるようにしないといけません。
viewInvoiceと言う名前の関数をmethods内に作成します。これも今までのコードは書かないのでご了承ください。
間違って消さないように。。。
credit.vueasync viewInvoice(event) { this.isLoading = true event.preventDefault() document.querySelectorAll('button').forEach((b) => (b.disabled = true)) // Call billing portal function const functionRef = firebase .app() .functions(process.env.VUE_APP_FUNCTION_ROCATION) .httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink') const { data } = await functionRef({ returnUrl: window.location.origin }) window.location.assign(data.url) },
process.env.VUE_APP_FUNCTION_ROCATIONにはFirebaseで最初に設定した地域を指定します。日本なら「asia-northeast」ですね。
これで終わりです!!
本当にコピペして頂くだけで、実行できます!
ちなみに、クレジットカードを登録しているかの確認は色々とあるのですが、下記の画像を見て下さい。
・クレジット決済を行っていない場合
・クレジット決済を行っている場合
クレジット決済を行っている場合は、
subscriptionsテーブルが作成されていますね。このようにして判断をすることもできます。
また、こんなことをしなくても「firebase-functions」を自分で作成することで判断することもできます。
その際は、下記の記事が参考になると思うので、時間がある方はぜひご覧ください!
さぁ、firebaseでユーザーが管理者かどうかの判別を行おう!
いかがだったでしょうか?
Stripeの導入は初の試みだったので、すごく大変でした。
ただ、これから学習を進めていく方々にはあまり時間を無駄にしてほしくはないので、この記事をみながら第4次産業革命の波に乗っていきましょう!!
以上、「コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも毎日初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading
- 投稿日:2021-01-27T23:24:36+09:00
コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義
大分タイトルが長くなりました。
皆さんこんにちは!
今や第4次産業革命とも呼ばれている「サブスクリプション」。
知らない人はいませんよね???
僕自身アマゾンプライム、ネットフリックス、U-NEXTのサブスクリプションに加入しております。(洋画と海外ドラマめちゃ好き。一番好きなのはプリズンブレイクです。好きな俳優はジェイソンステイサムです。)
元々、サブスクリプションというものは、企業の利益をより確実に財務表などに反映させるために作られたサービスです。これをすることで、リスクをある程度回避したうえで、株主や投資家といったお偉いさんたちにお話をできるわけです。
今やあの世界的に有名な「ポルシェ」もサブスクリプションを始めています。
まぁ、サブスクリプションの話はこの辺にしておきます。
今回はVue.js、Firebase、Stripeを使って、サブスクリプションの記事を書いていこうかなと思います。
まず初めに、@mogmetさんが書いた記事
Firebase ExtensionsのRun Subscription Payments with Stripeを使ってサブスク課金をコードを書かずに実装する
を参考にさせて頂きました。
とても分かりやすかったです!!
まずは、こちらの記事を見て下さい。
こちらの記事を見た艇で話を進めていきます。
なぜこの記事を書こうと思ったかと言うと、Vue.jsで実行するとなると色々変えなければいけないポイントがあったからです。
記事は見たはいいものの、実装できない!
とか
これから導入していきたい!
と考えている方に丁寧に解説していくので、肩の力を抜いてご覧ください。
ただし、1つ1つの動作が何を意味をしているかはキリがないので説明致しません。
それでは一緒に説明を見ていきましょう!!!
1, スクリプトファイルの読み込み
headタグにStripeのスクリプトファイルを読み込みます。index.html<script src="https://js.stripe.com/v3"></script>また、特定のページだけ読み込みたいという方は、こちらの記事にやり方が書いてあるのでぜひご覧ください!
Vue.jsでページごとにhead,meta,titleを変える方法
2, HTML要素、template要素の配置
こちら、最初だからと言って舐めない方が良いです。
僕は、このHTML要素について十分に理解していなかったので中々時間取られました。
最初に言っておきます。
template要素は必ず
index.htmlに書いてください!絶対に
.vueファイルのtemplate内に書かないでください!!!index.html<body> <template id="product"> <div class="product"> <img src="" alt="" /> <h2>name</h2> <p class="description">description</p> <form class="product-form"> <label for="price">Choose pricing plan</label> <select id="price" name="price"></select> <button type="submit">Subscribe</button> </form> </div> </template> </body>credit.vue<template> <button id="signout" type="button">Sign out</button> <div id="subscribe"> <h2>Subscribe</h2> <div class="test-card-notice"> Use any of the <a href="https://stripe.com/docs/testing#cards" target="_blank" rel="noopener noreferrer">Stripe test cards </a> for this demo, e.g. <div class="card-number">4242<span></span>4242<span></span>4242<span></span>4242</div> </div> <section class="products"></section> </div> <section id="my-subscription"> <h2>My subscription</h2> <p></p> <h3>View invoices, update subscription & payment methods</h3> <button id="billing-portal-button" @click="subscribeApply">Access customer portal</button> </section> <footer> <p> Made with ❤️ and ☕️ by Stripe & Firebase | View <a href="https://github.com/stripe-samples/firebase-subscription-payments" target="_blank" rel="noopener noreferrer" >source </a> </p> </footer> </template>もう一回言います。
<template id="product">はindex.htmlに書かないと後々沼にハマるのでご注意ください。3, dataプロパティに変数を宣言
stripeとdbとcurrentUserと言う名前の変数をdataプロパティに宣言します。credit.vue<script> export default { data() { return { stripe: null, db: null, currentUser: null } }, </script>4, コンテンツの表示
次に、Stripeで追加したコンテンツ(商品)を表示していきます。
Stripeに商品を表示すると、以下のような
productsテーブルが作成されると思います。現時点で
customersテーブルはあっても無くても構いません。無くても焦らずに読み進めて下さい。
productsテーブルが作成されない場合は、設定段階でなんらかの記入ミスがあるので、見返してみてください。そして、
startDataListenersと言う名前の関数をmethods内に作ります。credit.vue<script> export default { data() { return { stripe: null, db: null, currentUser: null } }, methods: { async startDataListeners() { const products = document.querySelector('.products') const template = document.querySelector('#product') await this.db .collection('products') .where('active', '==', true) .get() .then((querySnapshot) => { querySnapshot.forEach((doc) => { doc.ref.collection('prices').orderBy('unit_amount').get().then((priceSnap) => { if (!('content' in document.createElement('template'))) { console.error('Your browser doesn’t support HTML template elements.') } const product = doc.data() const container = template.content.cloneNode(true) container.querySelector('h2').innerText = product.name.toUpperCase() container.querySelector('.description').innerText = product.description?.toUpperCase() || '' // Prices dropdown priceSnap.docs.forEach((doc) => { const priceId = doc.id const priceData = doc.data() const content = document.createTextNode(`${priceData.unit_amount}円`) const option = document.createElement('option') option.value = priceId option.appendChild(content) container.querySelector('#price').appendChild(option) }) if (product.images.length) { const img = container.querySelector('img') img.src = product.images[0] img.alt = product.name } const form = container.querySelector('form') form.addEventListener('submit', this.createStripeCustomers) products.appendChild(container) }) }) }) // Get all subscriptions for the customer this.db .collection('customers') .doc(this.currentUser) .collection('subscriptions') .where('status', 'in', ['trialing', 'active']) .onSnapshot(async (snapshot) => { if (snapshot.empty) { // Show products document.querySelector('#subscribe').style.display = 'block' return } }) }, }, } </script>ここで、
const template = document.querySelector('#product')に注目。先ほど口うるさく言った、「
<template id="product">はindex.html内に書いて!」とはこのことです。これをindex.html内に書かないと、
document.querySelector('#product')がnullの値を返します。正直なぜかは分かりません。
分かる方がいたら、ぜひコメント欄にてご教えて下さると幸いです。
5, customersテーブルの作成、およびカード情報登録画面へ遷移
次に、customersテーブルを作成していきます。
下記のボタンを押したときの処理になります。
createStripeCustomersと言う名前の関数をmethods内に作成します。コードが長くなるので、これまでのコードは書かないので、ご了承ください。
間違って消さないように。
credit.vueasync createStripeCustomers(event) { event.preventDefault() document.querySelectorAll('button').forEach((b) => (b.disabled = true)) const formData = new FormData(event.target) const docRef = await this.db .collection('customers') .doc(this.currentUser) .collection('checkout_sessions') .add({ price: formData.get('price'), allow_promotion_codes: true, tax_rates: [process.env.VUE_APP_STRIPE_TAX_RATES], success_url: process.env.VUE_APP_BASIC_URL + '/user/account?isActive=0', cancel_url: process.env.VUE_APP_BASIC_URL + '/creditcardregistration', metadata: { tax_rate: '10% sales tax exclusive' } }) // Wait for the CheckoutSession to get attached by the extension docRef.onSnapshot((snap) => { const { error, sessionId } = snap.data() if (error) { // Show an error to your customer and then inspect your function logs. alert(`An error occured: ${error.message}`) document.querySelectorAll('button').forEach((b) => (b.disabled = false)) } if (sessionId) { // We have a session, let's redirect to Checkout // Init Stripe const stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY) stripe.redirectToCheckout({ sessionId }) } return false }) },ここで、注意すべき点は
tax_ratesです。何を注意するのかと言うと、
[process.env.VUE_APP_STRIPE_TAX_RATES]を配列として書いていることです。なぜかは、憶測に過ぎないのですが、国によって税率が違うからだと思います。
悪魔で憶測です。鵜呑みにはしないでくださいね。
また、
process.envなんて初めて見たという方は、こちらの記事初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!
をご覧ください!
環境変数(process.env)は非常に便利なので、ぜひこの機会にご利用ください!!!
6, 請求書ページに遷移
最後に、下記のボタンを押したときの処理を書いてきます。
当然のように、クレジットカードを利用している人が請求書を見れるようにしないといけません。
viewInvoiceと言う名前の関数をmethods内に作成します。これも今までのコードは書かないのでご了承ください。
間違って消さないように。。。
credit.vueasync viewInvoice(event) { this.isLoading = true event.preventDefault() document.querySelectorAll('button').forEach((b) => (b.disabled = true)) // Call billing portal function const functionRef = firebase .app() .functions(process.env.VUE_APP_FUNCTION_ROCATION) .httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink') const { data } = await functionRef({ returnUrl: window.location.origin }) window.location.assign(data.url) },
process.env.VUE_APP_FUNCTION_ROCATIONにはFirebaseで最初に設定した地域を指定します。日本なら「asia-northeast」ですね。
これで終わりです!!
本当にコピペして頂くだけで、実行できます!
ちなみに、クレジットカードを登録しているかの確認は色々とあるのですが、下記の画像を見て下さい。
・クレジット決済を行っていない場合
・クレジット決済を行っている場合
クレジット決済を行っている場合は、
subscriptionsテーブルが作成されていますね。このようにして判断をすることもできます。
また、こんなことをしなくても「firebase-functions」を自分で作成することで判断することもできます。
その際は、下記の記事が参考になると思うので、時間がある方はぜひご覧ください!
さぁ、firebaseでユーザーが管理者かどうかの判別を行おう!
いかがだったでしょうか?
Stripeの導入は初の試みだったので、すごく大変でした。
ただ、これから学習を進めていく方々にはあまり時間を無駄にしてほしくはないので、この記事をみながら第4次産業革命の波に乗っていきましょう!!
以上、「コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも毎日初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading
- 投稿日:2021-01-27T22:10:31+09:00
Vue.js で 親コンポーネントと同じ高さに設定する方法を考えた
背景
Vue.js で親コンポーネントと同じ高さにする。
具体的には、
height:auto(コンテンツの内容に応じて、heightが変わる。)のコンポーネントに対して、
ある特定条件を満たしている場合、透明色の色をかぶせる。と言うことが必要となりました。
height: 100%でも指定しておけば、うまくいくだろう。と思って試してみましたが、
height:100%が適用されるのは、親コンポーネントのheightが指定されている場合のみらしく、今回適用したいheight:auto` の場合には、適用できないとのこと。異なる方法で対応を検討する必要が生じました。結論
結論として、以下の方法で対応しました。
template<template> <div> <div :style="{height:fullHeight}" /> // 高さを指定する。 </div> </template>script(typescript)import {Vue, Component, Prop} from 'vue-property-decorator'; @Component({}) export default class Sample extends Vue { fullHeight:string; mounted() { this.fullHeight = this.$el.clientHeight + 'px'; // DOMから高さを取得する。 } }説明
概要
Vue.js のAPIでElementを取得して、高さを取得しています。
シンプルですが、この方法を取ることで、Elementが持つ他のプロパティを取得することも可能です。Vue.js API
- https://jp.vuejs.org/v2/api/index.html#el
Element のプロパティ
- https://developer.mozilla.org/ja/docs/Web/API/Element注意事項
mounted で取得する
createdやcomputedを使用すると、 Element(DOM) を生成する前に、Element要素にアクセスすることになるので、エラーを吐いてしまいます。時間があれば、調べた経緯とかも整理して、編集したいと思います。
- 投稿日:2021-01-27T20:12:16+09:00
vue-simple-suggestの機能を最小限で実装
概要
Vue.jsでサジェスト機能を実装したくてやり方を調べていたら、vue-simple-suggestなるライブラリがあることが分かったので使ってみる。
インストール方法は公式参照
公式GitHub→https://github.com/KazanExpress/vue-simple-suggest環境
Vue.js 2.5.17
vue-simple-suggest 1.10.3実装
全体
Form.vue<template> <div class="form-group"> <vue-simple-suggest v-model="selected" :list="getSuggestionList" :filter-by-query="true"> <input type="text" name="tag" id="tag" placeholder="タグを入力してください" autocomplete="off"> </vue-simple-suggest> </div> </template> <script> import VueSimpleSuggest from "vue-simple-suggest"; import 'vue-simple-suggest/dist/styles.css'; export default { components: { VueSimpleSuggest }, data() { return { selected: '', List:'', }; }, methods: { async getSuggestionList() { return await axios.get('/api/tagList') .then(res => this.List = res.data ) .catch((error)=>{ this.errorMsg = 'Error! Could not reach the API. ' + error console.log(this.errorMsg) }) }, } } </script>ポイント
form.vue<vue-simple-suggest v-model="selected" :list="getSuggestionList" :filter-by-query="true"> <input type="text" name="tag" id="tag" placeholder="入力してください" autocomplete="off"> </vue-simple-suggest>フォームに入力された文字がv-modelに入る
getSuggestionList()APIで引っ張ってきたlistが入る
これがないとうまくサジェストされません
<vue-simple-suggest></vue-simple-suggest>の中に<input>を実装のもポイントform.vue<script> import VueSimpleSuggest from "vue-simple-suggest"; //忘れずにインポート import 'vue-simple-suggest/dist/styles.css'; //忘れずにインポート export default { components: { VueSimpleSuggest }, data() { return { selected: '', //入力された文字を格納 List:'', //getSuggestionList()で引っ張ってきた配列を格納 }; }, methods: { async getSuggestionList() { return await axios.get('/api/tagList') .then(res => this.List = res.data ) .catch((error)=>{ this.errorMsg = 'Error! Could not reach the API. ' + error console.log(this.errorMsg) }) }, } } </script>最後に
困ったら公式ドキュメント!
- 投稿日:2021-01-27T14:50:34+09:00
Vuejsのコンポーネントについて
HTMLテンプレートと機能などをまとめることができる機能としてコンポーネントがあります。
今回は、コンポーネントの基本的な機能についてまとめてみたいと思います。コンポーネントを使う上で基本的な考え方
コンポーネント化することで何がメリットかというと
・機能ごとに区別して作成が可能
・再利用可能になり保守性が高めることができる
ことがあげられます。そして、
コンポーネントには"使う側(親)”と"使われる側(子)"と言う関係になります。
この関係を抑えることでコードの記載する内容も変わってきますので注意しましょう。グローバルコンポーネントとローカルコンポーネント
コンポーネントには2種類あります。
グローバルとローカルのコンポーネントです。
・Vueのインスタンスを複数作成した際にどのインスタンスでも適用させたい場合にはグローバルコンポーネントを使用します。
・特定のVueインスタンス内で使用したい場合にはローカルコンポーネントを使用します。定義する場合には下記のようになります。
記載方法の違いと言えばcreateApp({})で定義するか、.componentで宣言するかの違いです。グローバルコンポーネント
Vue.createApp({}).component('mydata',{ data:function(){ return{ title:'', } }, template: `<input type="text" v-model="title" />`, })ローカルコンポーネント
Vue.createApp({ component: { 'mydata': { data: function () { return { title: '', } }, template: `<input type="text" v-model="title" />`, }, }, }).mount('#app')一点注意点としては、グローバルコンポーネントを利用する場合にはJavaScriptファイルが上から読み込まれてしまうので、適用したいVueインスタンスよりも前に記述することが必要です。
Vue.component('sample1', { data() { return { testms: 'hello!' } }, template: '<div>{{ testms }}</div>' }) new Vue({ el: '#app' }) //上記でVueインスタンスが定義されたため、ここから下のコンポーネントは読み込まれない。 Vue.component('sample2', { data() { return { testms2: 'bye!' } }, template: '<div>{{ tsetms2 }}</div>' })参考:VueのComponent(コンポーネント)の書き方・使い方について解説
コンポーネント定義の内容について
コンポーネントの定義については、2つポイントを説明します。
sample.jsVue.createApp({ component: { 'mydata': { data: function () { return { title: '', } }, template: `<input type="text" v-model="title" />`, }, }, }).mount('#app')・dataプロパティの取り扱い
コンポーネント内で使用したいdataプロパティについては、定義する際にオブジェクトを返す関数である必要があります。
data: function () { //function定義 return { //return title: '', } },・templeteの取り扱い
コンポーネントのHTMLを定義するためにはtempleteを定義する必要があります。
template: `<input type="text" v-model="title" />`,ただ、現実問題としてコンポーネントが複雑になると文字列として定義するには可読性が低くなってしまいます。
そのために、templeteの内容をHTMLへ記載する方法があります。html<div id="app"> <mydata></mydata> </div> <script type="text/x-template" id="mydata-template"> <input type="text" v-model="title" /> </script>javascripttemplate: '#mydata-template',template内の要素を下記のようにscriptタグ内で定義することhtmlに記載することが可能です。
<script type="text/x-template" id="~~~~"> //templeteの内容 </script>後は、htmlのidとJavaScriptの'#~'で紐づけることで、コンポーネントのtemplete部分の可読性が上がり、記載も簡単になります。
コンポーネント自体が冗長になった時には。。
上記のsample.jsファイルのコンポーネントが冗長になり、可読性が低いと感じる時にはコンポーネントを切り離して,constで宣言してあげることで回避するのも一つの手です。
sample.js//コンポーネントを切り離す const mydataobj = { data: function () { return { title: '', } }, template: '#mydata-template', } Vue.createApp({ component: { 'mydata': mydataobj }, }).mount('#app')と言うような形でコンポーネントの概要についてまとめてみました。
コンポーネントが理解できれば後は値の受け渡しが必要ですよね。
近日中に追記したいと思います。
近日中に追記予定
- 投稿日:2021-01-27T11:59:31+09:00
Vue Apollo-compsableでqueryをシーケンシャルに実行する方法メモ
1番目のqueryのレスポンスを2番目のqueryのvariablesに渡したかった
単なる知っているか知らないかだけの問題で、良く読むと公式にも書かれていました。
ポイントは一つだけでuseQueryに渡せるオプションの中に
enabledというプロパティが存在していました。
このプロパティはデフォルトではtrueで、そのままだと即時リクエスト発行の挙動となりますが
falseに指定してやる事で、trueに変更するまでリクエストを発行しなくなります。
この仕組みを利用して、シーケンシャルにQueryを発行できるというわけです。以下の例は1つ目のqueryで文字列uuidを受け取り
その値を2つ目のqueryのvariablesに条件として指定した場合の例示です。おまけの情報としてuseQueryのレスポンスに含まれるloadingの値を利用した方が良いのではないか?
という意見もあるかと思いますが、
私自身当初はそれを試みたのですが、loadingはその名前が示す通り
現在読み込み中かという意味合いで、読み込みを開始する前はfalseとなっており
今回のような用途には利用できませんでした。自分自身enabledの存在に気がつけず、少しハマったので備忘として記録を残します。
import * as VueApolloComposable from '@vue/apollo-composable' import * as VueCompositionApi from '@vue/composition-api' import { FirstQueryDocument, SecondQueryDocument, ThirdQueryDocument } from './graphql/queries.graphql' const useFirstQueryResponse = () => { const isLoaded = ref<boolean>(false) const uuid = ref<string>('') const { onResult } = VueApolloComposable.useQuery(FirstQueryDocument) onResult((result) => { isLoaded.value = true, uuid = result?.data?.firstQueryResponse?.uuid } return { isLoaded, uuid, } } const useSecondQueryResponse = ( firstQueryLoaded: Ref<boolean>, uuid: Ref<string>, ) => { const secondQueryResponse = ref<SecondQueryResponse>([]) const isLoaded = ref<boolean>(false) const conditions = { page: ref(1), // 改ページ処理などのページ数の例 uuid, // 最初のQueryで受け取ったレスポンスの値を2つ目のQueryの条件として渡す } const variables = computed(() => ({ per: 20,// 1ページあたりに何件表示するかのパラメータの想定 page: conditions.page.value, uuid: conditions.uuid.value, })) const options = computed(() => ({enabled: firstQueryLoaded.value })) const { onResult } = VueApolloComposable.useQuery(SecondQueryDocument, variables, options) onResult((result) => { secondQueryResponse.value = result?.data?.secondQueryResponse isLoaded.value = true }) return { secondQueryResponse, isLoaded, } } const useThirdQueryResponse = (secondQueryLoaded: Ref<boolean>,) => { const thridQueryResponse = ref<thridQueryResponse>([]) const variables = {} const options = computed(() => ({enabled: secondQueryLoaded.value })) const { onResult } = VueApolloComposable.useQuery(ThirdQueryDocument, variables, options) onResult((result) => { thirdQueryResponse.value = result?.data?.thirdQueryResponse }) return { thirdQueryResponse.value = result?.data?.thirdQueryResponse } } export default defineComponent({ name: `SequentialQueryComp` setup(){ const firstQueryResponse = useFirstQueryResponse() const secondQueryResponse = useSecondQueryResponse( firstQueryResponse.isLoaded, firstQueryResponse.uuid, ) const thirdQueryResponse = useThirdQueryResponse(secondQueryResponse.isLoaded) } }) // 以下FirstQueryDocumentの参考例 import gql from 'graphql-tag' export const FirstQueryDocument = gql` query firstQuery { firstQueryResponse { uuid } } `
- 投稿日:2021-01-27T06:15:53+09:00
jQueryを知っているだけの怠惰なひとが1時間以内にVue, React, Angularを完全に理解する (コピペするだけのサンプル付)
動機とこのページの趣旨
jQuery ...もう14年モノらしい。
業務で基本的にjQueryを10年ほど利用してきたが、スキルマップ作成とやらでVue, React, Angular の経験を問われたため、知ったかぶりしたいので調べた各種資料。以下を順に読んで
index.htmlを3ファイル作るだけで1時間以内にVue, React, Angular「完全に理解した」顔をしよう。タイトルの「怠惰」はプログラマの美徳なのでお飾りフレーズとして書いてしまったが、コピペという怠惰はプログラム的には怠惰では無い、むしろ闇なので、これで一通りかじったらむしろナニモワカラナイの絶望の谷に堕ちてみることをお勧めする。そんな趣旨。時代は「Angular」「React」「Vue」の3大フレームワークに集約 らしい...
Hello Vue world のために以下を読め
さて時間がないので早速始めよう!
Vue.jsでできること8選。凄さが分かる実用例スニペット集
凄い!けれどその凄さを実感している場合ではない。
Vue.jsで Hello World!ひとまず元気に挨拶、Hello world
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <title>Title</title> </head> <body> <div id="app"> <!-- testValの内容を表示する --> {{ testVal }} </div> </body> <script> const app = new Vue({ el: '#app', data: { testVal: 'Hello World!' } }); </script> </html>早速動かせた実感を得たら以下。
Vue.jsで湯婆婆を実装してみる
公式(日本語)
Vue.js は公式を読めと随所に書いてあるので、公式サイトを読むのが最も手っ取り早く完全に理解できる。Hello React World のために以下を読め
次、React。こちらはまず。
React (JavaScript) で湯婆婆を実装してみる元気に湯婆婆
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>React 湯婆婆</title> <script src="https://unpkg.com/react/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> (() => { 'use strict'; const {useState} = React; function Yubaba() { const [name, setName] = useState(''); const newName = name.substr(Math.floor(Math.random() * name.length), 1); return ( <div> <p>契約書だよ。そこに名前を書きな。</p> <input type='text' value={name} onChange={e => setName(e.target.value)}/> <p>フン。{name}というのかい。贅沢な名だねぇ。</p> <p>今からお前の名前は{newName}だ。いいかい、{newName}だよ。分かったら返事をするんだ、{newName}!!</p> </div> ); } ReactDOM.render( <Yubaba/>, document.getElementById('root') ); })(); </script> </body> </html>ReactでHello Worldをする前に...
Facebook公式のcreate-react-appコマンドを使ってReact.jsアプリを爆速で作成する
以上で一通りセットアップはできる。ReactでHelloWorldをしてみよう!
公式(日本語)
をサラリと読もう。Hello Angular World のために以下を読め
ここまでで30分経っただろうか。もう少しだ。
AngularJSでHello World
AngularJS で Hello World元気にHello world! (3回め)
<!DOCTYPE html> <html ng-app> <head> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script> <script> var HelloWorld = { Controller: function($scope) { $scope.name = 'World'; $scope.greeting = 'Hello'; $scope.bye = function() { $scope.greeting = 'Good-bye'; }; } }; </script> <link rel="stylesheet" href="css/main.css"> <title>Hello World</title> </head> <body> <h1>AngularJS Example: Hello World</h1> <div ng-controller="HelloWorld.Controller"> Input your name → <input type="text" ng-model="name" size="20"> <hr> <p>{{greeting}} {{name}}!</p> <hr> <p><button ng-click="bye()">Bye!</button></p> </div> </body> </html>もう湯婆婆なのか何なのかわからなくなっているが、以下。
とほほのAngular入門
公式(日本語)
とほほの解説はとても心強い。で、jQueryとどう違うの?
3種類動かしてみたところでウンチクくらいは語れるようにしておこう。以下で完璧だ。
JavaScriptが辿った変遷
jQuery愛好家のためのVue.js、React入門(いずれAngularも)
jQuery から Vue.js へのステップアップ実際に実務でjQueryを置き換えられるかなんて言う顔をするなら以下。
Vue.jsとjQueryで同じ機能を作成し、コードを比較する
Vue.jsとjQueryで同じ機能を作成し、コードを比較する - その2
ReactとjQueryの比較
JavaScript: フレームワーク React/Vue/Angularについてこれで1時間以内かな?
最後に我らのjQuery
ここまでjQueryだけ知っているひとを想定読者にしたので、最後にウッカリこの記事を開いてしまった人のために、念の為jQueryについてもHello worldしておく。逆の境遇においてもこれでjQueryを完全に理解してほしい。
jQueryの基礎
頼まれてもいないのにcssのお餅付きだ。迎春なので鏡餅をCSSで作った参照。
jQuery公式については
https://jquery.com/ と、
http://semooh.jp/jquery/ がテッパンか。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Progate</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <style type="text/css"> .kagamimochi { width: 100px; text-align: center; } .mikan { width: 50px; height: 40px; background: #e88522; border-radius: 50%; position: relative; z-index: 10; display: inline-block; } .mikan::after { content: ""; width: 15px; height: 5px; background: linear-gradient(#4f9c5d 50%, #6cb576 50%); border-radius: 50%; display: inline-block; position: absolute; top: 0; right: 10px; transform: rotate(-20deg); display: inline-block; } .mochi1 { width: 80px; height: 40px; background: #fff; border: 1px solid #000; border-radius: 50%; display: inline-block; margin-top: -15px; position: relative; z-index: 5; } .mochi2 { width: 100px; height: 50px; background: #fff; border: 1px solid #000; border-radius: 50%; display: inline-block; margin-top: -20px; position: relative; z-index: 4; } .kami { width: 92px; height: 92px; background: #fff; border: 5px solid #f00; transform: rotateX(45deg) rotateZ(45deg); display: inline-block; margin-top: -75px; position: relative; z-index: 1; } </style> <script> $(function(){ $('#hide-text').click(function(){ $('#text').slideUp(); }); }); </script> </head> <body> <!-- このボタンを押すと --> <div class="btn" id="hide-text">説明を隠す</div> <div class="kagamimochi"> <div class="mikan"></div> <div class="mochi1"></div> <div class="mochi2"></div> <div class="kami"></div> </div> <!-- この表示が隠れる --> <h1 id="text">Hello, World!</h1> <script src="script.js"></script> </body> </html>これでどんどん具体的に、ナニモワカラナイを目指せそうです。すべてのJSはここからだ。Enjoy!
以上お粗末様でした。
- 投稿日:2021-01-27T00:56:22+09:00
Scoped CSSの破れ
Scoped CSSは、コンポーネント内にスタイルを閉じ込めるための便利な機能ですが、かなり困ったケースを見つけたので、既知かもしれませんが再現方法をメモっておきます。確認環境
- Vue.js
- Scoped CSSを有効にしたSingle File Component
- Slot機能は未使用
再現方法
- コンポーネントを2つ用意し、親子関係にする(以下、
ParentとChildと呼ぶ)Child側で適当なスタイル.fooを定義しておく(未使用でも構わない)Parent側で、たまたま同名の.fooを定義しておく(スタイルの内容は違うもの)Parent側で、<Child class="foo" />とする(Parent側で定義したスタイルだけが当たるハズ)Child内で定義した.fooがChildに意図せず当たるサンプル
https://codesandbox.io/s/loving-burnell-6ve2k?file=/src/components/Parent.vue
(Safariで開くとエラーになるのでChromeで)何が問題か?
Child内で定義したスタイルが、Parent側から呼び出せるChild内で定義したスタイルが、意図していない箇所に当たるParentとChildを別々に開発していると、マージした際にスタイルが崩れる場合があるとりま回避方法
Child側でスタイルを定義する際に、簡単にはマッチしないセレクタにしておく(ユニークな親子構造とか)- または
<Child class="foo" />みたいにしない備考
Scoped CSSは、技術的にはコンポーネントごとにカスタムdata属性を自動で振って、スタイルの影響範囲を閉じ込めているだけなので、Vueに限らずScoped CSSを謳っている他のライブラリでも起きてるかもしれません。
- 投稿日:2021-01-27T00:28:32+09:00
初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!
皆さんこんにちは!
今日は.envファイルについて書いていきます。
ところで皆さん、例えばバックエンドとのやり取りを行う際、URLはどういう風に書かれていますか?
axios.get('http://127.0.0.1')こんな風に書くと思います。
ただ、これを毎回コピーして持ってくるのって正直めんどくさいかと。
また、実際にサーバーにあげる時って、
http://127.0.0.1のようなローカルホストとは通信を行いませんよね???他にも、シークレットキーなど第三者に知られてはいけない情報をファイルに直接書き込むのは良くありません。
ここで、登場するのがenvファイルです!
聞き覚えのある方もいれば、全く聞いたことがないという人もいます。
全く知らなくても大丈夫!
この記事を見て、.envファイルを使いこなしていきましょう!!
一度使うと恐らくやめられないくらい便利なので、ぜひ見て下さい!
それでは順を追って説明しています。
.envと.env.productionの作成
プロジェクト直下に
.envと.env.productionを作成します。project/
┣ public/
┣ src/
.env
.env.produnctionこんな感じで作ります。
copy nul .env copy nul .env.productionMacの方は
touchで作成してください。また、コマンドプロンプトでの作業に慣れてない方は、手動で作成してもOKです。
固定値の設定
次に、固定値を以下のように定めます。
/.envVUE_APP_BACKEND_SITE_URL = 'http://127.0.0.1'/.env.productionVUE_APP_BACKEND_SITE_URL = 'https://example.com'ローカルサーバーを立ち上げてる人は一旦止めて下さい。
止めないと、.envファイルは更新されません。
名前を付けるときは、
VUE_APPから必ず始めて下さい!!!そして、これを実際に呼んでみたいと思います。
index.jsaxios.get(process.env.VUE_APP_BACKEND_SITE_URL).then(() => {})
process.envで始め、先ほど設定したキー名を付けます。これで、ローカル環境の時は
http://127.0.0.1がコールされ、実際のサーバーではhttps://example.comがコールされます。他にも、Saasを利用する際のAPIキーなどもここに保管しておくことが重要です。
簡単かつ作業効率もぐっと上がります!
また、セキュリティ面でも活躍するのでどんどん使ってください!!!
以上、「初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading









