- 投稿日:2020-05-23T23:14:28+09:00
【備忘録】HTMLのテンプレート
単独のHTMLのテンプレートです。Vue.js版とかも下に書いてますが、CDN利用なのでNode.jsで使う場合とは異なるのでご注意を。
時々バージョンアップしたり追加したりします。ソースコード
汎用版
特にフレームワークを使用しないパターン。
index.html<!doctype html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta http-equiv='X-UA-Compatible' content='IE=edge' /> <meta name='viewport' content='width=device-width, initial-scale=1' /> <title>...</title> <link rel="stylesheet" href="./style/basic.css"> </head> <body> <script src="./script/basic.js"></script> </body> </html>basic.csshtml, body { width: 100%; height: 100%; margin: 0px; overflow: hidden; }basic.js"use strict"Vue.js(CDN)版
index.html<!doctype html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta http-equiv='X-UA-Compatible' content='IE=edge' /> <meta name='viewport' content='width=device-width, initial-scale=1' /> <title>...</title> <link rel="stylesheet" href="./style/basic.css"> </head> <body> <div id='app' style="display:none"> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="./script/basic.js"></script> <script> const vm = new Vue({ el: '#app', data: () => ({ }), methods: { }, mounted() { document.getElementById('app').style.display = "block"; }, }); </script> </body> </html>Vue.js + ElementUI(CDN)版
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta http-equiv='X-UA-Compatible' content='IE=edge' /> <meta name='viewport' content='width=device-width, initial-scale=1' /> <title>...</title> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <link rel="stylesheet" href="./style/basic.css"> </head> <body> <div id='app' style="display:none"> <el-container> <el-header> </el-header> <el-main> </el-main> <el-footer> </el-footer> </el-container> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="https://unpkg.com/element-ui@2.13.2/lib/index.js"></script> <script src="https://unpkg.com/element-ui@2.13.2/lib/umd/locale/ja.js"></script> <script src="./script/basic.js"></script> <script> ELEMENT.locale(ELEMENT.lang.ja); const vm = new Vue({ el: '#app', data: () => ({ }), methods: { }, mounted() { document.getElementById('app').style.display = "block"; }, }); </script> </body> </html>チラシの裏(読まなくてもいい余談)
仕事でもプライベートでもモックや検証用なんかにHTMLファイルを仕立てるんですが、毎回ウェブで拾ってくるか、既存のソースから関係ないところを削除して使っていたので、備忘録も兼ねて初投稿です。
今後も、なにかパーツに使えそうなものとかを備忘録として残していこうかと思います。
- 投稿日:2020-05-23T23:11:01+09:00
Vue.js 動的引数名について
はじめに
vue.jsのドキュメントをコードを書きながら復習してたら詰んだのでメモ
動的引数とは
vue.js バージョン 2.6.0 から、角括弧で囲むことで JavaScript 式をディレクティブの引数に使うこともできます(引用: vue.jsドキュメント )
hoge.html<html> <head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> {{ message }} <a v-on:click="doSomething">click</a> <!-- clickするとdoSomethingを呼ぶ --> <a v-on:[eventName]="doSomething">click</a> <!--ここに書かれている[eventName]が動的引数 --> <!-- eventNameの値をdataで設定することができる 今回でいうと値はclickとなる--> <!-- なので 実際のページで見ると上と下どちらも同じように展開される。--> </div> <script> var app = new Vue({ el: '#app', data: { message: "Hello Vue!", eventname: "click" //こちらでeventnameの値を設定 }, methods: // clickすると呼び出させれる doSomething: function () { this.message = this.message.split('').reverse().join('') } } }) </script> </body> </html>コード内での説明になってしまいましたが
これだけが動的引数ではありません。ただ今回はこちらで詰んでしまったので
こちらのみの説明になります。今回詰んだ箇所
hoge.html<!-- 省略 --> <a v-on:[eventName]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventName: "click" } </script>今回はv-bindに設定されているeventNameという命名規則で詰みました。
ドキュメントだとそのままキャメルケースのままdataで設定すると値が参照できると書かれているのですが実際にコンソールを見ると
[Vue warn]: Property or method "eventname" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See:とでてきて
まあ何度もスペルミスしていないか確認したが間違ってなさすぎて逆に困った。コンソールをずっと見てたら
Property or method "eventname"キャメルケースで書いたはずなのにキャメルケースじゃ無くなっていることに気づいた。
そこで3つのパターンを検証してみた。htmlとjs側どちらもキャメルケースではない書き方。
hoge.html<!-- 省略 --> <a v-on:[eventname]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventname: "click" } </script>結果
コンソールエラーなし
htmlのみキャメルケース
hoge.html<!-- 省略 --> <a v-on:[eventName]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventname: "click" } </script>結果
コンソールエラーなし
jsのみキャメルケース
hoge.html<!-- 省略 --> <a v-on:[eventname]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventName: "click" } </script>結果
コンソールエラーあり
という結果でした。
3つの検証を元に動的引数の参照元のデータはキャメルケースでは参照できないことがわかりました。バージョンでの検証
僕の知識がなさすぎの原因とも思い
動的引数が適応された2.6.0を指定して、動かしてみたが結果は変わらなかった。おわりに
今回動的引数についていろいろ検証してみましたが
確証的な結果ではないのでわかるかたいらっしゃいましたら
ご教授お願いいたします。参照記事
- 投稿日:2020-05-23T23:11:01+09:00
Vue.js 動的引数について
はじめに
vue.jsのドキュメントをコードを書きながら復習してたら詰んだのでメモ
動的引数とは
vue.js バージョン 2.6.0 から、角括弧で囲むことで JavaScript 式をディレクティブの引数に使うこともできます(引用: vue.jsドキュメント )
hoge.html<html> <head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> {{ message }} <a v-on:click="doSomething">click</a> <!-- clickするとdoSomethingを呼ぶ --> <a v-on:[eventName]="doSomething">click</a> <!--ここに書かれている[eventName]が動的引数 --> <!-- eventNameの値をdataで設定することができる 今回でいうと値はclickとなる--> <!-- なので 実際のページで見ると上と下どちらも同じように展開される。--> </div> <script> var app = new Vue({ el: '#app', data: { message: "Hello Vue!", eventName: "click" //こちらでeventNameの値を設定 }, methods: // clickすると呼び出させれる doSomething: function () { this.message = this.message.split('').reverse().join('') } } }) </script> </body> </html>コード内での説明になってしまいましたが
これだけが動的引数ではありません。ただ今回はこちらで詰んでしまったので
こちらのみの説明になります。今回詰んだ箇所
hoge.html<!-- 省略 --> <a v-on:[eventName]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventName: "click" } </script>今回はv-bindに設定されているeventNameという命名規則で詰みました。
ドキュメントだとそのままキャメルケースのままdataで設定すると値が参照できると書かれているのですが実際にコンソールを見ると
[Vue warn]: Property or method "eventname" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See:とでてきて
まあ何度もスペルミスしていないか確認したが間違ってなさすぎて逆に困った。コンソールをずっと見てたら
Property or method "eventname"キャメルケースで書いたはずなのにキャメルケースじゃ無くなっていることに気づいた。
そこで3つのパターンを検証してみた。検証
htmlとjs側どちらもキャメルケースではない書き方。
hoge.html<!-- 省略 --> <a v-on:[eventname]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventname: "click" } </script>結果
コンソールエラーなし
htmlのみキャメルケース
hoge.html<!-- 省略 --> <a v-on:[eventName]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventname: "click" } </script>結果
コンソールエラーなし
jsのみキャメルケース
hoge.html<!-- 省略 --> <a v-on:[eventname]="doSomething">click</a> <script> //---省略 data: { message: "Hello Vue!", eventName: "click" } </script>結果
コンソールエラーあり
という結果でした。
3つの検証を元に動的引数の参照元のデータはキャメルケースでは参照できないことがわかりました。バージョンでの検証
僕の知識がなさすぎの原因とも思い
動的引数が適応された2.6.0を指定して、動かしてみたが結果は変わらなかった。おわりに
今回動的引数についていろいろ検証してみましたが
確証的な結果ではないのでわかるかたいらっしゃいましたら
ご教授お願いいたします。追記
しっかりドキュメントを下まで読んでみるとあった(ごめんなさい)
in-DOM テンプレート (HTML ファイルに直接書かれるテンプレート) を使う場合、ブラウザが強制的に属性名を小文字にするため、キー名を大文字にするのは避けるべきです:(引用: vue.jsドキュメント )
とのことです。大文字にするのは避けましょう。
スネークケースでも一応通りました。参照記事
- 投稿日:2020-05-23T21:25:23+09:00
Vue.jsでメニュー 切り替え メモ
切り替えメニューを作りたい!
飲み物メニューボタンを押したときは、飲み物のメニューだけを表示
食べ物メニューボタンを押したときは、食べ物のメニューだけを表示
したい!scriptタグにメソッドとデータを準備
selectedMenu
というデータと
changeMenu
というメソッドを作る。
selectedMenu
の値は空文字でもよい。その場合初期状態では非表示になる。<script> export default { components: {}, data: function () { return { selectedMenu: "1" //選択しない状態で何も表示したくないときは空文字でもよい。 } }, methods: { changeMenu: function (num) { this.selectedMenu = num } } } </script>templateタグの中を作っていきます
それぞれのメニューボタンを押すと、
changeMenu()
が動いて、selectedMenu
の中身が変わる。
v-if
を使って、selectedMenu
の中身に応じて、表示・非表示を切り替えるようにする。
selectedMenu === 1にする凡ミスをよくやった。
型に注意!!
<template> <button v-on:click="changeMenu('1')">飲み物メニュー</button> <button v-on:click="changeMenu('2')">食べ物メニュー</button> <ul v-if="selectedMenu==='1'"> <li>ビール</li> <li>レモンサワー</li> <li>お冷</li> </ul> <ul v-if="selectedMenu ==='2'"> //v-else-ifでもよい <li>ハンバーグ</li> <li>カレー</li> <li>お茶漬け</li> </ul> </template>ついでに、
どのメニューを選択しているかわかりやすいように、menuボタンに赤色でもつけておく。
v-bind
を使って、条件が合うとbutton
タグにactive
クラスが追加されるようにする。
buttonがactiveクラスになったときだけ、赤くなるようになった!<button v-on:click="changeMenu('1')" v-bind:class="{'active':selectedMenu ==='1'}" >飲み物メニュー</button> <button v-on:click="changeMenu('2')" v-bind:class="{'active':selectedMenu ==='2'}" >食べ物メニュー</button> <style> button.active { color: red; } </style>参考
- 投稿日:2020-05-23T20:31:49+09:00
VuePress の検索ボックスを日本語対応させる
ここの左上にある検索ボックスの話
https://vuepress.vuejs.org/ちょっと引っかかったのでメモ。
TL;DR
Vue Press のバージョンを v1.5.0 に上げる。
ちょっと解説
VuePress v.1.4.0 の段階では、非アスキー文字での検索ができなかった
https://github.com/vuejs/vuepress/issues/2242下記のプルリで対応されて
https://github.com/vuejs/vuepress/pull/2283v1.5.0 でひっそりとリリースされた。
https://github.com/vuejs/vuepress/compare/v1.4.0...v1.5.0
ありがたい。
- 投稿日:2020-05-23T18:18:53+09:00
Vue + Apollo + GraphQLを使っていてmutationの後にUIが更新されない場合
Apolloを使っていてmutationを使ってデータの更新をした場合、updateを呼んでキャッシュを更新する必要がありました。ドキュメントに書いていたけど見つけるまでハマったのでメモ。
updateRelationship(relationship, variables) { this.$apollo.mutate({ mutation: updateCompanyUserRelationship, variables: variables, // updateを呼んであげる update: (store, {data: {todo}}) => { const data = store.readQuery({query: userQuery, variables: {company_id: this.currentCompany.id}}); store.writeQuery({ query: userQuery, variables: { company_id: this.currentCompany.id }, data }); }, }).then(() => { this.$notify({ group: 'foo', title: 'ユーザー情報を更新しました。', }); this.$refs['modal'].hide(); this.refresh(); }); },
- 投稿日:2020-05-23T16:52:57+09:00
Vue.js prototypeに登録したpluginのユニットテストを書く
Vue.js で plugin のテストを書いた時のメモ。prototype に登録したプラグインって、どうやって Jest から呼べばいいんだろうってちょっと悩んだ。最終的に localVue.use() してからlocalVue.prototype.$hoge() って実行すればちゃんと Jest からでも実行できた。
テスト対象コード
例えば、HTTP ステータスコードによって処理を行う処理があったとする。本来であればテストしやすいように install とは別ファイルでロジックを管理するべきなんだろうけど。元がこうなっていたので、このコードを保ったままユニットテストを書く。
export const MyPlugin = { install(Vue) { Vue.prototype.$myPlugin = (statusCode: number) => { switch (statusCode) { case 401: /* 処理省略 */ break; case 500: /* 処理省略 */ break; default: /* 処理省略 */ break; } }; } };テストコード
テストコードはこんな感じ。vue-test-utils の createLocalVue でインスタンスを作成して、 use() でプラグインを適用。その後 prototype から $myPlugin を直接呼べば良い。
import { createLocalVue } from "@vue/test-utils"; import { MyPlugin } from "my-plugin.ts"; const localVue = createLocalVue(); localVue.use(MyPlugin); describe("MyPlugin", () => { it("引数が401の場合", () => { localVue.prototype.$myPlugin(401); }); it("引数が500の場合", () => { localVue.prototype.$myPlugin(500); }); });Vue のテストコードといえば shallowMount を使ってコンポーネントのユニットテストをよく書いていたけど、いざプラグインのテストを書こうと思ったら、なかなか思い付かず。vue-test-utils は便利。
- 投稿日:2020-05-23T16:04:43+09:00
Vueディレクティブの省略記法【初心者向け】
Vueディレクティブの省略記法をよく忘れるので、メモとして、残しておきます。
ディレクティブ 省略記法 例 v-text {{...}} <p>{{message}}</p> v-on @ <a @click="onclick"> </a> v-bind : <a :href="url"> </a> v-slot # <template #header> </template>
- 投稿日:2020-05-23T13:57:40+09:00
Using 1 worker with 2048MB memory limit をどうにかする
はじめに
Vue.js や Nuxt.js で TypeScript を使って開発したプロジェクトをビルドすると、以下のような記述を見る事があります。
Using 1 worker with 2048MB memory limitVue.js + TypeScript のビルド時
Nuxt.js + TypeScript のビルド時
最近のバージョンではこのメッセージは見かけなくなりましたけど少し前までは出ていました。
これは何?
これは読んで字の如く TypeScript の型チェックを実行するのに最大こんだけのメモリを使いますぜと言っているわけですが、この 2048MB と言う数字はどこから来ているのか。
これは、この型チェックプロセスを担当する fork-ts-checker-webpack-plugin さんが初期値として持っている値です。
最近の Nuxt.js でこのメッセージが出なくなったのは、同梱されているこのプラグインのバージョンが 3.x 系から 4.x 系に変わったからであるようです。2048MB をどうにかする
型チェックごときに貴重なメモリを 2GB もおごってやるのって剛毅すぎない?もう少しささやかな量で満足してもらえない?と言う場合、あるいは 2GB とかみみっち事言ってるんじゃない!欲しいなら 2TB くらいくれてやるわ!と言う場合に、どこをどうすればこの数字を弄れるのかを解説します。
Vue.js + TypeScript の場合
vue.config.js
で、Webpack の設定をいじる事ができます。
以下のようにchainWebpack
のところに追記してやります。(ファイルがなければ作成)
試しに制限値を512MB
にしてみます。vue.config.jsmodule.exports = { // ...省略 chainWebpack: (config) => { config.plugin("fork-ts-checker").tap((args) => { args[0].memoryLimit = 512; return args; }); }, // ...省略 };512MB に変わってますね!
上の
512
の部分を好きに変えれば良いわけですが、まあ小さすぎてもダメでしょうし、2
とか3
とかでうまく行く気はしませんね。
プロジェクトの具合によって程良い数字を見つけて貰えればと思います。Nuxt.js + TypeScript の場合
Nuxt.js の場合は、
create-nuxt-app
でプロジェクトを始めればnuxt.config.ts
と言うファイルがあるはずですので、そこに追記します。nuxt.config.tsexport default { // ...省略 typescript: { typeCheck: { memoryLimit: 512 }, ignoreNotFoundWarnings: true, }, // ...省略 };これでビルドすると・・・
こちらも 512MB の制限が有効になりました。
ビルドオプションについては Nuxt TypeScript のドキュメント にもざっくりと記載がありますが、設定できる値等細かい話はプラグイン側のドキュメントを見なさいよと言うスタンスですね。
そもそもなんでこんな事をやることになったのか
Nuxt.js + TypeScript で作ったプロジェクトを EC2 で動かそうとした時に、ビルドがいつまで経っても終わらなくて何じゃこれってなったのがきっかけでした。
で、よくよく見たらどうもこのUsing 1 worker with 2048MB memory limit
が怪しいぞ、と。t2.micro ってメモリ 1GB しかないのに 2GB とか要求されたらそらいつまでも終わらないわけだよ、と。
じゃあこの2048MB
をどうにかしてやらなきゃいけないけど、ググっても意外と情報転がってませんね、と。
あんまりこんな事で困る人自体がいないせいかとも思ったわけですが、日本語でこの辺に言及している記事も見当たらないので記録として書いておこうかな、と思った次第です。この記事が同じ疑問にぶち当たった誰か(とか数ヶ月後の自分とか)の役に立てばと思います。
参考
- 投稿日:2020-05-23T12:53:40+09:00
Nuxt.jsをVue.jsに解体するTips(Axios編)
背景
- Nuxt.jsで動いているSPAアプリをVue.jsに解体してほしい的な話があり、部分部分をTipsとして投稿します。
- APIはLaravel(6.x)です。なおLaravelとNuxt.jsは同一リポジトリです(なぜ)
- Nuxt.jsは2.9。Vueは2.6.10
- 今回はAxios編です。
Vue.jsでthis.$axiosを使えるようにPluginを作る
resources/js/plugin/axios.jsimport axios from "axios" const AxiosPlugin = {} AxiosPlugin.install = function (Vue) { Vue.prototype.$axios = axios; } export default AxiosPlugin;あとはPluginを読み込むだけで、this.$axiosが使えます。
resources/js/app.jsimport Vue from "vue"; import router from '~/router' import App from '~/App.vue' import AxiosPlugin from '~/plugin/axios'; Vue.use(AuthPlugin) new Vue({ router: router, render: h => h(App), }).$mount('#app')Nuxt.jsでのonRequestやonErrorを使いたい場合
Nuxt.js Axios ModuleのonRequestやonErrorを使いたい時があると思います。
その時はinterceptorsを使います。
resources/js/plugin/axios.jsimport axios from "axios" const AxiosPlugin = {} AxiosPlugin.install = function (Vue) { Vue.prototype.$axios = axios; Vue.prototype.$axios.interceptors.response.use( function (response) { console.info(response) return response; }, function (error) { console.error(error) return Promise.reject(error); } } export default AxiosPlugin;参考
関連リンク
- 投稿日:2020-05-23T12:26:30+09:00
カーソルキーでプルダウンを選択する
始めに
よくセレクトボックスで操作感をよくするためにキーボードでも選択ができるようにしているサイトがありますが、具体的にどうやっているかをまとめました。
実装方法
端的にいえば、focus処理をJS側で泥臭く設定しています。もうちょっとなんとかならないのかなと思いましたが、これがスタンダードなやり方な気がしました(他にやり方があれば教えて欲しいです)。
具体的にはtabindex="0"
を設定してfocusできる要素にして、後はキーイベントを拾ってそれに応じて前や後の要素をfocusさせます。
実装イメージは以下の通りです。実装イメージ<template lang="pug"> .select-list template(v-for="option in $props.options") //- tabindexをセットして、keydownイベントでキー操作をハンドリングする .select-list__item( :key="option.id" ref="elItems" tabindex="0" @click="onSelect(option.id)" @keydown="onItemKeyDown" ) | {{ option.text }} </template> <script> /** キーボードの入力コード */ const KEY_CODES = { ENTER: 13, UP: 38, DOWN: 40, }; Vue.component('SelectBox', { data() { return { focusIndex: 0, }; }, methods: { focusItem(index) { this.$data.focusIndex = _.clamp(index, 0, this.$refs.elItems.length - 1); const elItem = this.$refs.elItems[this.$data.focusIndex]; elItem.focus(); }, onItemKeyDown(event) { event.preventDefault(); event.stopPropagation(); // Enterキー: クリックイベントを発火させて、クリックと同じ挙動にする if (event.keyCode === KEY_CODES.ENTER) { const elItem = this.$refs.elItems[this.$data.focusIndex]; elItem.click(); } // ↑キー: 一つ上の要素をフォーカスさせる if (event.keyCode === KEY_CODES.UP) { this.focusItem(this.$data.focusIndex - 1); return; } // ↓キー: 一つ下の要素をフォーカスさせる if (event.keyCode === KEY_CODES.DOWN) { this.focusItem(this.$data.focusIndex + 1); return; } }, }, }); </script>実際に実装したサンプルコードは以下になります。
See the Pen カーソルキーでプルダウンを選択する by wintyo (@wintyo) on CodePen.
検索+バーチャルスクロールの場合
検索ボックスがあってそこから↓キーで項目にfocusする方法も同じようにJSでハンドリングすればいいですが、項目数が多くてバーチャルスクロールで実装した場合はかなりの鬼門でした。。。
ここではvue-virtual-scrollerのRecycleScrollerを使った際の実装方法について書いていきます。focus対象の要素はquerySelectorで探す
何番目とかで見つけられないので、クラスを付与してそのクラスを探すようにしました。ここで注意なのは再利用しているということで使われていない要素に昔の情報が残っていることがあるので、キチンとactiveなものにだけクラスをつけるようにします。
実装イメージ<template lang="pug"> RecycleScroller( ref="scroller" :items="_filteredOptions" :itemSize="40" keyField="id" v-slot="{ item, active }" ) .select-item( tabindex="0" :class="{ [`js-active-item-${item.id}`]: active }" @click="onSelect(item.id)" @keydown="onItemKeyDown" ) | {{ item.text }} </template> <script> Vue.component('SelectBox', { methods: { focusItem(index) { if (!this.$refs.scroller) { console.warn('scroller not found'); return; } this.$data.focusIndex = _.clamp(index, 0, this._filteredOptions.length - 1); const option = this._filteredOptions[this.$data.focusIndex]; // RecycleScrollerにあるエレメントから対象のDOMを探す const elItem = this.$refs.scroller.$el.querySelector(`.js-active-item-${option.id}`); if (!elItem) { console.warn('focus item lost!'); return; } elItem.focus(); }, }, }); </script>検索ボックスから項目に移動する際は上にスクロールしてからにする
バーチャルスクロールで一番上の要素が無い場合があるのでスクロールしてからフォーカスします。
scrollToItem
はドキュメントには書かれてなかったのですが、Issueには書かれてました(結構危ないかも。。。)
https://github.com/Akryum/vue-virtual-scroller/issues/180スクロールしてからフォーカスif (event.keyCode === KEY_CODES.DOWN) { // 上にスクロールしてからフォーカスする this.$refs.scroller.scrollToItem(0); window.setTimeout(() => { this.focusItem(0); }, 10); }要素がblurされるたびにfocusし直す
使いまわしていくので、中身が更新されてfocusが外れてしまう場合があります。これをどうしようか色々検討したのですが、最終的にはblurされたらfocusし直すやり方が一番focusをキープできました。ただしこれでも上手くfocusできない時がありました。。。そもそもスクロールしているとfocus対象の要素が存在しない時もありますし、これが限界のような気がしました。
検討メモ
却下された他の方法についても記載しておきます。
RecycleScrollerのupdateイベントをみて更新する
updateイベントは更新後だと思っていましたが、どうやら違うような気がしていて、updateイベント後にフォーカスし直しても外れてしまうことが多々ありました。setTimeoutを挟んで一定時間置いてからfocusするという方法もなくは無いですが、それだと不安定なので却下となりました。サンプルコード
以上の実装をしたサンプルは以下の通りです。
See the Pen カーソルキーでプルダウンする(バーチャルスクロール版) by wintyo (@wintyo) on CodePen.
終わりに
以上がカーソルキーでプルダウンを操作する方法でした。バーチャルスクロールについてはキー操作させるのはかなり難しく、中途半端な感じになってしまいました。。。vue-selectというライブラリはhover時もfocusする挙動になっていて、これにしていれば途中でfocusが外れてもそこまで気にならないのかなとか思ってきました。
ググり力が足りないだけかもしれませんが、意外とこういった記事が見当たらなかったので誰かのお役に立てれば幸いです。
- 投稿日:2020-05-23T12:03:37+09:00
wh.imで自作ゲームを登録するまで
はじめに
こんにちは。普段は情報系の学科で大学生をしている者です。
この記事に紹介されている、wh.imというサービスの立ち上げに関わっているのですが、その一環でwh.im上で楽しめるゲームを開発しました。
このサービスの特徴として、誰でもゲームを投稿 できます!そのやり方を知っていただきたく、記事を書きますので、興味を持った方はぜひゲーム開発を試してみてください!
簡単なアプリ開発のためのドキュメントはありますが、やや不親切な部分があるので詳しく説明します。ゲーム登録するまでの流れ
実際にwh.imにゲームを登録するまでの流れは次のようになります。
- ゲームを作る
- デプロイする
- テスト環境にゲームを登録し、テストしていただく
- 本番環境にゲームを登録
- 審査が通ったら、公開完了!!ゲームの開発環境
まず、ゲームの開発環境として、wh.imのミラーサイトであるsbx.wh.imというサイトがあります。sbxはサンドボックスの略です。ここで部屋に入室すると、ゲームに「開発用アプリ 8080」というのがあります。ここを起動することで
localhost:8080
で起動しているゲームをテストすることができます。localhost:3001
のものもありますので、お好みの方をご利用ください。
まずは実際に動かしてみる
Vue.js版で簡単にデータベースとの通信ができるnpmパッケージ
whim-client-vue
をご用意しました。Vue.js以外をご利用したい場合は、下のpostMessageをご利用いただくことになります。React版も作りたいと思っておりますのでお待ちください。$ npm install -g vue-cli # or yarn global add vue-cli $ vue create [appname]ここで色々設定しますが、whim-client-vueで使うので、Vuexは必ず入れるようにしてください。他はお好みで大丈夫です。
$ cd [appname] $ npm install whim-client-vue # or yarn add whim-client-vueで準備完了です。
$ npm run serve # or yarn serveでサーバーを立ち上げてください。これで開発環境での起動ができるようになります。
その後、次のようなファイルを追加します。src/main.jsimport Vue from "vue"; import App from "./App.vue"; import Vuex from "vuex"; import whimClientVue from "whim-client-vue"; Vue.use(Vuex); const store = new Vuex.Store(); Vue.config.productionTip = false; Vue.use(whimClientVue, { store }); new Vue({ store, render: h => h(App) }).$mount("#app");ここからはお好きなように開発できますが、じゃんけんやNGワードゲームのように中央と4分割された各プレーヤーの上に画面を表示させるためには、次のような構成にするのがおすすめです。
. ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── components │ │ ├── main │ │ │ └── Index.vue │ │ └── player │ │ └── Index.vue │ ├── main.js │── vue.config.js └── yarn.lock画面中央にある表示は
src/components/main
内に、各プレイヤーの画面はsrc/components/player
内に実装していくのがいいと思います。src/App.vue<template> <div id="app"> <Main class="main" /> <Player v-for="user in $whim.users" :key="user.id" class="box" :class="`pos${user.positionNumber}`" :displayUser="user" /> </div> </template> <script> export default { name: "App", components: { Main: () => import("@/components/main/Index"), Player: () => import("@/components/player/Index") } }; </script> <style lang="scss" scoped> .main { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; background: rgba(256, 256, 256, 0.7); z-index: 1; border-radius: 10px; } .box { width: 50%; height: 50%; position: absolute; text-align: center; &.pos1 { top: 0%; left: 0%; } &.pos2 { top: 0%; left: 50%; } &.pos3 { top: 50%; left: 0%; } &.pos4 { top: 50%; left: 50%; } } </style>ここまでで、ゲームの実装の下準備は完了です!ここまでのコードにplayerやmainの簡単な画面を加えたものはこちらに置いておきます。
デプロイする
Netlify、GitHub Pages、Firebase Hosting など、お好きな環境でデプロイしてください。個人的には、GitHub連携できるNetlifyがおすすめです。GitHubにpushするたびにbuild&deployできる、CI環境が簡単につくれます。
サンドボックス環境でのゲーム登録の仕方
次のように登録します。
まずdeveloper.sbx.wh.imにアクセスします。すると、Googleのログインが求められるのでログインをお願いします(現在、アプリの登録にはGoogleアカウントが必要です)。
そして、新規アプリをクリックし必要事項を記入します。
そうすれば登録完了です。
僕たちがチェックし問題なければ、(日中であれば)数時間以内にゲームを遊べるようにしたいと思います。審査完了時にはGmailで連絡を入れさせていただきます。こちらはテスト環境なのでバグが含まれていても認可します。
sbx.wh.imの「遊び場」へ行くと、あなたのゲームが登録されているはずです。本番環境への登録
本番環境への登録も上と同様にしてできます。
developer.wh.imにアクセスし同じ手順で完了です。
審査の結果も同様にGmailでご連絡致します。ゲーム作りの方法
具体的なゲーム作りの方法は別の記事にじゃんけんを例にして説明
する予定です。
記事を公開しました。よろしくお願いします。Vue.js以外でのゲーム作りについて
先ほど説明した方法では、すべてのデータベース操作は
whim-client-vue
に隠蔽されているのですが、Vue.js以外では、以下の方法を用います(Reactについてはライブラリを公開したいと思っています)。
ゲーム作りで不可欠となる利用者どうしでのデータのやり取りのやり方をご説明します。技術としてはJavaScriptのpostMessage
を利用しています。
記法ですが、window.parent.postMessage({hogehoge}, document.referer);とします。wh.imではiframeを利用しているので、
window.parent
でwh.im本体の画面から通信できます。document.referer
を用いることで安全性を高めています。
詳細は開発者ドキュメントをご覧ください。終わりに
次の記事として、実際に公開されているじゃんけんの実装を説明した記事もご用意しました。そちらもぜひ読んでみてください!
- 投稿日:2020-05-23T11:39:19+09:00
vuexfireでbindFirestoreRefすると、idのenumerable属性がfalseになる問題
備忘録を兼ねて
問題点
Vuexfireのドキュメンテーションに以下の記述があります。
Any document bound by Vuexfire will retain it's id in the database as a non-enumerable, read-only property.
bindしたデータをそのままviewから呼び出すのであれば、enumerable属性の真偽に関わらず
data.id
でidプロパティにアクセスできるので問題が生じません。しかし、Object.entries等にObjectを渡した際は検出してくれません。vuex.jsconst db = firebaseApp.firestore(); state = { data: null } // -> { value: 'hoge', id: 'fuga' } // idはenumerable: falseとして保存されている actions = { bindData: firestoreAction(async ({ bindFirestoreRef }) => { return bindFirestoreRef('data', db.collection('hoge')); }), }index.jsfor (let [key, value] of Object.entries(this.$store.state.data)) { console.log(`${key}: ${value}`); // -> { value: 'hoge' } }この仕様は、例えばObjectをcomposition-API等に通した際などに問題となり得ます。
main.vuesetup(props, context) { const data = ref(context.root.$store.state.data); console.log(data.value) // -> { value: 'hoge' } }解決法
さて、VuexfireのAPIReferenceに次の記述があります
options can contain
serialize
: a function to provide a custom serialization strategy when a document from firebase is set on the Vue instance. This allows to customize the id key, to transform data, etc.さらにVuefireのReferenceを辿ると次のように記述されています
options.serialize
: The function receives a DocumentSnapshot
as its first argument and is expected to return a plain object to be set on the Vue Instance. Here is the default function that is used when no override is provided:これに従って
serialize
関数を定義し、callbackとしてbindFirestoreRef
に渡すとenumerable
属性がtrue
になってくれます。vuex.jsconst serialize = (snapshot: firestore.DocumentSnapshot) => { return Object.defineProperty(snapshot.data(), 'id', { value: snapshot.id, enumerable: true, }); }; actions = { bindData: firestoreAction(async ({ bindFirestoreRef }) => { return bindFirestoreRef('data', db.collection('hoge'), { serialize }); }) }main.vuesetup(props, context) { const data = ref(context.root.$store.state.data); console.log(data.value) // -> { value: 'hoge', id: 'fuga' } }
- 投稿日:2020-05-23T11:00:05+09:00
Vue.jsで作る!自動保存するToDoリスト~その2~Bootstrap編
初めに
前回の
Vue.jsで作る!自動保存するToDoリスト~その1~の続きです。
今回はBootstrapを使用して、前回作成したToDoリストの見た目を装飾していこうと思います。目次
・このパートでの完成イメージ
・Bootstrapの記述
・classの解説
・まとめこのパートでの完成イメージ
Bootstrapの知識が既にある人は中をいじって自己流で綺麗にしてください。
Bootstrapの記述
index.html<div class="m-4" id="app"> <!-- v-modelはこのinputをVueとバインディングさせる役割 --> <input class="ml-4 form-control col-sm-11 border-dark" placeholder="ここにtodo入力" v-model="message"> <div class="row m-4"> <!-- @click=""の中はVueで記述するメソッド(@はv-on:の省略です) --> <button class=" col-sm-6 btn btn-primary btn-lg" @click="add_item()">追加</button> <button class="col-sm-6 btn btn-danger btn-lg" @click="all_del_item">全削除</button> </div> <div class=" border container-fluid mb-1" v-for="(item,idx) in items"> <!-- v-for( 配列, index(配列内の要素に振られた添字)を(item,idx)と表している ) --> <ul><li class="border-left"> {{ item }} <button class="col-sm-3 float-right btn btn-warning " @click="del_item( idx )"> 削除 </button> </li></ul> </div> </div>クラス名が一気に増えましたね!次にこのクラスでどう変化するかを解説します!
classの解説
<!-- * <div class="m-4" id="app"> * m-4 @margin: 4; * <input class="ml-4 form-control col-sm-11 border-dark" placeholder="ここにtodo入力" v-model="message"> * ml-4 @margin-left: 4; * form-control @text系にこのclassをつけるだけで多少良い見た目になる * col-sm-11 @画面幅を12個に分けた分の11を使って表示する(sm:タブレットサイズ以下になったら縦並び、または余白を無視する) * border-dark @dark色のborderをつける(そのまま) * <div class="row m-4"> * row @グリッドシステムで要素を分割する時の親要素につける * m-4 @margin: 4; * <button class=" col-sm-6 btn btn-primary btn-lg" @click="add_item()"> * col-sm-6 @画面幅を12個に分けた分の6を使って表示する(sm:タブレットサイズ以下になったら縦並び、または余白を無視する) * btn @Bootstrapで用意されたボタンのデザインになる * btn-primary @ボタンの色をprimaryにする * btn-lg @ボタンの大きさLargeにする * <button class="col-sm-6 btn btn-danger btn-lg" @click="all_del_item"> * col-sm-6 @画面幅を12個に分けた分の6を使って表示する(sm:タブレットサイズ以下になったら縦並び、または余白を無視する) * btn @Bootstrapで用意されたボタンのデザインになる * btn-danger @ボタンの色をdangerにする * btn-lg @ボタンの大きさLargeにする * <div class=" border container-fluid mb-1" v-for="(item,idx) in items"> * border @全方位にborderをつける * container-fluid @画面サイズに合わせて流動的に変わる * mb-1 @margin-bottom: 1; * <li class=" border-left" > * border-left @左にborderをつける * <button class="col-sm-3 float-right btn btn-warning " @click="del_item( idx )"> * col-sm-3 @画面幅を12個に分けた分の3を使って表示する(sm:タブレットサイズ以下になったら縦並び、または余白を無視する) * float-right @float: right;右に寄せる * btn @Bootstrapで用意されたボタンのデザインになる * btn-warning @ボタンの色をwarningにする * -->まとめ
実はBootstrapを模写以外で記述するのは初めてです。
ですが自分で書くことによってより一層理解が深まって一歩成長できたと思います!
この記事を見てくださったあなたの成長を応援させていただきます!!!
- 投稿日:2020-05-23T10:04:17+09:00
Nuxt.jsをVue.jsに解体するTips(Vuex編)
背景
- Nuxt.jsで動いているSPAアプリをVue.jsに解体してほしい的な話があり、部分部分をTipsとして投稿します。
- APIはLaravel(6.x)です。なおLaravelとNuxt.jsは同一リポジトリです(なぜ)
- Nuxt.jsは2.9。Vueは2.6.10
- 今回はVuex編です。
まとめ
- nuxt/store/index.jsを解体する場合、
namespacedはfalse
- nuxt/store/hoge.jsを解体する場合(index.js以外の場合)、
namespacedはtrue
nuxt/store/index.jsを解体する場合
Nuxt.jsの場合
Nuxt.jsでは以下のコードだけで、よしなにやってくれます。
nuxt/store/index.jsexport const state = () => ({ ~~省略~~ }) export const mutations = { ~~省略~~ } export const actions = { ~~省略~~ } export const getters = { ~~省略~~ }Vue.jsの場合
モジュール単位で分割して作成します。
ディレクトリ構造は下記のイメージです。resources/js ├── App.vue ├── app.js └── store ├── modules │ ├── hoge.js │ └── index.js └── store.jsresources/js/store/modules/index.jsの作成
resources/js/store/modules/index.jsconst state = () => ({ ~~省略~~ }) const mutations = { ~~省略~~ } const actions = { ~~省略~~ } const getters = { ~~省略~~ } export default { namespaced: false, //nuxt/store/index.jsを解体する場合、namespacedはfalseになります state: state(), getters: getters, actions: actions, mutations: mutations }resources/js/store/store.jsでVuexの設定をする
resources/js/store/store.jsimport Vue from 'vue' import Vuex from 'vuex' import index from '~/store/modules/index' Vue.use(Vuex) export default new Vuex.Store({ modules: { index } })resources/js/app.jsでresources/js/store/store.jsを設定する
resources/js/app.jsimport Vue from "vue"; import App from '~/App.vue' import store from '~/store/store' new Vue({ store, render: h => h(App), }).$mount('#app')nuxt/store/hoge.jsを解体する場合(index.js以外の場合)
nuxt/store/hoge.jsの中身は以下と仮定する
nuxt/store/hoge.jsexport const state = () => ({ ~~省略~~ }) export const mutations = { ~~省略~~ } export const actions = { ~~省略~~ } export const getters = { ~~省略~~ }Vue.jsの場合のresources/js/store/modules/hoge.jsのコード
resources/js/store/modules/hoge.jsconst state = () => ({ ~~省略~~ }) const mutations = { ~~省略~~ } const actions = { ~~省略~~ } const getters = { ~~省略~~ } export default { namespaced: true, // nuxt/store/hoge.jsを解体する場合(index.js以外の場合)、namespacedはtrueになります state: state(), getters: getters, actions: actions, mutations: mutations }resources/js/store/store.jsでVuexの設定をする
resources/js/store/store.jsimport Vue from 'vue' import Vuex from 'vuex' import index from '~/store/modules/index' import hoge from '~/store/modules/hoge' Vue.use(Vuex) export default new Vuex.Store({ modules: { index, hoge } })resources/js/app.jsでresources/js/store/store.jsを設定する
resources/js/app.jsimport Vue from "vue"; import App from '~/App.vue' import store from '~/store/store' new Vue({ store, render: h => h(App), }).$mount('#app')参考
関連リンク
- 投稿日:2020-05-23T01:29:39+09:00
NuxtでGitベースのヘッドレスCMS機能を実現するNuxt/Contentを使ってみた
Nuxt/Contentって?
Nuxtでマークダウン形式のファイルをCMS風に使用するためのモジュールです。
一部ではヘッドレスCMSいらなくなるんじゃね?説まで出ているので、使ってみることにしました。インストール
Nuxtはあらかじめインストールしておいてください。
今回は公式ドキュメントにはないTypeScript での使用法を紹介したいと思います。Nuxt/contentのインストール
yarn add @nuxt/content次に
nuxt.config.js
のmodules
プロパティを以下のように追加modules: [ '@nuxt/content' ],nuxt-composition-api(おまけ)
これはNuxt/Contentを使用するだけなら不要です。ただ自分はComposition-apiを普段から使っていて、
もしもNuxtでComposition-apiを使うときは@vue/composition-api
よりもこちらを使う方がオススメ
(今後のアップデートでは不要になるといいですね)yarn add nuxt-composition-api次に
nuxt.config.js
に以下を追加buildModules: [ 'nuxt-composition-api' ]ちなみに、自動的に
@vue/composition-api
が読み込まれるので、nuxt-composition-api
からrefやreactiveが読み込めます。contentディレクトリを作る
ルートディレクトリに
content
ディレクトリを作ります(複数形ではありません)。このディレクトリに入れた
.md
ファイルがマークダウンとして認識されます。
.md
以外にも.yaml
.csv
.json
.json5
がパースされるそうです。今回は後学のために、
content
の下にさらにarticles
ディレクトリを作っていますが、これは任意で、特にディレクトリ名は制限はないと思います。content/ articles/ article-1.md例えばこんな感じになると思います。
article-1.mdの中身は適当なマークダウンを書いてみましょう。
ちなみにコードブロックも書けます。PrismJS をラップしてハイライティングしているようです。pagesで表示する
ページコンポーネントで表示してみましょう。
pages/index.vue
にて以下のように記述します。<template> <article> <h1>{{ doc.title }}</h1> <nuxt-content :document="doc" /> </article> </template> <script lang="ts"> import { ref, onMounted, useContext } from 'nuxt-composition-api' export default { setup() { const doc = ref({}) const ctx: any = useContext() onMounted(async () => { doc.value = await ctx.$content('articles/article-1').fetch() }) return { doc } } } </script>シンプルですね。ちゃんと表示されていればOKです。表示したい記事を
<nuxt-content>
タグに渡しています。CMS的な使い方ですと、
_slag.vue
といもうのを作って、コンテキストからparams
経由でslag変数を取得すればブログのページっぽく書けると思います。
useContext
はNuxtのコンテキスト情報を取得するもので、通常のsetup
の引数のコンテキスト変数とは異なります。
なぜctx
がanyかというと、型情報が今の所ないようなので、anyにしておかないとESLintとかに怒られるからです。
また、なぜ新世代のasyncData
に相当するuseAsync
を使わないかというと、基本このNuxt/ContentがSPA/MPAで運用されると予想されるので、useAsync
は使えないと思い、onMounted
フックで使用しています。この辺り「こっちの方がいいのでは?」というやり方を知っていれば教えてくれると助かります。
Vueコンポーネントをマークダウンに書く
マークダウン上でVueコンポーネントを使ってみることにします。
今回はただただカウントするだけのコンポーネントを作って置いてみましょう。
~/components/TheCounter.vue
<template> <div> {{ count }} <button @click="add()">ADD!</button> </div> </template> <script lang="ts"> import { ref, defineComponent } from 'nuxt-composition-api' export default defineComponent({ name: '', setup() { const count = ref(0) const add = () => count.value++ return { count, add } } }) </script>次にマークダウン上にこのコンポーネントを書きます。
書くのですが、必ずケバブケース & セルフクロージングタグではない書き方にしてください。
具体的には以下のような形式で書いてください。OK! <the-counter></the-counter> NG! ケバブケースじゃない <TheCounter></TheCounter> NG! セルフクロージングタグになっている <the-counter />最後に、
<nuxt-content>
を表示するVueファイルにて、コンポーネントを登録しておきます。<template> <article> <h1>{{ doc.title }}</h1> <nuxt-content :document="doc" /> </article> </template> <script lang="ts"> import { ref, onMounted, useContext } from 'nuxt-composition-api' import TheCounter from '~/components/TheCounter.vue' export default { name: '', components: { TheCounter }, setup() { const doc = ref('') const ctx: any = useContext() onMounted(async () => { doc.value = await ctx.$content('articles/article').fetch() }) return { doc } } } </script>これで表示されていればOKです。
終わりに
Vueコンポーネントをマークダウンに直接書けるというのは、なかなか画期的なのではないでしょうか?
その気になれば簡単な投票コンポーネントとか、メールフォームをどこにでも置けるという感じになると思います。Nuxt/contentはまだ生まれたばかりで、ドキュメントもまだ少ないというのが現状です。
公式サイトで「acting as Git-based Headless CMS」(GitベースのヘッドレスCMSとして機能)とあるように、Nuxtで機能するヘッドレスCMS的な何かな使い方が可能です。なんかヘッドレスCMSって覚えること多いなぁとか、Nuxt単体で色々やりたいなぁという方にはぜひオススメ。
リンク
- 投稿日:2020-05-23T01:29:39+09:00
NuxtでGitベースのヘッドレスCMS機能を実現するNuxt/Contentの紹介
Nuxt/Contentって?
Nuxtでマークダウン形式のファイルをCMS風に使用するためのモジュールです。
一部ではヘッドレスCMSいらなくなるんじゃね?説まで出ているので、使ってみることにしました。インストール
Nuxtはあらかじめインストールしておいてください。
今回は公式ドキュメントにはないTypeScript での使用法を紹介したいと思います。Nuxt/contentのインストール
yarn add @nuxt/content次に
nuxt.config.js
のmodules
プロパティを以下のように追加modules: [ '@nuxt/content' ],nuxt-composition-api(おまけ)
これはNuxt/Contentを使用するだけなら不要です。ただ自分はComposition-apiを普段から使っていて、
もしもNuxtでComposition-apiを使うときは@vue/composition-api
よりもこちらを使う方がオススメ
(今後のアップデートでは不要になるといいですね)yarn add nuxt-composition-api次に
nuxt.config.js
に以下を追加buildModules: [ 'nuxt-composition-api' ]ちなみに、自動的に
@vue/composition-api
が読み込まれるので、nuxt-composition-api
からrefやreactiveが読み込めます。contentディレクトリを作る
ルートディレクトリに
content
ディレクトリを作ります(複数形ではありません)。このディレクトリに入れた
.md
ファイルがマークダウンとして認識されます。
.md
以外にも.yaml
.csv
.json
.json5
がパースされるそうです。今回は後学のために、
content
の下にさらにarticles
ディレクトリを作っていますが、これは任意で、特にディレクトリ名は制限はないと思います。content/ articles/ article-1.md例えばこんな感じになると思います。
article-1.mdの中身は適当なマークダウンを書いてみましょう。
ちなみにコードブロックも書けます。PrismJS をラップしてハイライティングしているようです。pagesで表示する
ページコンポーネントで表示してみましょう。
pages/index.vue
にて以下のように記述します。<template> <article> <h1>{{ doc.title }}</h1> <nuxt-content :document="doc" /> </article> </template> <script lang="ts"> import { ref, onMounted, useContext } from 'nuxt-composition-api' export default { setup() { const doc = ref({}) const ctx: any = useContext() onMounted(async () => { doc.value = await ctx.$content('articles/article-1').fetch() }) return { doc } } } </script>シンプルですね。ちゃんと表示されていればOKです。表示したい記事を
<nuxt-content>
タグに渡しています。CMS的な使い方ですと、
_slag.vue
といもうのを作って、コンテキストからparams
経由でslag変数を取得すればブログのページっぽく書けると思います。
useContext
はNuxtのコンテキスト情報を取得するもので、通常のsetup
の引数のコンテキスト変数とは異なります。
なぜctx
がanyかというと、型情報が今の所ないようなので、anyにしておかないとESLintとかに怒られるからです。
また、なぜ新世代のasyncData
に相当するuseAsync
を使わないかというと、基本このNuxt/ContentがSPA/MPAで運用されると予想されるので、useAsync
は使えないと思い、onMounted
フックで使用しています。この辺り「こっちの方がいいのでは?」というやり方を知っていれば教えてくれると助かります。
Vueコンポーネントをマークダウンに書く
マークダウン上でVueコンポーネントを使ってみることにします。
今回はただただカウントするだけのコンポーネントを作って置いてみましょう。
~/components/TheCounter.vue
<template> <div> {{ count }} <button @click="add()">ADD!</button> </div> </template> <script lang="ts"> import { ref, defineComponent } from 'nuxt-composition-api' export default defineComponent({ name: '', setup() { const count = ref(0) const add = () => count.value++ return { count, add } } }) </script>次にマークダウン上にこのコンポーネントを書きます。
書くのですが、必ずケバブケース & セルフクロージングタグではない書き方にしてください。
具体的には以下のような形式で書いてください。OK! <the-counter></the-counter> NG! ケバブケースじゃない <TheCounter></TheCounter> NG! セルフクロージングタグになっている <the-counter />最後に、
<nuxt-content>
を表示するVueファイルにて、コンポーネントを登録しておきます。<template> <article> <h1>{{ doc.title }}</h1> <nuxt-content :document="doc" /> </article> </template> <script lang="ts"> import { ref, onMounted, useContext } from 'nuxt-composition-api' import TheCounter from '~/components/TheCounter.vue' export default { name: '', components: { TheCounter }, setup() { const doc = ref('') const ctx: any = useContext() onMounted(async () => { doc.value = await ctx.$content('articles/article').fetch() }) return { doc } } } </script>これで表示されていればOKです。
終わりに
Vueコンポーネントをマークダウンに直接書けるというのは、なかなか画期的なのではないでしょうか?
その気になれば簡単な投票コンポーネントとか、メールフォームをどこにでも置けるという感じになると思います。Nuxt/contentはまだ生まれたばかりで、ドキュメントもまだ少ないというのが現状です。
公式サイトで「acting as Git-based Headless CMS」(GitベースのヘッドレスCMSとして機能)とあるように、Nuxtで機能するヘッドレスCMS的な何かな使い方が可能です。なんかヘッドレスCMSって覚えること多いなぁとか、Nuxt単体で色々やりたいなぁという方にはぜひオススメ。
リンク
- 投稿日:2020-05-23T00:59:43+09:00
Vue.js 親・子コンポーネント間 データ 受け渡し
やりたいこと
Vue.js (Vue CLI)で、親・子コンポーネント間でデータを受け渡したい。
今回は以下のような流れでデータを受け渡します。
child1.vue(子1) → pearent.vue(親) → child2.vue(子2)child1.vue(子1) → pearent.vue(親)、子から親へデータを移動
今回は、ボタンを押すと
input
に記載された文字列を親コンポーネントに渡す処理を実装します。まずchild1.vue(子1)
<template> <div id="target-input"> <p>以下input内に入力されたデータを親に渡します</p> <input type="text" @input="example = $event.target.value"> <button @click="sendToParent">親へデータを渡す</button> </div> </template> <script> export default { data(){ return { example : "", } }, methods: { sendToParent() { this.$emit("textForParent",this.example); } }, }; </script>ポイント
・methods
内、this.$emit
は引数を2つとります。1つ目は好きに名前を付けてください。
(後ほど親コンポーネント内で使用します)
・2つ目は、親コンポーネントへ渡したいデータを指定します。今回はdata
内のexample
です。上記で「送り口」の実装は完成です。
しかしこのままでは「受け取り口」が用意されていない為、データが親コンポーネントへ渡りません。
受け取り側であるpearent.vue(親)に、「受け取り口」を実装していきます。
pearent.vue(親)<template> <div id="app"> <child1 v-on:textForParent="text = $event"></child1> <child2></child2> </div> </template> <script> import child1 from './child1.vue' import child2 from './child2.vue' export default { name: 'App', components: { child1, child2, }, data(){ return { text : "", } }, }; </script>ポイント
・v-on
ディレクティブの後に、子コンポーネント内で設定したthis.$emit
の第1引数をとります。
・$event
には、子コンポーネント内のthis.$emit
の第2引数である、データが入ります。これで、親コンポーネント
data
内のtext
へ、子コンポーネントからデータを渡すことが出来ました。pearent.vue(親) → child2.vue(子2) 、親から子へデータを移動
では今度は親コンポーネントから子コンポーネントへデータを移動してみましょう。
先ほどのtext
を、child2.vueへ渡してみます。pearent.vue(親)
<template> <div id="app"> <child1 v-on:textForParent="text = $event"></child1> <child2 v-bind:textForChild2="text"></child2> </div> </template> <script> import child1 from './child1.vue' import child2 from './child2.vue' export default { name: 'App', components: { child1, child2, }, data(){ return { text : "", //実際には、child1からのデータが入っています } }, }; </script>ポイント
・<child2></child2>
にv-bind
を付けてあげます。text
のデータを子に送ります。
上記で「送り口」はできました。今度はchild2.vue(子2)に「受け取り口」を作ります。
child2.vue(子2)<template> <div> <p>以下に親コンポーネントから渡されたtextデータを表示します。</p> <p>{{textForChild2}}</p> </div> </template>> <script> export default { props:["textForChild2"], }; </script>ポイント
・props
を設定して親コンポーネントからデータを受け取ります。これをtemplate
内で呼び出します。以上です!
今回の例ではchild1.vue(子1)内で作られたデータがpearent.vue(親)へ渡り、そこからchild2.vue(子2)へ渡りました。子から親へ、親から子へデータを受け渡しすることが出来ました!
- 投稿日:2020-05-23T00:59:43+09:00
Vue.js 親・子 コンポーネント間 データ 受け渡し
やりたいこと
Vue.js (Vue CLI)で、親・子コンポーネント間でデータを受け渡したい。
今回は以下のような流れでデータを受け渡します。
child1.vue(子1) → pearent.vue(親) → child2.vue(子2)child1.vue(子1) → pearent.vue(親)、子から親へデータを移動
今回は、ボタンを押すと
input
に記載された文字列を親コンポーネントに渡す処理を実装します。まずchild1.vue(子1)
<template> <div id="target-input"> <p>以下input内に入力されたデータを親に渡します</p> <input type="text" @input="example = $event.target.value"> <button @click="sendToParent">親へデータを渡す</button> </div> </template> <script> export default { data(){ return { example : "", } }, methods: { sendToParent() { this.$emit("textForParent",this.example); } }, }; </script>ポイント
・methods
内、this.$emit
は引数を2つとります。1つ目は好きに名前を付けてください。
(後ほど親コンポーネント内で使用します)
・2つ目は、親コンポーネントへ渡したいデータを指定します。今回はdata
内のexample
です。上記で「送り口」の実装は完成です。
しかしこのままでは「受け取り口」が用意されていない為、データが親コンポーネントへ渡りません。
受け取り側であるpearent.vue(親)に、「受け取り口」を実装していきます。
pearent.vue(親)<template> <div id="app"> <child1 v-on:textForParent="text = $event"></child1> <child2></child2> </div> </template> <script> import child1 from './child1.vue' import child2 from './child2.vue' export default { name: 'App', components: { child1, child2, }, data(){ return { text : "", } }, }; </script>ポイント
・v-on
ディレクティブの後に、子コンポーネント内で設定したthis.$emit
の第1引数をとります。
・$event
には、子コンポーネント内のthis.$emit
の第2引数である、データが入ります。これで、親コンポーネント
data
内のtext
へ、子コンポーネントからデータを渡すことが出来ました。pearent.vue(親) → child2.vue(子2) 、親から子へデータを移動
では今度は親コンポーネントから子コンポーネントへデータを移動してみましょう。
先ほどのtext
を、child2.vueへ渡してみます。pearent.vue(親)
<template> <div id="app"> <child1 v-on:textForParent="text = $event"></child1> <child2 v-bind:textForChild2="text"></child2> </div> </template> <script> import child1 from './child1.vue' import child2 from './child2.vue' export default { name: 'App', components: { child1, child2, }, data(){ return { text : "", //実際には、child1からのデータが入っています } }, }; </script>ポイント
・<child2></child2>
にv-bind
を付けてあげます。text
のデータを子に送ります。
上記で「送り口」はできました。今度はchild2.vue(子2)に「受け取り口」を作ります。
child2.vue(子2)<template> <div> <p>以下に親コンポーネントから渡されたtextデータを表示します。</p> <p>{{textForChild2}}</p> </div> </template>> <script> export default { props:["textForChild2"], }; </script>ポイント
・props
を設定して親コンポーネントからデータを受け取ります。これをtemplate
内で呼び出します。以上です!
今回の例ではchild1.vue(子1)内で作られたデータがpearent.vue(親)へ渡り、そこからchild2.vue(子2)へ渡りました。子から親へ、親から子へデータを受け渡しすることが出来ました!