- 投稿日:2020-01-17T22:55:45+09:00
Vue.jsでスライドショーを作成したお話
結論
- Vue.js上にスライドショーを実装するフレームワークeagle.jsを使うだけでそれっぽいスライドショーができる
- ホスティングはFirebaseで爆速アップロード
- 出来上がったサイトは これ ※レスポンシブ対応していません
- ソースはここ
発端
弊社では新入社員のサポートとして、一年間先輩社員のメンターがアサインされ、月次で状況把握(カフェーで雑談)を行っている。
年末に新入社員の親御さんに向けてお宅のお子さんこんなことやってるんすよ。という先輩からのメッセージを載せた色紙を送り付けるサプライズイベントが行われる。
これが原因で転職する人がいそう
ただメッセージを書くのはナンセンスなのでどうせならウェブアプリの形で公開してやろうと思いつき実装。
結果として謎のQRコードとここにアクセスしろというメッセージが親御さんに届いた。技術セット
- Vue.js
- eagle.js
- Firebase
eagle.jsのポイント
タグで区切るだけで1ページ作成できる
<slide :steps="1" enter="slideInDown"> <eg-transition enter="fadeInDown" leave="fadeOut"> <div v-if="step >= 1"> スライドだよ! </div> </eg-transition> </slide>ページめくりのアニメーションも柔軟に設定可能
感想
それなりに好評でした。どうせなら文字じゃなくて成果物で語りたいよね。
コンポーネントとして分離できるしちょっとした動きをつけたい時に使えそう。
- 投稿日:2020-01-17T20:36:48+09:00
VueとFirebaseと使ってAtCoderのAC数をツイートするウェブアプリケーションを作った
概要
AtCoder上でのAC数を1日1回ログインしたツイッターアカウントでツイートしてくれるdevotterというアプリケーションを作成したよというお話
リンク
スライド
Github
ソースコード(フロントエンド)
<template> <v-container pa-12 fill-height> <v-layout align-center text-center wrap> <v-flex xs12> <v-alert v-model="alert" type="error" dismissible>{{errorMessage}}</v-alert> <v-alert v-model="success" type="success" dismissible>SUCCESS!</v-alert> <v-img :src="require('../assets/thai monk.svg')" class="my-3" contain height="200"></v-img> <h1>「Devotter」</h1> <h2> その精進 <br />ツイートしませんか </h2> <br /> <h5>このアプリケーションは、1日1回AtCoderでのAC数をTwitterにツイートしてくれるアプリケーションです。</h5> <br /> <v-form> <v-text-field @change="changeField" type="text" v-model="atcoderId" v-if="login" label="AtCoderID" color="#00acee" required ></v-text-field> </v-form> <v-btn @click="signin" v-bind:disabled="isnull" v-show="login" color="#00acee" rounded >Sign In To Twitter</v-btn> <v-btn @click="logout" v-show="!login" color="#00acee" rounded>Log Out from Twitter</v-btn> </v-flex> </v-layout> </v-container> </template> <script> import firebase from "firebase"; import axios from "axios"; export default { name: "guestUserScreen", methods: { logout: function() { firebase .auth() .signOut() .then(()=>{ this.success=true; this.login=true; }) .catch(()=>{ this.alert=true; this.errorMessage="サインアウトに失敗しました。" }); }, signin: function() { var db = firebase.firestore(); var document = this.atcoderId; axios .get(this.userApi + this.atcoderId) .then(response => { this.userInfo = response; const provider = new firebase.auth.TwitterAuthProvider(); this.success = true; firebase .auth() .signInWithPopup(provider) .then(function(result) { let token = result.credential.accessToken; let secret = result.credential.secret; db.collection("users") .doc(document) .set({ accessToken: token, accessTokenSecret: secret }); }) .catch(function() { this.alert = true; this.errorMessage = "ERROR!"; }); }) .catch(() => { this.alert = true; this.errorMessage = "無効なAtCoderID名です。"; }); }, changeField: function() { this.atcoderId ? (this.isnull = false) : (this.isnull = true); } }, created: function() { firebase.auth().onAuthStateChanged(user => { if (user) { this.login = false; } }); }, data: function() { return { atcoderId: "", isnull: true, userInfo: "", userApi: "https://kenkoooo.com/atcoder/atcoder-api/v2/user_info?user=", alert: false, errorMessage: "", success: false, login: true }; } }; </script>ソースコード(バックエンド)
//import const functions = require('firebase-functions'); const admin = require('firebase-admin'); const serviceAccount = require('./atcontributter-firebase-adminsdk-5p86i-6e55085ef8.json'); const axios = require('axios'); const twitter = require('twitter'); const cors = require('cors')({ origin: true }); const decycle = require('json-decycle').decycle; const retrocycle = require('json-decycle').retrocycle; //initialize const adminConfig = JSON.parse(process.env.FIREBASE_CONFIG); adminConfig.credential = admin.credential.cert(serviceAccount); admin.initializeApp(adminConfig); //create instance const firestore = admin.firestore(); const bucket = admin.storage().bucket(); //global let consumerKey; let consumerKeySecret; let accessToken; let accessTokenSecret; let receiveAc; let receiveNewAcString; let receiveNewAc; let senderAc; let uploadPath; let problemCount; let newProblemCount; //main exports.devotterCronJob = functions.region('asia-northeast1').https.onRequest(async (request, response) => { cors(request, response, () => { response.set('Access-Control-Allow-Origin', 'http://localhost:5000'); response.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST'); response.set('Access-Control-Allow-Headers', 'Content-Type'); }); try { //firebase storageから昨日のAtCoderAC情報が入ったjsonを取得する const receiveJson = await bucket.file('ac.json').download(); receiveAc = JSON.parse(receiveJson); //kenkooooさんのAtCoderAPIを使って本日のAtCoderAC情報をaxiosで取得 const receiveNewJson = await axios.get('https://kenkoooo.com/atcoder/resources/ac.json', { headers: { 'accept-encoding': 'gzip' } }); receiveNewAcString = JSON.stringify(receiveNewJson, decycle()); receiveNewAc = JSON.parse(receiveNewAcString, retrocycle()).data; //firestoreからtwitter apiのconsumerKeyとconsumerKeySecretを取得 const getConsumerKeyQuerySnapShot = await firestore.collection('api').doc('keys').get(); consumerKey = getConsumerKeyQuerySnapShot.data().consumerKey; consumerKeySecret = getConsumerKeyQuerySnapShot.data().consumerKeySecret; //firestoreからツイートに必要なユーザー情報を取得 const getUserDataQuerySnapShot = await firestore.collection('users').get(); getUserDataQuerySnapShot.forEach(async document => { try { //取得したUserQuerySnapShotからaccessTokenとaccessTokenSecretを取得 const getAcceseTokenQuerySnapShot = await firestore.collection('users').doc(document.id).get(); accessToken = getAcceseTokenQuerySnapShot.data().accessToken; accessTokenSecret = getAcceseTokenQuerySnapShot.data().accessTokenSecret; //昨日のjsonデータ中のfirestoreに登録しているユーザーのAC数を抽出 for (const element in receiveAc) { if (receiveAc[element].user_id === document.id) { problemCount = receiveAc[element].problem_count; break; } } //今日のjsonデータ中のfirestoreに登録しているユーザーのAC数を抽出 for (const element in receiveNewAc) { if (receiveNewAc[element].user_id === document.id) { newProblemCount = receiveNewAc[element].problem_count; break; } } //twitterクライアントにkeyを登録 const client = new twitter({ consumer_key: consumerKey, consumer_secret: consumerKeySecret, access_token_key: accessToken, access_token_secret: accessTokenSecret }); //twitter apiを使ってツイート let tweetMessage = '今日の' + document.id + 'さんのAC数は' + (newProblemCount - problemCount) + 'でした。\n#devotter' client.post('statuses/update', { status: tweetMessage }, (error) => { if (!error) { response.status(200).send('success!'); } else { response.status(500).send(error); } }); //今日取得したAC数をfirebase storageにアップロード senderAc = JSON.stringify(receiveNewAc); uploadPath = 'ac.json'; bucket.file(uploadPath).save(senderAc); } catch (error) { response.status(500).send(error); } }); } catch (error) { response.status(500).send(error); } });
- 投稿日:2020-01-17T20:30:01+09:00
【Next.js/Nuxt.js】Nuxt.jsで言うfetch()やasyncData()のNext.js版→getInitialProps()
Next.jsのgetInitialProps()
Next.jsでも、Nuxt.jsで言うところのfetch()またはasyncData()にあたる処理を見つけました。
getInitialProps(context)
です。詳しくは、getInitialProps | 公式サイト(英語)に記載があります。
画面表示前にfetchができるとのことです。※もし認識に誤りがある場合はコメント頂けるとありがたいです。
説明
getInitialProps()の引数は、Context Object | 公式サイト(英語)です。
jsxの
this.props
にgetInitialProps()
でreturnしたオブジェクトのプロパティが入ってきます。以下は、上記の公式サイトより簡素に書き直したものです。
import React from 'react' class Page extends React.Component { // 引数「context」については上述 static async getInitialProps(context) { return { name: 'Hanako' } } render() { // this.propsにプロパティ「name」が入ってくる return <div>Name is {this.props.name}</div> } } export default Page参照URL
- 投稿日:2020-01-17T20:30:01+09:00
【Next.js/Nuxt.js】Nuxt.jsで言うasyncData()のNext.js版→getInitialProps()
Next.jsのgetInitialProps()
Next.jsでも、Nuxt.jsで言うところのasyncData()にあたる処理と思われる処理を見つけました。
getInitialProps(context)
です。詳しくは、getInitialProps | 公式サイト(英語)に記載があります。
画面表示前にfetchができるとのことです。※もし認識に誤りがある場合はコメント頂けるとありがたいです。
説明
getInitialProps()の引数は、Context Object | 公式サイト(英語)です。
jsxの
this.props
にgetInitialProps()
でreturnしたオブジェクトのプロパティが入ってきます。以下は、上記の公式サイトより簡素に書き直したものです。
import React from 'react' class Page extends React.Component { // 引数「context」については上述 static async getInitialProps(context) { return { name: 'Hanako' } } render() { // this.propsにプロパティ「name」が入ってくる return <div>Name is {this.props.name}</div> } } export default Page参照URL
- 投稿日:2020-01-17T17:04:28+09:00
plugin実践編:vue-scrolltoでスムーズにスクロール
vue-scrolltoとは
ページ内リンク(アンカーリンク)で
スクロールしながら指定場所に飛びます?♀️?
【公式】
https://www.npmjs.com/package/vue-scrollto比較: vue-scrollto未使用
味気ない?
nuxt-linkを使うと1回しか機能しない
a hrefでやれば解決するけど…
どうせならnuxt-link使いたいじゃないindex.vue<template> <div class="page"> <nuxt-link to="#anchor"> 下に飛ぶ </nuxt-link> <div id="anchor"> とべた! </div> </div> </template>比較: vue-scrollto使用
スクロールバーに着目?
スルスルっとスクロール?
通常と違い、何回でもとべますね。
コードはstep順に記載していきます✍️step1: インストール
ターミナル$ npm i vue-scrollto
filepages/ --| sample.vue plugins/ --| vue-scrollto.js nuxt.config.jsstep2: /pluginsにjsファイルを追加
【vue-scrollto.js】
・Nuxt.jsでpluginを使用時の書き方
https://ja.nuxtjs.org/api/configuration-plugins/・オプションは公式の
Options項目で確認できます。duration: スクロール継続時間
easing: 速度の緩急
offset: 遷移後の位置調整
少しだけ上に設定すると⭕️easingに関してはここが参考になります!
https://note.com/ritar/n/n5e8ed0e07917vue-scrollto.jsimport Vue from 'vue' import VueScrollTo from 'vue-scrollto' Vue.use(VueScrollTo, { duration: 700, easing: [0, 0, 0.1, 1], offset: -100, })step3: nuxt.config.jsのpluginsに記載
nuxt.config.jsplugins: [ '~plugins/vue-scrollto' ],vue-scrollto.jsに記載したoptionsは
modulesに記載することもできます。
vue-scrollto公式のNuxt.js項目で確認できます。ただvue-scrollto.jsに書いた方が
まとまって分かりやすいと思います?nuxt.config.jsmodules: [ ['vue-scrollto/nuxt', { duration: 700 }], ],step4: テンプレートでページ内リンクを作成
【sample.vue】
・nuxt-link toの中にv-scroll-toを入れる
toが2個あって変な感じがしますが
どちらも必要なので削らないように✏️
・リンク先を''で囲むこの書き方は
vue-scrollto公式の
Usage項目で確認できます。sample.vue<template> <div class="page"> <nuxt-link v-scroll-to="'#anchor'" to > 下にとぶ </nuxt-link> <div id="anchor"> とべた! </div> </div> </template>これで完成です??
ローカルver
一応ローカルの書き方も。
vue-scrollto.jsをまるごとコピペで⭕️
オプションもdata内に書けば適応されます。sample.vue<template> <div class="page"> <nuxt-link v-scroll-to="'#anchor'" to > 下にとぶ </nuxt-link> <div id="anchor"> とべた! </div> </div> </template> <script> import Vue from 'vue' import VueScrollTo from 'vue-scrollto' Vue.use(VueScrollTo, { duration: 700, easing: [0, 0, 0.1, 1], offset: -100, }) export default { data () { return { options: { el: '#anchor', onDone: (el) => console.log(el) } } } } </script> <style lang="scss" scoped> #anchor { margin-top: 1000px; } </style>今まで週3で投稿していましたが
来週から月水の2回に変更致します?
自社サービスに手をつけているためです。
お楽しみに?落ち着いたらまた週3になるかも?
- 投稿日:2020-01-17T16:23:17+09:00
Vuetifyでボトムナビゲーション上にフローティングボタンをつける
はじめに
Vuetify を使うと優れた UI をとても簡単に作れますが、少し特殊なことをやろうとするとつまづくことが多いです。
本記事では、モバイルアプリでよく見かける、ボトムナビゲーションからはみ出たアイツ をつける方法を記載します。アイツの一般的な名称は、多分「オーバーラップ FAB」なんですが。
出来上がるもの
開発環境
- Windows10
- Node.js 12.13.1
- Atom 1.42.0
- vue/cli 4.1.1
- Vue 2.6.1
- Vuetify 2.0.3
アイコンをインストールする
とりあえずアイコンを用意しましょう。今回は、Fontawesome を使います。
環境に合わせてご自由にどうぞ。npm install @fortawesome/fontawesome-free -Dmain.jsimport Vue from 'vue' import App from './App.vue' import vuetify from './plugins/vuetify'; import '@fortawesome/fontawesome-free/css/all.css' /* この行を追加する */ボトムナビゲーションを追加する
v-app ディレクティブの後ろにコードを追加します。
App.vue<template> <v-app> <!-- 省略 --> <!-- ここからボトムナビゲーション --> <v-bottom-navigation dark grow > <v-btn> <span>Home</span> <v-icon>fas fa-home</v-icon> </v-btn> <v-btn> <span>Edit</span> <v-icon>fas fa-edit</v-icon> </v-btn> </v-bottom-navigation> <!-- ここまでボトムナビゲーション --> </v-app> </template>フローティングボタンを用意する
Vuetify では v-speed-dial ディレクティブを使うと、子ボタンを内包したフローティングボタンが使えます。
App.vue<template> <v-app> <!-- 省略 --> <!-- ここからフローティングボタン --> <v-layout justify-center row wrap> <v-speed-dial v-model="fab" > <template v-slot:activator> <v-btn color="red darken-2" dark fab > <v-icon v-if="fab">mdi-close</v-icon> <v-icon v-else>mdi-plus</v-icon> </v-btn> </template> <v-btn fab dark small color="orange" > <v-icon>mdi-note</v-icon> </v-btn> <v-btn fab dark small color="red" > <v-icon>mdi-camera</v-icon> </v-btn> <v-btn fab dark small color="indigo" > <v-icon>mdi-file</v-icon> </v-btn> </v-speed-dial> </v-layout> <!-- ここまでフローティングボタン --> <!-- 省略 --> <script> export default { name: 'App', components: { }, data: () => ({ fab: false, /* フローティングボタン のために追加 */ }), }; </script>あとはフローティングボタンを、ボトムナビゲーションに入れたら完成ですね
完成、そんなことはなかった
すみません、どうやら簡単にはできないようです。
ボトムナビゲーションにフローティングボタンを入れると、とても残念な結果になります。一応ソースも置いておきます。
App.vue<template> <v-app> <!-- 省略 --> <v-bottom-navigation dark grow > <v-btn> <span>Home</span> <v-icon>fas fa-home</v-icon> </v-btn> <!-- ここからフローティングボタン --> <v-layout justify-center> <v-speed-dial v-model="fab" > <template v-slot:activator> <v-btn color="red darken-2" dark fab > <v-icon v-if="fab">mdi-close</v-icon> <v-icon v-else>mdi-plus</v-icon> </v-btn> </template> <v-btn fab dark small color="orange" > <v-icon>mdi-note</v-icon> </v-btn> <v-btn fab dark small color="red" > <v-icon>mdi-camera</v-icon> </v-btn> <v-btn fab dark small color="indigo" > <v-icon>mdi-file</v-icon> </v-btn> </v-speed-dial> </v-layout> <!-- ここまでフローティングボタン --> <v-btn> <span>Edit</span> <v-icon>fas fa-edit</v-icon> </v-btn> </v-bottom-navigation> </v-app> </template>ボタンのスタイルを !important で無理やり書き換えれば多少の抵抗はできそうですが、時間のムダです。
もちろん僕はやりました
やめておいた方が吉ですボトムナビゲーションとフローティングボタンは分離する
ボトムナビゲーション内にフローティングボタンは置けません
ボトムナビゲーションは画面の下部に固定されるので、ついでにフローティングボタンもボトムナビゲーションの中に含めたくなりますが、それではうまくいきません。
そこで、v-footer ディレクティブを用意し、その中に、フローティングボタンとボトムナビゲーションを入れます。...というわけで、v-footer ディレクティブの中に、フローティングボタンとボトムナビゲーションを入れます。
また、v-footer には fixed プロパティを付与し、画面下部に固定します。App.vue<template> <v-app> <!-- 省略 --> <v-footer padless fixed > <v-row> <v-col cols="12" class="pa-0" > <v-layout justify-center> <v-speed-dial v-model="fab" > <template v-slot:activator> <v-btn color="red darken-2" dark fab class="float" > <v-icon v-if="fab">mdi-close</v-icon> <v-icon v-else>mdi-plus</v-icon> </v-btn> </template> <v-btn fab dark small color="orange" > <v-icon>mdi-note</v-icon> </v-btn> <v-btn fab dark small color="red" > <v-icon>mdi-camera</v-icon> </v-btn> <v-btn fab dark small color="indigo" > <v-icon>mdi-file</v-icon> </v-btn> </v-speed-dial> </v-layout> </v-col> <v-col cols="12" class="pa-0" > <v-bottom-navigation dark grow > <v-btn> <span>Home</span> <v-icon>fas fa-home</v-icon> </v-btn> <v-btn> <span>Edit</span> <v-icon>fas fa-edit</v-icon> </v-btn> </v-bottom-navigation> </v-col> </v-row> </v-footer> </v-app> </template>仕上げ
フローティングボタンをボトムナビゲーションに重ねます。
まず、フローティングボタンを配置している row の高さを 0 にします。App.vue<template> <v-app> <!-- 省略 --> <v-footer padless fixed > <v-row> <v-col cols="12" class="pa-0" style="height: 0px;" <!-- 高さを 0 にする --> > <!-- 省略 --> </v-col> </v-row> </v-footer> </v-app> </template>次に、フローティングボタンを上方向にズラすために、スタイルを適用します。
App.vue<template> <v-app> <!-- 省略 --> <template v-slot:activator> <v-btn color="red darken-2" dark fab class="float" <!-- 位置調整用のスタイルを適用 --> > <v-icon v-if="fab">mdi-close</v-icon> <v-icon v-else>mdi-plus</v-icon> </v-btn> </template> <!-- 省略 --> </v-app> </template> <script> export default { name: 'App', components: { }, data: () => ({ fab: false, }), }; </script> <style> .float { -webkit-transform: translateY(-20%); /* フローティングボタンを上部にズラす */ transform: translateY(-20%); } </style>まとめ
Vuetify は色々と捗り過ぎて素晴らしいですが、単純にやり過ぎるとドツボにハマります。
やはりどのようなフレームワークを使おうが CSS の基本的な知識(特にFlexbox)は大事ですね。あと、もっと楽な方法あったらコメントいただけると嬉しいです!
- 投稿日:2020-01-17T16:05:49+09:00
Vue.jsとVuexで編集formのdateにstateをコピーして同期させずに使いたい
参考
[Vue] Storeのstateをコンポーネントのローカルstateにコピーする
How can I clone data from Vuex state to local data?結論
this.formData = JSON.parse(JSON.stringify(this.stateData))問題提起
Vue.jsとVuexを使っていて編集formを作っている時に
Vuexのstateから取り出したデータをcomponentのdataオプションに渡して
dataをv-modelでbindして編集してみた。すると
dataのオブジェクトをbindしたはずが
stateのオブジェクトも一緒に同期して編集されてしまった。…後で追記する。
- 1つずつ代入していたエピソードとコード
- 実際の利用例
- 解説
- 投稿日:2020-01-17T14:53:04+09:00
PlayCanvasでVueJS+OnsenUI環境作ってみた
PlayCanvasでVueJS+OnsenUI環境作ってみた
【ハンズオン】Vue.jsをPlayCanvasの中で使って3DモデルビュワーなWebページを作る
で公式(?)のPlayCanvasでのVueJS環境構築を教えていただいたので、VueJS+OnsenUI環境もできるのではないかと思ってやってみました。
全体
こんな感じでRootにindex.jsを追加して、属性にindex.htmlとOnsenUIで必要なCSS2つセットしてます。
index.js/*jshint esversion: 6, asi: true, laxbreak: true*/ const Index = pc.createScript('index'); Index.attributes.add("VueHtml", {type:"asset", assetType:"html"}); // 登録したhtmlを取得 Index.attributes.add("OnsenUICss", {type:"asset", assetType:"css", array: true}); // initialize code called once per entity Index.prototype.initialize = function() { // var self = this; // this書き換え const wrapper = document.createElement("div"); // div作成 wrapper.classList.add("wrapper"); // 作成したdivにwrapperというclass名を指定 wrapper.innerHTML = this.VueHtml._resources[0]; // 事前に登録していたhtmlをwrapperに流し込み document.body.appendChild(wrapper); // bodyにwrapperを追加 // CSS const cssElement = document.createElement("style"); this.OnsenUICss.forEach((css) => { cssElement.innerHTML = css._resources[0]; document.head.appendChild(cssElement); }); // VueJS const app = new Vue({ // Vue呼び出し el: '#app', // id名がappの要素を参照 template: '#main', data() { // 使用するdataを登録 return({ }); }, methods: { // イベントハンドラ作成 } }); // canvasを取得 const canvas = document.getElementsByTagName("canvas")[0]; // canvasを取得 canvas.classList.add("pcCanvas"); // canvasにclass名を指定 // canvasを移動 const canvasInsert = document.getElementById('canvas_insert'); canvasInsert.appendChild(canvas); }; // update code called every frame Index.prototype.update = function(dt) { }; // swap method called for script hot-reloading // inherit your script state here // Index.prototype.swap = function(old) { }; // to learn more about script anatomy, please read: // http://developer.playcanvas.com/en/user-manual/scripting/index.html<template id="main"> <v-ons-page> <!-- PlayCanvasのcanvas移動先 --> <div id="canvas_insert"></div> <p style="text-align: center"> <v-ons-button @click="$ons.notification.alert('Hello World!')"> Click me! </v-ons-button> </p> </v-ons-page> </template> <div id="app"></div>onsenui.cssはOnesnUI用CSS
onsen-css-components.min.cssはOnesnUIコンポーネント用CSSをコピペして作成
ExternalScriptには
- https://cdn.jsdelivr.net/npm/vue
- https://unpkg.com/onsenui/js/onsenui.min.js
- https://unpkg.com/vue-onsenui@2.6.2/dist/vue-onsenui.jsを指定したら
- 投稿日:2020-01-17T11:02:07+09:00
【Nuxt.js】 asyncDataとfetchって結局どう使うの?
TL;DR.
asyncData
とfetch
はコンポーネントがロードされる度に呼び出され、外部からデータを取得する際に使用するacyncData
は外部から取得したデータをページコンポーネントのみで使用する場合に使用するfetch
は取得したデータをVuexのstoreに格納して使用したい場合に使用するそもそもacyncDataとfetchって何?
- acyncDataとfetchはページコンポーネントの初期化前に呼び出される関数のこと。
- SSR(サーバーサイドレンダリング)、ページ遷移前にも呼び出される。
- 第1引数に
context
(オブジェクト)を取るので、クエリパラメータなどの値にアクセスして処理を行うことができる。
context
内で利用できるキーに関してはAPI基本Contextを参照- 一方で、コンポーネントのインスタンスが作成される前に処理が実行されてしまうため、
this
でコンポーネントのインスタンスにアクセスすることができない。aycncDataの使用法
acyncData
は外部からデータを取得し、ページコンポーネントへ直接セットすることを目的として使用される。asyncData
によって返される値はコンポーネントのテンプレートからアクセスすることで使用できる。<template> <div>{{ hoge }}</div> </template> <script> export default { async asyncData({ app }) { const doc = await app.$firebase.firestore().collection('hoge').get() return { hoge: doc.data().hoge } } } </script>fetchの使用法
fetch
は外部から取得したデータをVuexのstoreに格納して使用することを目的として使用される。acyncData
とは違ってコンポーネントに値を直接セットすることができない。<template> <div>{{ $store.state.hige }}</div> </template> <script> export default { async fetch({ app, store }) { const doc = await app.$firebase.firestore().collection('hige').get() store.commit('setHige', doc.data().hige) } } </script>結局どう使うの?
SSRとCSRのライフサイクルは
plugin
→middleware
→validate
→asyncData→fetch
→beforeCreate
の順で処理が実行されているので、タイミング的にはどちらを使用しても影響範囲は変わらないように思える。asyncDataの場合、そのページで直接apiを叩いてデータを取得することを目的としているため、Vuexを使用して共通化している関数などを使用しない。したがって、storeに格納しないデータ(アカウントデータやテーマなどの情報はlocalStrageやsessionStrageかから取得することを想定)を使用してデータ取得を行いたい場合に使用するイメージ。また、asyncData内でstoreを使用することはできるが、gettersを動かしてページにデータを持ってくる必要がある。
fetchの場合、Vuexのmutationsを直接叩いてstoreに値をセットして、ページから直接storeを参照するために使用する。したがって、取得したデータの加工や表示方法をstoreに入れる前に全て行う必要がある。つまり、一旦storeに入れてしまったデータを編集して表示することが難しい。取得したデータを完全にページで成形して使用する場合(マスターデータや表示のみで使用する一覧系データなど)に使用するイメージ。また、アプリケーションないで使用しているデータや処理方法がVuexに大きく依存している場合、かなり有効になる。
- 投稿日:2020-01-17T09:12:18+09:00
Vue.jsのドキュメントを初心者なりに読んで解釈してみた②
前書き
前回は「Vue インスタンス」まで読み進めました。
いよいよ今回は Vue.js の基本になるであろうテンプレート構文を読んでいきます。途中、「ドキュメントのサンプルコードだけじゃ分からん……」となった箇所で、自分なりに理解するためにコードを書いています。いうまでもなくガチ初心者が書いたコードなので、エレガントじゃないとか色々あると思いますが、そこは目を瞑ってください。
ただ、間違った解釈をしていたり、不足していることがあったときは、優しくマサカリをコメント欄に置いていっていただけますと幸いです。
テンプレート構文
ドキュメントの順序通り書き下したいところですが、「ディレクティブって何ぞや?」と乱舞する片仮名が気になって仕方ないので、簡単な説明の部分だけ先にさらってしまいましょう。
ディレクティブ(雑な要約)
ディレクティブとは、
v-
から始まる特別な属性です。ディレクティブの仕事は、属性値の式が変化したときに、リアクティブに副作用を DOM に適用することです。
「はじめに」の条件分岐とループのチュートリアルが分かりやすい例ですね。v-if
ディレクティブが、seen
という式の真偽値をもとに<p>
要素を削除したり挿入していました。ディレクティブについては後でまた書き下します。
用語が分かったところで、改めてドキュメントの上から順に読んでいきましょう。展開
テキスト
もっとも基本的な形は Mustache構文 という、{{}}
を使ったテキスト展開です。宣言的レンダリングの最初のチュートリアルに出てきましたね。
チュートリアルで体験した通り、対応するオブジェクトのプロパティの値に置き換わります。プロパティの値を変更すると、それに応じて更新されます。
v-once
を使うことで、値を変更しても更新を行わず、一度だけ展開することができます。
ただし、v-once
が使われていない部分では影響を受けます。ドキュメントのコードだとピンと来なかったので、実験的にコードを書いてみました。
index.html<div id="app_A" v-once> {{ message }} </div> <div id="app_B"> {{ message }} </div>
{{message}}
を突っ込んだ2つの div があります。div#app_A
にはv-once
を指定し、div#app_B
には何も指定していません。
両方にmessage
の内容が表示されるようにコードを書きます。index.jsvar msg = { message: 'Hello Vue!' } var app_A = new Vue({ el: '#app_A', data: msg }) var app_B = new Vue({ el: '#app_B', data: msg })結果Hello Vue! Hello Vue!無事に同じテキストが表示されました!
では、コンソールでapp_A.message
の内容をtest
に変えてみましょう。結果Hello Vue! test
div#app_A
にはv-once
が指定されているので、message
の内容が変更されても「Hello Vue!」が表示されたままです。一方、div#app_B
はmessage
の更新に応じて表示が変わりました。
念のためコンソールでapp_A.message
の中身を見てましょう。test
になっていますね。ドキュメントで述べられている「同じノードのあらゆる他のバインディングが影響を受ける」というのは、こういうことだと思います。多分。
生のHTML
{{}}
に入れたデータは、HTMLではなくプレーンなテキストとして扱われます。ドキュメントではHTMLコードと結果しか書いておらず、肝心の
rawHtml
に何が入っているのか一見わかりづらいですが、<span style="color:red">This should be red.</span>
という文字列が入っています。
前述の通り、{{}}
で展開されたデータはプレーンなテキストとして扱われるので、文字列がそのまま出ています。
v-html
ディレクティブを使用したほうは、プレーンなHTMLテキストとして解釈されるので、「This should be red.」という赤字が表示されます。Vue は、文字列ベースのテンプレートエンジンではないので~の部分はよくわかりませんでした。取り敢えず今は、みだりに使わないようにするという程度の理解で進めます。ちゃんと説明できるようになったら加筆します。
属性
{{}}
は、HTML属性の内部で使えません。NG例<!-- dynamicId にIDとして指定したい文字列を入れた想定 --> <div id="{{dynamicId}}"></div> <div id={{dynamicId}}></div> <!-- dynamicId に id="hoge" みたいな文字列を入れた想定 --> <div {{dynamicId}}></div>代わりに、
v-bind
を使います。ドキュメントに書いてありますが、比較のため再掲してみましょう。OK<div v-bind:id="dynamicId"></div>
v-bind:
と書いた後に、使いたいHTML属性を書いて、データを指定する!って感じで書けば良さそうですね。HTML属性が存在していることを
true
やfalse
といった真偽値属性で示す場合、v-bind
の挙動は少しだけ変わります。
ドキュメントのコードを元に実験してみましょう。まずはtrue
の場合です。index.html<button id="app" v-bind:disabled="isButtonDisabled">Button</button>index.jsvar app = new Vue({ el: '#app', data: { isButtonDisabled: true } })描画結果<button id="app" disabled="disabled">Button</button>次に、
isButtonDisabled
の値をfalse
にしてみます。index.jsvar app = new Vue({ el: '#app', data: { isButtonDisabled: false } })描画結果<button id="app">Button</button>
disabled
属性がまるごと出力されませんでしたね。
このように、真偽値属性がfalse
、null
、undefined
の場合は、HTML属性を含まず要素が描画されます。JavaScript 式の使用
Vue.js は、データバインディング(=データと対象を結び付けて反映する)内部で、JavaScript の式を使うことができます。
ドキュメントのコードを使って、確かめていきます。index.html<div id="app"> <p>{{ number + 1 }}</p> <p>{{ ok ? 'YES' : 'NO' }}</p> <p>{{ message.split('').reverse().join('') }}</p> <div v-bind:id="'list-' + id">sample</div> </div>index.jsvar app = new Vue({ el: '#app', data: { number: 1, ok: false, message: 'ABC', id: 'a' } })結果<p>2</p> <p>NO</p> <p>CBA</p> <div id="list-a">sample</div>HTML 側で書いた JavaScript の式が、結果に反映されました。
注意したいのは、どんな式でも動作するわけではないことです。
ドキュメントで挙げられているような「変数の宣言」「if文」といった単一の式ではないもの(要はその一文だけでは完結しない式、という認識でいいのかな……)は対象外です。ディレクティブ
先に書いた通り、
v-
から始まる特別な属性を指します。v-for
を除くディレクティブ属性値は、単一の JavaScript 式を期待し、その式が変化したとき、リアクティブに副作用を DOM に適用します。引数
ディレクティブ名の後にコロンを表記して、引数をとるディレクティブもあります。
例えば、v-bind
は、HTML 属性を引数として、リアクティブにその値を更新します。v-on
の場合は、click などの受け取りたいイベント名を引数として取ります。基本的な書き方v-ディレクティブ名:引数="値"動的引数
Vue.js バージョン2.6.0から、[]
で囲むことで、JavaScript 式をディレクティブの引数に使うことができるようになりました。動的引数を使う書き方v-ディレクティブ名:[値1]="値2"ただし、いくつかの制約があります。
値 の制約
要はnull
と string(文字列)しか使えない、ということだと思います。真偽値とか入ってても、ねえ……ってことでしょう。多分。
null
は明示的にバインディングを削除するのにつかわれる、とドキュメントにありますが、例となるコードが無いのでちょっと理解ができませんでした。式 の制約
構文上の制約があります。例えばスペースとか引用符なんかは、HTML 属性としては不正な文字なので、使ってはいけません。それらを含まない式を使うか、置き換えが必要です。
また、HTML ファイルに直接書く場合、ブラウザが属性名を強制的に小文字にしてしまいます。小文字変換後のプロパティがあれば動きますが、後々管理に苦労することが容易に想像つくので、キャメルケースなど大文字を混ぜるようなことは原則として避けましょう(というか、禁止という認識で良い問題ないと思います)。ダメな例<!-- スペースや引用符が含まれている --> <a v-bind:['foo' + bar]="value"> ... </a> <!-- この someAttr は someattr に変換されるので、someattr プロパティが無いと動かない --> <a v-bind:[someAttr]="value"> ... </a>修飾子
修飾子(Modifier といいます)は、.
で表記された特別な接尾語です。ディレクティブv-
を、特別な方法でバインドすることを示します。
サンプルコードがよくわからないので、v-on
やv-model
の説明を読むときに改めて復習しようと思います。省略記法
v-
接頭辞のうち、最もよく使われるディレクティブであるv-bind
やv-on
には特別な省略記法が提供されています。v-bindの省略記法<!-- 完全な構文 --> <a v-bind:href="url"> ... </a> <!-- 省略記法 --> <a :href="url"> ... </a> <!-- 動的引数の省略記法 (2.6.0 以降) --> <a :[key]="url"> ... </a>v-onの省略記法<!-- 完全な構文 --> <a v-on:click="doSomething"> ... </a> <!-- 省略記法 --> <a @click="doSomething"> ... </a> <!-- 動的引数の省略記法 (2.6.0 以降) --> <a @[event]="doSomething"> ... </a>修飾子がつくときはどうするんだろう?と思ったのですが、ちょうど先程の
v-on
の修飾子のページを見ていた時に見つけました。普通に後ろにくっつけて良さそうです。<button @click.ctrl="onClick">A</button>次回予告
基本的な記法が分かって楽しくなってきた半面、ドキュメントの咀嚼が難しくなってきました……。
順番通り、次は算出プロパティを頑張って読んでいこうと思います。
- 投稿日:2020-01-17T01:08:43+09:00
WordPressの固定ページをでVue.jsを使うには
お断り:今回は新規構築サイトの「固定ページ」でVue.jsを使用した際のメモです。
構築済みサイトの場合、既存の記事に悪影響がないことは保障しかねます。なぜやろうと思ったのか?
jQueryしか使えない筆者が、Vue.jsの学習を兼ねて簡単なWebアプリを構築しました。
テーマを利用してデザインの手間は省くため WordPressにVue.jsを組み込むに至りました。初学者なので間違ったことをお伝えしていたらご指導お願いします?
必要な手順
WordPressで使うといっても、普通のWebページに導入するのとほとんど変わりません。
①サーバー上にNode.jsを導入する
レンタルサーバーでなければ特に苦労なく導入できると思います。
参考記事:Samurai Blog様私の場合レンタルサーバー(ColorfulBox)をお借りしていたので少し苦労しました。
レンタルサーバーの始め方 様の情報通り作業すれば導入できました。②WordPressでVue.jsを読み込む
公式サイトからダウンロードし、サーバーに配置します。
詳細な方法は「WordPress javascript 読み込み」などで検索すればたくさん出てくるので、
ニーズに合った方法を選択してください。ここまでで準備は完了です。
③HTMLをコーディング
普通にHTMLを記述すると自動で成形されてしまうので、
カスタムHTMLブロックを利用するとストレスフリーです。固定ページ(カスタムHTML)<div id="app"> {{ message }} </div>④スクリプトをコーディング
私の場合、固定ページごとに個別のスクリプトを仕込みたかったので「Scripts n Styles」というプラグインで対応しました。
scriptvar app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })このプラグインを利用する場合、スクリプトの出力先を「head」か「body」で選べるのですが「body」に出力してください。
「head」だと動きませんでした。
動作確認
Scripts n StylesはUpdate Scriptsを押下し、
固定ページを公開してアクセスしましょう。
{{ message }} ではなく Hello Vue! と表示されていますね!
お疲れ様でした!!
- 投稿日:2020-01-17T01:08:43+09:00
WordPressでVue.jsを使うには
お断り:今回は新規構築サイトの「固定ページ」でVue.jsを使用した際のメモです。
構築済みサイトの場合、既存の記事に悪影響がないことは保障しかねます。なぜやろうと思ったのか?
jQueryしか使えない筆者が、Vue.jsの学習を兼ねて簡単なWebアプリを構築しました。
テーマを利用してデザインの手間は省くため WordPressにVue.jsを組み込むに至りました。初学者なので間違ったことをお伝えしていたらご指導お願いします?
必要な手順
WordPressで使うといっても、普通のWebページに導入するのとほとんど変わりません。
ただ、Googleで軽く調べても「WordPressでVue.jsを使う」みたいな記事が出てこず、
(もしかして使えない…?)とか思ってしまったので情報を残しておきたいと思いました。①サーバー上にNode.jsを導入する
レンタルサーバーでなければ特に苦労なく導入できると思います。
参考記事:Samurai Blog様私の場合レンタルサーバー(ColorfulBox)をお借りしていたので少し苦労しました。
レンタルサーバーの始め方 様の情報通り作業すれば導入できました。②WordPressでVue.jsを読み込む
公式サイトからダウンロードし、サーバーに配置します。
詳細な方法は「WordPress javascript 読み込み」などで検索すればたくさん出てくるので、
ニーズに合った方法を選択してください。ここまでで準備は完了です。
③固定ページの作成(HTMLをコーディング)
普通にHTMLを記述すると自動で成形されてしまうので、
カスタムHTMLブロックを利用するとストレスフリーです。固定ページ(カスタムHTML)<div id="app"> {{ message }} </div>④固定ページの作成(スクリプトをコーディング)
私の場合、固定ページごとに個別のスクリプトを仕込みたかったので「Scripts n Styles」というプラグインで対応しました。
scriptvar app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })このプラグインを利用する場合、スクリプトの出力先を「head」か「body」で選べるのですが「body」に出力してください。
「head」だと動きませんでした。
動作確認
Scripts n StylesはUpdate Scriptsを押下し、
固定ページを公開してアクセスしましょう。
{{ message }} ではなく Hello Vue! と表示されていますね!
お疲れ様でした!!