- 投稿日:2019-12-04T23:53:59+09:00
2019年 よく使ったJavaScriptたちをまとめる
はじめに
JavaScript Advent Calendar 2019の4日目です。
本日は@watsuyo_2が「2019年 よく使ったJavaScriptたちをまとめる」を記事にします!フロントエンドエンジニア1年目を振り返りながら、これからJavaScriptを学ぶ人やよく使うメソッドを知りたい人がわかりやすいようにまとめます。
普段、 Nuxt.js(Vue.js) と Firestore を使用した開発をしているので例としてでてきますがご了承ください。↓は1年前に転職するまでの流れを書いた記事にあります。
新年からWebエンジニアになる僕に2018年、起こったことArray.prototype.map()
配列の要素をひとつひとつ取り出し、第一引数の値(例で言うx)を元に処理を行い、その結果から新たな配列を作れる
実務的な例
よく使うパターンとして、オブジェクト配列をそれぞれ取り出して、Firestoreにデータ更新、取得するような時に使います。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] todos.map(todo => ( firebase.firestore().collection(`todos/${todo.id}`).update({ todo.content }) ))Array.prototype.filter()
配列の要素をひとつひとつ取り出し、第一引数の値(例で言うx)を元に比較を行い、trueだった結果から新たな配列を作れる
実務的な例
オブジェクト配列をそれぞれ取り出して、特定の要素を削除する場合などで使いました。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] // 特定のtodo.idが'PHCXSNTRVZHNAJYWJT6'以外の配列を作成 const deleteOne = () => todos.filter(todo => todo.id != 'PHCXSNTRVZHNAJYWJT6' ) console.log(deleteOne()) // [ // { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, // { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, // { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' } // ]Array.prototype.find()
配列の要素をひとつひとつ取り出し、第一引数の値(例で言うx)を元に比較を行い、trueだった結果を取得する
実務的な例
オブジェクト配列をそれぞれ取り出して、配列中の特定の要素を抽出する場合などで使いました。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] const pickUpTodo = () => todos.find(todo => todo.id === 'PHCXSNTRVZHNAJYWJT6' ) console.log(pickUpTodo()) //{ id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' }Array.prototype.push()
配列に新しい要素を追加する
実務的な例
配列になにか新しい要素を追加する場面はよく遭遇しました。
const todos = [ { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' } ] todos.push({ id: 'WRABJ9IY4RJML7L4OB2', content: 'リーダブルコードを読む' }) console.log(todos) // [ // { id: 'H6BHBW7WS7H8DAT8HAK', content: '早起きする' }, // { id: 'HGX3DMZ9ZT8GN7KHYUY', content: '早く寝る' }, // { id: '4MT32NDNS6XFJUNGRPH', content: '牛乳を買う' }, // { id: 'PHCXSNTRVZHNAJYWJT6', content: 'Youtubeは1日2時間まで' }, // { id: 'WRABJ9IY4RJML7L4OB2', content: 'リーダブルコードを読む' } // ]String.prototype.match()
正規表現に対して文字列のマッチングし、マッチすればtrue、しなければfalseを返します。
実務的な例
入力されたデータに対し、バリデーションをかける時などでよく使いました。
const telA = '070-7374-0044' const telB = '09024570411' console.log(telA.match(/^(0{1}\d{9,10})$/g)) // null console.log(telB.match(/^(0{1}\d{9,10})$/g)) // true三項演算子
if文の省略的な立ち位置になります。
以下がif文と三項演算子との対照表です。パターンA: if文
const hoge = 0 if(hoge === 0) { return 'fuga' } else { return 'hoge' }パターンB: 三項演算子
const hoge = 0 hoge === 0 ? 'fuga' : 'puge'実務的な例
実務では上記の例のように全く対象のものとしては捉えておらず、可読性を重視します。
特にreturn
で結果だけが欲しい場合では三項演算子
を使うことが多いです。例えばVue.js×TypeScriptに限る話になりますが、computed内で値が
this
を使って参照する値が存在するかをチェックし、存在すればその値を返し、無ければnullを返す場合に使います。computed: { todo (): Terms | null { return this.todo.titles ? todo.titles : null } },おわりに
今回、例としてあげた部分はほんの一部で、その他にもたくさんありますので奥は深いです。
振り返ってみても、JavaScriptフレームワークが様々な現場で普及している2019年ですが、基本的なメソッドや書き方は初心忘れるべからずということで押さえておきましょう。メンションつきツイートをしていただけるとたいへん喜びます!
thanks-mentionsというQiitaの記事を作者に対してメンションを飛ばしながらツイートが出来るPWAを作りました?
ぜひ、メンションつきツイートをしていただけるとたいへんとてもとても喜びます!
JavaScript Advent Calendar 2019
JavaScript Advent Calendar 2019明日の担当は、
@okumurakengoさんです!
- 投稿日:2019-12-04T23:31:01+09:00
Vue Devtoolsでパフォーマンス改善ポイントを見つける
この記事は、Vue.js Advent Calendar 201 9 #1 8 日目の記事です。
Vue Devtools の Performance タブを使って、パフォーマンスの改善ポイントをサクッと見つける方法を紹介したいと思います。
「サクっと」というのがポイントです。さらに、見つけた改善ポイントから実際にパフォーマンスを改善する例を紹介したいと思います。
Vue Devtools とは
Vue.js を使った開発におけるデバッグなどを助けてくれるツールです。
Chrome Extensionとしても提供されています。
機能ごとにタブが存在しており、今回はその中の Performance タブを使います。Vue Devtools について詳しくは以下の記事が参考になると思います。
Vue Devtools で快適なデバッグ - ROXX(旧 SCOUTER)開発者ブログ
Performance タブ
Vue.js で構築されたアプリケーションのパフォーマンスを簡易的に計測できる機能です。
Chrome DevTools を使った計測ほど詳細は分からないものの、敷居が低く簡単に使うことができる印象があります。
パフォーマンスの悪いコンポーネントをサクッと見つけたい場合は、この Performance タブを使うのがおすすめです。Performance タブの使い方についても上記の記事が参考になると思います。
Vue Devtools でパフォーマンス改善ポイントを見つける
早速、Vue Devtools の Performance タブを使ってパフォーマンス改善ポイントを見つけてみたいと思います。
改善対象のコンポーネント
改善対象のコンポーネントは以下のようなものになります。
- 大量の選択肢から 1 つを選択するダイアログコンポーネント
- 選択肢にマウスオーバーすると、その選択肢の説明を下の方に表示
実際の動作デモもあります。
https://vue-devtools-performance-handson.netlify.comまた、コードは以下に置いてあるのでよろしければ確認してみてください。
https://github.com/shun91/vue-devtools-performance-handson/blob/master/src/components/HeavyDialog.vue動作がもっさり...
上記の動画やデモでも明らかですが、このダイアログは動作がもっさりしていて重いです。1
まず、ボタンをクリックしてからダイアログが開くまでが重くて、アニメーションももっさりしています。
さらに、マウスオーバーすると選択肢の背景色が変わるのですが、ここのアニメーションももっさりしています。パフォーマンスを計測してみる
では、実際に Vue Devtools の Performance タブを使ってパフォーマンスを計測してみます。
今回は、選択肢にマウスオーバーした時のパフォーマンスを計測してみます。まずは Frames per second から確認してみます。
その様子を動画にしてみましたが、選択肢にマウスオーバーした瞬間に明らかに fps が下がったことが分かると思います。続いて、Component render を確認してみます。
こちらも、選択肢にマウスオーバーした瞬間に何やらたくさんの render 処理が走っているようです。改善ポイントを見つける
計測結果をもう少し詳しく見てみます。
Component render では、左側のコンポーネント名をクリックすると、そのコンポーネント内で走った処理 (Lifecycle Hooks) にかかった時間と回数を確認することができます。
例えば、
<VListTile>
をクリックしてみると、以下のようになります。なんと、選択肢にマウスオーバーしただけなのに
updateRender
が 600 回も実行されていて、トータルで 71333ms (≒ 71 秒!) もかかっていることが分かりました...
合わせて、<VRadio>
や<VIcon>
も同じようにupdateRender
が 600 回も実行されていました。つまり、
<VListTile>
,<VRadio>
,<VIcon>
のあたりにパフォーマンスを改善するためのポイントがありそうだと言えそうです。このように、Performance タブを使えばパフォーマンスの悪いコンポーネントを簡単に見つけ出すことができます。
この記事の目的としてはここでもう果たせたのですが、せっかくなので実際にパフォーマンスを改善するところまでやってみたいと思います。
改善する
先程、
<VListTile>
,<VRadio>
,<VIcon>
に改善ポイントがありそうだということがわかりました。実装を確認してみると、これらのコンポーネントは
v-for
で繰り返し描画されており、選択肢 1 つ 1 つを構成している要素だということが分かります。<v-list-tile v-for="{ value } in items" :key="value" ripple @click="selected = value" @mouseenter="updateOnmoused(value)" @mouseleave="updateOnmoused('')" > <v-list-tile-action> <v-radio :value="value" /> </v-list-tile-action> <v-list-tile-content> <v-list-tile-title>{{ value }}</v-list-tile-title> </v-list-tile-content> </v-list-tile>1 つの選択肢にマウスオーバーしただけで 600 回も render 処理が走るということは、つまり、「1 つの選択肢にマウスオーバーしただけで、すべての
<VListTile>
,<VRadio>
,<VIcon>
が再描画されている」可能性がありそうです。2これは明らかに無駄です。マウスオーバーした部分だけが再描画されれば十分のはずです。
再描画される部分をコンポーネント化する
無駄な部分まで再描画されないようにするためには、該当部分をコンポーネント化して切り出すとよいことがあります。
詳しくは以下の記事が参考になると思います。コンポーネントを使って描画更新のコストを削減する | seihmd tech blog
では、
<VListTile>
の部分を<TheListItem>
という名前のコンポーネントに切り出してみます。diff --git a/src/components/HeavyDialog.vue b/src/components/HeavyDialog.vue index cd89f9c..8297e4c 100644 --- a/src/components/HeavyDialog.vue +++ b/src/components/HeavyDialog.vue @@ -1,7 +1,7 @@ <template> <v-dialog v-model="dialog" scrollable max-width="300px"> <template #activator="{ on }"> - <v-btn v-on="on" dark>open heavy dialog</v-btn> + <v-btn v-on="on">open light dialog</v-btn> </template> <v-card> @@ -12,22 +12,14 @@ <v-card-text class="pa-0" style="height: 300px;"> <v-radio-group v-model="selected"> <v-list class="pa-0"> - <v-list-tile - v-for="{ value } in items" - :key="value" - ripple - @click="selected = value" - @mouseenter="updateOnmoused(value)" + <the-list-item + v-for="item in items" + :key="item.value" + :item="item" + @click="selected = item.value" + @mouseenter="updateOnmoused(item.value)" @mouseleave="updateOnmoused('')" - > - <v-list-tile-action> - <v-radio :value="value" /> - </v-list-tile-action> - - <v-list-tile-content> - <v-list-tile-title>{{ value }}</v-list-tile-title> - </v-list-tile-content> - </v-list-tile> + /> </v-list> </v-radio-group> </v-card-text> @@ -45,8 +37,11 @@ <script lang="ts"> import Vue from "vue"; +import TheListItem from "./TheListItem.vue"; export default Vue.extend({ + components: { TheListItem }, + data: () => ({ dialog: false, selected: "",変更後のコードは LightDialog.vue として保存します。
<TheListItem>
の実装は以下のようになります。<template> <v-list-tile ripple @click="$emit('click')" @mouseenter="$emit('mouseenter')" @mouseleave="$emit('mouseleave')" > <v-list-tile-action> <v-radio :value="item.value" /> </v-list-tile-action> <v-list-tile-content> <v-list-tile-title>{{ item.value }}</v-list-tile-title> </v-list-tile-content> </v-list-tile> </template> <script lang="ts"> import Vue, { PropType } from "vue"; export default Vue.extend({ props: { item: { type: Object as PropType<any>, required: true } } }); </script>改善後のパフォーマンスを計測してみる
コードを修正できたので、再度パフォーマンスを計測してみます。
まずは Frames per second からです。
見事に 60fps を維持することができています!続いて Component render です。
<VListTile>
は 1 回どころかまったく再描画されなくなりました!改善前後を見比べても確かに動作が軽快になっていることが分かります。
動画だと分かりにくい場合は実際のデモを触ってみてください。
Before After まとめ
この記事では、Vue Devtools の Performance タブを使って、パフォーマンスの改善ポイントを見つける方法を紹介しました。
さらに、見つけた改善ポイントに修正を加えて、実際にパフォーマンスを改善しました。
- Performance タブを使えばパフォーマンスの悪いコンポーネントを簡単に見つけ出すことができます。
- Chrome DevTools を使った計測ほど詳細は分からないものの、敷居が低く簡単に使うことができます。
また、今回使用したコードは以下のリポジトリに置いてありますのでよかったら確認してみてください。
shun91/vue-devtools-performance-handson
https://github.com/shun91/vue-devtools-performance-handson最後までお読みいただきありがとうございました!mm
おまけ
- 「ボタンをクリックしてからダイアログが開くまでが重い」のも、Performance タブを使って改善ポイントを見つけることができます。こちらはぜひご自身の手で試してみてください。 3
- Vue Devtools の Performance タブについての記事が意外と見つけられなかったので、この記事が少しでも誰かのお役に立てれば嬉しいです。
- この記事を書くためのデモを実装するのに、はじめは Vuetify 2.x を使っていたのですが、プロダクションビルドすると思ったようにパフォーマンスが悪くならない現象に遭遇しました。Vuetify 1.x にダウングレードしたところ、想定通りパフォーマンスが悪くなったので、Vuetify 2.x ではプロダクションビルド時に何らかのパフォーマンス最適化が行われるようになったみたいです。(すみません、詳しくは調べられていないです...)
あえて重くなるような作りにしているだけではありますが... ↩
この可能性を確信に変えるには、Chrome DevTools などでさらなる検証が必要ですが、この記事の主旨からはズレるので割愛します mm ↩
仮想スクロールを使うとパフォーマンスを改善できます。Vue.js だと vue-virtual-scroller というライブラリがあります。 ↩
- 投稿日:2019-12-04T23:08:36+09:00
Vue.jsでちょっとおしゃんてぃなimport
はじめに
いなたつアドカレの四日目の記事です。
今回はVue.jsで使えるちょっとかっこつけたおしゃんてぃなimport方法について書いてくぜ
じっそー
router.jsconst loadView = view => () => import(`./views/${view}.vue`) export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: loadView('Home') }, { path: '/about', name: 'about', component: loadView('About') } ] })こんなかんじですね、前提として、viewに関するファイルがviewsディレクトリにまとまっているとおもってください。
loadViewで引数viewを受けて無名関数を高階的にimportする関数を返却しています。
これで、import文を別に何度も書く必要がなく、importすることができ、すこしおしゃんてぃな感じが醸し出せます。
- 投稿日:2019-12-04T22:58:46+09:00
vue-paginateのボタンにクリックイベントを設定する
vue-paginateのボタンにクリックイベントを設定する
vue-paginate
https://github.com/TahaSh/vue-paginate
paginate-linksの要素内に、
@change="関数"
と指定するだけ。
<paginate-links for="paginate-resource" id="paginate-button" class="pagination" @change="scrollToTop" :show-step-links="true" v-if="tasks.length !== 0" :limit="3"></paginate-links>
- 投稿日:2019-12-04T19:50:17+09:00
Vue Composition APIで型がぶっ壊れて楽しかったです
これは bosyu Advent Calendar 2019 の 4日目の記事です。
よろしくね。bosyuのフロントはVueを使っているのですが、最近 Composition API が上がってきているのでそれを使うようにしていこうぜ〜〜〜
という感じでやっております。https://github.com/vuejs/composition-api
これね。でこれがたまにアレなところがあるのですが、先日おもしろいところを見つけたので、それについて書いていくぞというアレです。
多分近々直ります。なにがおきた
タイトル通りなんですが、型が不思議になりました。
具体的なコードを書くと以下のようなときに型が?????となります。nazo.vueexport default createComponent({ setup () { const state = reactive({ status: 'hoge', value: 'fuga' }); const v = state.value; } });ここでは
state
は以下のようになるように期待していますが、type State = { status: string; value: string; }VSCode等でみると
state
はstring
となってしまいます![]()
なんでや
ということで、どんなかんじに型定義されているかみてみましょ。
https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/reactive.ts#L129
export function reactive<T = any>(obj: T): UnwrapRef<T> { if (process.env.NODE_ENV !== 'production' && !obj) { warn('"reactive()" is called without provide an "object".'); // @ts-ignore return; } if (!isPlainObject(obj) || isReactive(obj) || isNonReactive(obj) || !Object.isExtensible(obj)) { return obj as any; } const observed = observe(obj); def(observed, ReactiveIdentifierKey, ReactiveIdentifier); setupAccessControl(observed); return observed as UnwrapRef<T>; }こんなかんじでした。
return
のところの型がas
になってて強さを感じる。まあそれはさておきこの
UnwrapRef<T>
てのがきになりますね。
それを見てみましょう。https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/ref.ts#L17
export type UnwrapRef<T> = T extends Ref<infer V> ? UnwrapRef2<V> : T extends BailTypes ? T // bail out on types that shouldn’t be unwrapped : T extends object ? { [K in keyof T]: UnwrapRef2<T[K]> } : T // prettier-ignore type UnwrapRef2<T> = T extends Ref<infer V> ? UnwrapRef3<V> : T extends BailTypes ? T : T extends object ? { [K in keyof T]: UnwrapRef3<T[K]> } : T // ... // prettier-ignore type UnwrapRef10<T> = T extends Ref<infer V> ? V // stop recursion : Tこんな感じになってます。
ConditionalTypes
をつかって型を判定していますね。
http://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-typesでこの
Ref
の定義を見てみましょう。https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/ref.ts#L9
export interface Ref<T> { value: T; }です。
つまり
value
をメンバーとして持っている場合はRef
を extends しているとみなされてしまい、
UnwrapRef2
UnwrapRef3
... と流れていってしまいます。
最終的にUnwrapRef10
にたどり着きV
となります。結果的に最初に書いたように
state
がstring
のようになってしまうわけですね〜。かいけつさく
https://github.com/vuejs/composition-api/pull/167
PRはでてますが、Object
の中にObject
があったりすると、同様の問題が起きてしまうので困ったもんだなぁ〜という感じですね。かんたんにはどうこうできるような感じではないので、できれば
value
というのはできる限りつかわないようにするのがとりあえずの対策かなぁ〜。またVue-nextでは同様の問題が起きないような対処がされているようです。
https://github.com/vuejs/vue-next/blob/master/packages/reactivity/src/ref.tsなのでこれと同様な実装を
composition-api
にPRでなげるかとかですかね。まあそんな感じ。
あとは邪悪だけど
createComponent({ setup () { const state = reactive({ status: 'hoge', value: 'fuga' }) as unknown as { status: string, value: string }; const v = state.value; } });
unknown
とかany
とか一回型をぶっ壊して、むりやりやっちゃうとかね。
最高にダメな感じだけど。まとめ
- かたはむずかしい。
- かたはたのしい。
as unknow as Hoge
は最凶(強)。
- 投稿日:2019-12-04T18:27:33+09:00
Vue.js初心者が学習のためにシンプルなマインスイパーを作った
はじめに
この記事は 2019 年まで素の JavaScript でコーディングしていた人間が初めて Vue.js を学習したときの記録です
↓のURLにアクセスするとアプリに触れます。
https://vue-mine.firebaseapp.com何を作ったか?
見た目はあれです。今回は簡単な仕上がりを目指しました
(適当)
データバインディングの動きの確認をする程度の目的ですので、セルは単純な色分けで区別しています。開発環境
Windows10
Node.js v12.13.1
Vue.js 2.6.10
vue/cli 4.1.1
Atom 1.41.0
Github https://github.com/t-ube/vue-mine作成時につまづいた点と答え
Q. まずどのファイルをいじればいいのかわからない
A. <プロジェクトフォルダ>\src\App.vue を編集する
よっしゃさっそく作り始めたるわーと思い、コマンドラインで
vue create <プロジェクト名>と打ち込んだ直後に僕は手が止まりましたが、 @567000 様の記事を参考にして、まずは
App.vue
内の<template>
を色々といじるところから始めました。
Vue.js を vue-cli を使ってシンプルにはじめてみるQ. 表示条件の分岐ってどうやる?
A. 表示する要素のタグ内に制御用のif文を書く
Vue.js ではタグ内の v-if ディレクティブに指定した値が評価されて表示が切り替わります。
のコードを例にとります。
App.vue<p v-if="gameover">Game Over</p> <p v-else-if="complete">Clear</p> <p v-else>Please click green cell</p>まず、変数
gameover
の値が真のときにGame Over
が表示されます。
それ以外でかつ変数complete
が真のときにClear
が表示されます。
それ以外のときにはPlease click green cell
が表示されます。素の JavaScript で getElementById で要素を取得してから表示/非表示の切り替えとか面倒なことをしていたのが嘘のようです。Vue.js は素晴らしいです
Q. マインスイーパーのセルの並べ方ってどうやる?
A. v-for で要素を並べて grid-template を使う
Vue.js / CSS の合わせ技です。
App.vue<template> ~略~ <div id="map"> <div v-for="cell in grid_size*grid_size" ~略~ </template> <script> ~略~ #map { --grid-size: 6; display: grid; grid-template-columns: repeat(var(--grid-size), 30px); grid-template-rows: repeat(var(--grid-size), 30px); justify-content: center; align-content: end; padding: auto; margin: auto; } ~略~Vue.js では要素のタグ内に v-for ディレクティブを付けることで、同じ要素を繰り返し配置できます。
@miyauchoi 様の記事を参考にさせていただきました。
200行のVue.jsでスネークゲームを作ったQ. for 文で要素配置したらエラーになるんだけど?
A. v-bind:key が必要
v-for ディレクティブで要素を連続配置したらコンパイルエラーです。本当にありがとうございました。
./src/App.vue Module Error (from ./node_modules/eslint-loader/index.js): error: Elements in iteration expect to have 'v-bind:key' directives (vue/require-v-for-key) at src\App.vue:8:7: 6 | <p v-else>Please click green cell</p> 7 | <div id="map"> > 8 | <div v-for="cell in grid_size*grid_size" | ^ 9 | v-on:click="dig_cell(cell-1)" 10 | :class="{ 11 | cell: true,理由はエラーメッセージに書かれているので一目瞭然ですね!
僕は軽く1時間は悩みましたがね。
のようにコードを直しました。
App.vue<div v-for="cell in grid_size*grid_size" v-on:click="dig_cell(cell-1)" v-bind:key="cell.id" :class="{要素に値をバインドさせるためにidが必須とのことなので、v-for ディレクティブが動作しない怪現象に悩んだら v-bind:key を指定しているかを確認しましょう。
Q. クラスってどうやって作るのかわからない
A. コンポーネント化する(多分)
Google 先生で「Vue.js クラス」って検索しても CSS しかでてきません。
データを構造化したいときはどうするのでしょうか
コンポーネント化するっぽいのですが。
これは諦めて今後の課題としました。
というわけで、セルごとのパラメータ(周囲の地雷の数、地雷フラグ、掘削フラグ)はすべて独立した配列で管理しています。App.vuedata () { return { ~略~ surround_bomb: [], is_bomb: [], is_dig: [], ~略~ }気持ちが悪いですね。早いところ修正したいと思います。
Q. セル要素の二次元座標はパラメータ化する?
A. 座標パラメータは不要で、配列サイズから計算する
配列のインデックスと座標の変換関数を用意します。
配列のインデックスを得るための関数get_index
に渡した座標が二次元座標の領域を超えている場合、存在しないものとして -1 を返しています。App.vueget_cell_x(index){ return (Math.floor(index%this.grid_size)); }, get_cell_y(index){ return (Math.floor(index/this.grid_size)); }, get_index(x,y){ if(x < 0 || y < 0) return -1; else if(x>=this.grid_size || y>=this.grid_size) return -1; return ((y*this.grid_size)+x); }セルの周囲の地雷を数えるための関数
count_surrounding_bombs
に配列のインデックスを渡すと内部で座標に変換し、周囲の 8 セル分をチェックします。App.vueis_bomb_index(index){ return (this.is_bomb[index] == 1); }, is_bomb_cell(x,y){ return (this.is_bomb_index(this.get_index(x,y))); }, count_surrounding_bombs(index) { let x = this.get_cell_x(index); let y = this.get_cell_y(index); let counts = 0; if(this.is_bomb_cell(x-1,y-1)) counts += 1; if(this.is_bomb_cell(x,y-1)) counts += 1; if(this.is_bomb_cell(x+1,y-1)) counts += 1; if(this.is_bomb_cell(x-1,y)) counts += 1; if(this.is_bomb_cell(x+1,y)) counts += 1; if(this.is_bomb_cell(x-1,y+1)) counts += 1; if(this.is_bomb_cell(x,y+1)) counts += 1; if(this.is_bomb_cell(x+1,y+1)) counts += 1; return counts; }感想
Vue.js のデータバインディングって強力ですね。
ミニゲームを簡単に作れてしまうのがすごいです
次はコンポーネント化にチャレンジしてみます。
- 投稿日:2019-12-04T18:18:07+09:00
Vee-validate3を使って、子コンポーネントのエラーを親コンポーネントで表示する
概要
vee-validate3系で親子コンポーネントのvalidationを試してみたらすごい簡単だったのでざっと紹介
サンプルコードはtypescriptになってます使ったもの
- Nuxt 2.8.1
- vee-validate 3.0.3
サンプルコード
設定ファイル
plugins/veeValidate.tsimport Vue from 'vue'; import { ValidationObserver, ValidationProvider, localize, extend } from 'vee-validate'; import ja from 'vee-validate/dist/locale/ja.json'; import { required, max } from 'vee-validate/dist/rules'; Vue.component('ValidationProvider', ValidationProvider); Vue.component('ValidationObserver', ValidationObserver); extend('required', { ...required }); extend('max', { ...max }); localize('ja', ja);親コンポーネント
<validation-observer>
を使ってcomponentを囲むだけ
v-slot
に子コンポーネントのメッセージや、valid
かどうかなどを取得できますpages/test.vue<template> <div class="newPost"> <validation-observer v-slot="{ invalid, errors }"> <template v-if="invalid"> <div v-for="(error, i) in errors['test']" :key="`error${i}`"> <p>{{ error }}</p> </div> </template> <test-form /> </validation-observer> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import { ValidationObserver } from 'vee-validate'; import TestForm from '~/components/TestForm.vue'; @Component({ components: { TestForm } }) export default class Test extends Vue {} </script>子コンポーネント
<validation-provider>
で囲むcomponents/TestForm.vue<template> <div class="test-form"> <validation-provider vid="test" name="メッセージ" rules="required|max:10"> <textarea v-model="message" /> </validation-provider> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import { ValidationProvider } from 'vee-validate'; @Component export default class TestForm extends Vue { message: string = ''; } </script>
vid
を指定することで親コンポーネントでkeyとしてerrorsを判定できるerrors: { test: ['error message1', 'error message2'] }これだけで完了です!
- 投稿日:2019-12-04T18:13:37+09:00
君はVue,Reactの次に来るSvelteを知っているか?
はじめに
この記事はAteam Brides Inc. Advent Calendar 2019 5日目の記事です。
はじめまして、エイチームブライズ新卒1年目の@oekazumaです。最近僕がハマっているSvelteに関して書きたいと思います!
Svelteとは?
SvelteはRich Harris氏によって開発されたコンパイラーでVueやReactのようにブラウザー上でコンポーネント化をするフレームワークではなく*.svelteファイルをhtml, js, cssに変換します。
「すらりとした」という意味を持つ名の通り軽量で高速。
ベンチマークでReactの35倍、Vueの50倍速いです。Svelteの3つの魅力
公式にも書かれている下記の3つを中心に説明していきます!
1. Write less code (より少ないコードを書く)
2. No Virtual DOM (仮想DOMがない)
3. Truly reactive (本当に反応的)Write less code(記述量が少ない)
入力フォームで変数aとbに値を入力し、足して表示するプログラムを例にしてみると
React 442文字
import React, { useState } from 'react'; export default () => { const [a, setA] = useState(1); const [b, setB] = useState(2); function handleChangeA(event) { setA(+event.target.value); } function handleChangeB(event) { setB(+event.target.value); } return ( <div> <input type="number" value={a} onChange={handleChangeA}/> <input type="number" value={b} onChange={handleChangeB}/> <p>{a} + {b} = {a + b}</p> </div> ); };Vue 263文字
<template> <div> <input type="number" v-model.number="a"> <input type="number" v-model.number="b"> <p>{{a}} + {{b}} = {{a + b}}</p> </div> </template> <script> export default { data: function() { return { a: 1, b: 2 }; } }; </script>Svelte 145文字
<script> let a = 1; let b = 2; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p>すごく記述量が少ないことがわかると思います。
書き方自体はVueに似ている部分もあるので既にVueを書いている方だとそんなに違和感なく開発できそうです。No Virtual DOM(仮想DOMがない)
仮想DOMはオーバーヘッドであると言っています。大きな理由としては「実DOMとの差分を計算するのって無料じゃないしオーバーヘッドだよね」というところにあります。
Svelteは仮想DOMを使用せずに同様のプログラミングモデルで十分なパフォーマンスで、状態遷移を考慮することなくアプリを構築できます。
以下の流れでいうとSvelteは1と4だけで済むということです。仮想DOMでHTMLが書き換わるまでの流れ
1. 現在の状態(state)が変わる
2.再レンダリング(仮想DOMの再構成)を実行する
3.実DOMとの差分を計算する
4.実際にHTML(=実DOM)を書き換えるTruly reactive(本当に反応的)
ReactおよびVueは、状態変数が変更されたときに更新する場所を追跡できず、その結果、状態変数が存在するコンポーネント全体とそのすべての子を更新します。
一方、Svelteはアプリケーションを介してデータを追跡し、更新された変数に依存する変数のみを更新できます。さいごに
日本では正直全然話題になっていませんが、海外のフロントエンド界隈では盛り上がっているようでこれから日本でも流行っていくのではないかなと勝手に思っています。
数年後にはVue,Reactと肩を並べて語られている気がする...(^ω^)
今は日本語文献がかなり少ないので盛り上げていってもっと身近にSvelteを感じられるようになれば嬉しいなと思います!
この記事では実践的な部分がなかったのですが、明日に@mkinがsvelte3でToDoリストをチュートリアルと照らし合わせて作るぞ! 【入門編】を書いてくれるので楽しみにしていてください!私たちのチームで働きませんか?
エイチームは、インターネットを使った多様な技術を駆使し、幅広いビジネスの領域に挑戦し続ける名古屋の総合IT企業です。
そのグループ会社である株式会社エイチームブライズでは、一緒に働く仲間を募集しています!上記求人をご覧いただき、少しでも興味を持っていただけた方は、まずはチャットでざっくばらんに話をしましょう。
技術的な話だけでなく、私たちが大切にしていることや、お任せしたいお仕事についてなどを詳しくお伝えいたします!Qiita Jobsよりメッセージお待ちしております!
- 投稿日:2019-12-04T17:54:55+09:00
ゼロからVue.jsでビジュアルリグレッションテストするまでpart3/3
Part1 https://qiita.com/senku/items/07c3e2859ac90c03867a
Part2 https://qiita.com/senku/items/20e21033edd512be1d4d
Part3 ここ前回までにStorybookを整えてきたのは、Storybookから画像を生成するためでした。
今回は画像の生成とそれ以降をやっていきます。テンション爆上げ。Summary
- storycapは神
- reg-suitは神
storycapをいれる
storycapは、Storybookからスクリーンショットを生成するツールです。公式な話もどうぞ。→storybook-chrome-screenshotとzisuiとStorycapと
他にもJestから生成したり色々できるんですが、今回はやりません。storycapインストールします。
$ npm install --save-dev storycappuppeteerが入ってくるのでnode_modulesがでっかくなります。そこへの対処は後で。
とりあえずstorycapを動かしてみましょう。
package.json
のscriptsに、storycapを登録します。package.jsondiff --git a/package.json b/package.json index 75cab63..0df9125 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "vue-cli-service build", "lint": "vue-cli-service lint", "i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'", + "storycap": "storycap --serverCmd \"npm run storybook:ci\" http://localhost:6006 -o actual_images --serverTimeout 1 "storybook:build": "vue-cli-service storybook:build -c config/storybook", "storybook:ci": "vue-cli-service storybook:serve -p 6006 -c config/storybook --ci", "storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook"Storycapのオプションを軽く説明しておきます。詳細はリポジトリのREADMEに書いてあるヨ。
オプション 設定値 説明 なし http://localhost:6006 Storybookが起動しているURLを指定します。 --serverCmd npm run storybook:ci Storybookを起動するためのコマンドを指定します。
Part1で作成したコマンドをここで使います。-o
--outDiractual_images キャプチャ結果の出力先ディレクトリを指定します。 --serverTimeout 120000 Storybookに接続するまでの待ち時間(ミリ秒)です。
デフォルト20秒ですが、起動が遅れた時のためにおまじない的につけてます。今回は使いませんが、以下のオプションも使いがち。
オプション 説明 -V
--viewportキャプチャするviewportを指定します。 -V 1024x768 -V 360x640
のような複数指定もできます。
puppeteerが許可しないviewportにはできない模様。--puppeteerLaunchConfig puppeteerのコンフィグを指定できます。
ブラウザの言語はこのオプションで渡すしかなさそう。日本語にする場合は、エスケープを含めてこんな感じ--puppeteerLaunchConfig \"{\\\"args\\\":[\\\"--no-sandbox\\\",\\\"--disable-setuid-sandbox\\\",\\\"--disable-dev-shm-usage\\\",\\\"--lang=ja\\\"]}\"
画像が出力される
actual_images
は.gitignore
に登録しておきましょう。.gitignorediff --git a/.gitignore b/.gitignore index a0dddc6..5b15dcf 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ yarn-error.log* *.njsproj *.sln *.sw? + +actual_imagesstorycapを走らせてみます。もし
npm run storybook:serve
やnpm run storybook:ci
が動いている場合は停止してから実行してください。$ npm run storycap
actual_images
の下に、各Storyのキャプチャ画像が生成されましたね。これがビジュアルリグレッションテストの元ネタになります。reg-suitをいれる
reg-suitは、2つの画像群を比較して差分をレポートしてくれるツールです。publish先としてS3とかGCS、notify先にGitHubやSlackに対応しています。今回はS3とGitHubを使います。
公式の手順ではグローバルにインストールしていますが、後々のためにプロジェクトローカルにインストールします。
$ npm install --save-dev reg-suit設定を作成するため
reg-suit init
を走らせます。node_modules
の中のファイルを叩きますよ。$ ./node_modules/.bin/reg-suit init
最初のプラグイン選択では、
reg-keygen-git-hash-plugin
,reg-notify-github-plugin
,reg-publish-s3-plugin
を選択しておきます。次は共通の設定です。
irectory contains actual images.
には、storycapで出力したactual_images
を指定しましょう。? Working directory of reg-suit. .reg ? Directory contains actual images. actual_images // ここだけ変更 ? Threshold, ranges from 0 to 1. Smaller value makes the comparison more sensitive. 0
reg-notify-github-plugin
の設定は言われるがままにやります。GitHub認証のためにブラウザが開くので、認証後、通知するリポジトリのClientIDを取得して貼り付けましょう。[reg-suit] info Set up reg-notify-github-plugin: ? notify-github plugin requires a client ID of reg-suit GitHub app. Open installation window in your browser Yes ? This repositoriy's client ID of reg-suit GitHub app // リポジトリのClientIDを入力
reg-publish-s3-plugin
の設定では、AWS関連に環境変数があればバケットの自動作成も行えるみたいです。別途作ったバケットを設定することもできます。[reg-suit] info Set up reg-publish-s3-plugin: ? Create a new S3 bucket Yesこんな感じの
regconfig.json
が生成されれば完了です。
reg-suit init
をせずに直接作成しても大丈夫です。その場合は.gitignore
に.reg
を追加されていないので、手動で追加しておきましょう。regconfig.json{ "core": { "workingDir": ".reg", "actualDir": "actual_images", "thresholdRate": 0, "ximgdiff": { "invocationType": "client" } }, "plugins": { "reg-keygen-git-hash-plugin": true, "reg-notify-github-plugin": { "clientId": "環境によってちがいます" }, "reg-publish-s3-plugin": { "bucketName": "バケット名" } } }プラグインのオプションは色々設定できます。ここでは
reg-publish-s3-plugin
のオプション例について軽く触れます。詳しくはreg-publish-s3-pluginのREADMEをみてください。
pathPrefix
の指定があると、そのパスの下にファイルが配置されます。"pathPrefix": "hoge"
ならS3BUCKET/hoge/COMMITID...
って感じのパスになります。一つのバケットを複数のテストで使い回す場合はどうぞ。
customDomain
の指定はでnotifyが通知するレポートの公開URLを調整できます。regconfig.jsonの編集"reg-publish-s3-plugin": { "bucketName": "バケット名",, "pathPrefix": "配置先のPrefix" "customDomain": "レポート公開URLのFQDN" }S3互換ストレージを使う場合は
sdkOptions
でいろいろ設定できます。エンドポイントとか変えればいいですね。ここはAWS CLIのマニュアルを読んだほうがいいのかな。regconfig.jsonの編集"reg-publish-s3-plugin": { "bucketName": "バケット名", "sdkOptions": { "endpoint": "エンドポイントのURL" } }設定が完了したので、reg-suitを実行するためのスクリプトを作っておきましょう。
package.jsondiff --git a/package.json b/package.json index 953a07c..bbc6c2b 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "lint": "vue-cli-service lint", "i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'", "storycap": "storycap --serverCmd \"npm run storybook:ci\" http://localhost:6006 -o actual_images --serverTimeout 1 + "reg-suit": "reg-suit run", "storybook:build": "vue-cli-service storybook:build -c config/storybook", "storybook:ci": "vue-cli-service storybook:serve -p 6006 -c config/storybook --ci", "storybook:serve": "vue-cli-service storybook:serve -p 6006 -c config/storybook"これで準備ができました。
GitHubのmasterリポジトリに、ここまでのコミットをpushしておきます。(重要)
レポートを出していこう
まずは現在のコミット(masterの最新コミット)で生成したキャプチャ画像を、S3へpublishします。
今のmasterの画像をアップロード
すでに
actual_images
は生成されているので、reg-suit run
を実行するだけです。$ npm run reg-suit実行後にS3バケットを確認すると、コミットのハッシュから始まる画像ファイルと、レポートのindex.htmlとその他ファイルがアップロードされています。
index.htmlを開くとこんな感じ。コンポーネントを変更してPRをつくる
レポートをわかりやすくするため、見た目の差分があるPullRequestを作ります。
とりあえずブランチを切り替えましょう。$ git checkout -b testなんでもいいんですが
HelloI18n.vue
あたりを変更します。src/components/HelloI18n.vuediff --git a/src/components/HelloI18n.vue b/src/components/HelloI18n.vue index 57ad691..21156c9 100644 --- a/src/components/HelloI18n.vue +++ b/src/components/HelloI18n.vue @@ -11,7 +11,7 @@ export default { <i18n> { "en": { - "hello": "Hello i18n in SFC!" + "hello": "Hello i18n in SFC!!!!!" } } </i18n>コミットしてpush。
$ git add src/components/HelloI18n.vue $ git commit -m "test commit" $ git push origin testGitHubにPullRequestを作成します。
参考までに、この時点でリモートブランチ(GitHub)はこんな状態になってます。
- masterブランチの最新コミット
- testブランチの最新コミット(
masterブランチの最新コミット
からfork)
- testブランチの最新コミットから作られたPullRequest
reg-suitのpublish先のS3バケットには、
masterブランチの最新コミット
に対応するキャプチャ画像だけがアップロードされています。PRにレポートを送る
現在のコミット(
testブランチの最新コミット
)のキャプチャ画像をstorycapで生成して、reg-suitで比較しましょう。$ npm run storycap $ npm run reg-suitこの作業によって、
- S3バケットに
testブランチの最新コミット
に対応するキャプチャ画像がアップロードされ、- reg-suitが
masterブランチの最新コミット
とtestブランチの最新コミット
の画像を比較して、testブランチの最新コミット
と関連するPullRequestにコメントを投稿されます。PullRequestを見てみましょう。なんか書き込まれてますね。
コメントのリンク先のレポートでも差分が確認できます。
reg-suitはここまで自動でやってくれます。神。
基本的な動きはこれで完成です。後はCIを考えましょう。CI戦略
CI戦略のために必要な情報を整理しておきます。
画像の比較のためには、PullRequestのfork元のコミットでstorycap+reg-suitが実行された(図A)上で、PullRequest自体の最新のコミットでstorycap+reg-suitが実行される(図B)必要があります。
master -->A - - - - - - - ↓ branch ↑ PR branch ---------->BGitHub flowを前提にすると、以下のタイミングでキャプチャの取得とreg-suitによる判定を行えばよさそうです。
- masterブランチが進んだとき(masterへのmergeが起きたとき)
- PullRequestが作成されたとき
- PullRequestのブランチがpushされたとき
reg-notify-github-plugin
は賢いので、これらのトリガーでnpm run storycap
とnpm run reg-suit
が実行するだけで、PullRequestにコメントをつけるようになります。S3の容量が許すなら、全てのコミットに実行してもおそらく問題ありません。
reg-notify-slack-plugin
とかだとPullRequestの存在確認ができないので、どのパターンか独自に判断しないと常に通知されることになります。storycapをpackage.jsonから外したい
CIなんてDockerで走らせればいいんですよ!というわけでおもむろにstorycapをuninstallします。設定ファイルは残しておきます。
reg-suitをプロジェクトにインストールしたのはこのためでした。$ npm uninstall storycap次は
Dockerfile
を作ります。storycapをインストールしつつ、npm run storycap
とnpm run reg-suit
を実行させます。Puppeteer公式のRunning Puppeteer in Dockerを参考にしました。
宗教上の理由によりイメージを使い回さずdocker-build
で完結させます。
また、SSH鍵でGitHubにアクセスできると信じて、experimentalな機能でSSH鍵を渡しています。# syntax = docker/dockerfile:experimental FROM node:10-slim ARG AWS_ACCESS_KEY_ID ARG AWS_SECRET_ACCESS_KEY ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ && apt-get update \ && apt-get install -y ssh git google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* RUN usermod -aG audio,video node RUN mkdir -p -m 0600 ~/.ssh RUN --mount=type=secret,id=ssh ssh-keyscan -H github.com >> ~/.ssh/known_hosts COPY . /home/node WORKDIR /home/node RUN npm ci RUN npm install storycap RUN npm run storycap RUN --mount=type=secret,id=ssh npm run reg-suit次は
docker build
するぞい。前述の宗教上の理由により--force-rm
を付けます。experimentalな機能を有効にするためのDOCKER_BUILDKIT
環境変数もバッチリだ。AWS用の環境変数はちゃんと--build-arg
で渡すんだ。$ DOCKER_BUILDKIT=1 docker build -t reg-suit-gambaruzoi -f Dockerfile --force-rm \ --build-arg AWS_ACCESS_KEY_ID=あなたのアクセスキー \ --build-arg AWS_SECRET_ACCESS_KEY=あなたのシークレットアクセスキー .これで環境を汚染せずに
npm run storycap
もnpm run reg-suit
も実行できます。ヨカッタネ。現場からは以上です。
- 投稿日:2019-12-04T17:52:22+09:00
Nuxt.js with TypeScript and Composition API
はじめに
この記事は Nuxt.js Advent Calendar 2019 6日目の記事です。
皆さん、Composition API 使ってますか? Composition API は現在開発中で、2020年1Qにもリリース予定の Vue.js 3.0 からデフォルトで搭載されることになっている新しいコンポーネントの実装方法です。Composition API を使うことで、散らかりがちだった実装を一箇所に固めることができたり、今まで組み合わせにくかった TypeScript との連携が比較的やりやすくなったりします。Vue.js 2.x にもプラグインとして導入すれば今すぐ使い始めることができるようになっています。
この記事では、その Composition API を Nuxt.js に導入し、さらに TypeScript を使ってなるべく1型の力を借りて型安全なフロントエンドを作成する方法を紹介します。
環境構築
諸注意
今回は
npm
ではなくyarn
を使っていきます。npm
派の方は適宜コマンドを読み替えてください。また、コマンドは全てmacOS Catalinaでの実行例となっています。Linuxなどをお使いの方はパッケージマネージャーなどが出てきた際に、適切なコマンドに読み替えてください。準備
とりあえず
node
とyarn
を入れておきます。エディタはVSCodeやWebStormなどお好みで。$ brew install node yarnプロジェクトの初期化
yarn create
コマンドでざっくり作っていきます。create-nuxt-app
ではまだTypeScriptを使ったプロジェクトの生成には対応していないので、いったんJavaScript用のプロジェクトを作ります。各種選択肢はお好みでOKです。一応どれを選んだかはここに書いておきます。% yarn create nuxt-app composition-sample yarn create v1.19.2 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Installed "create-nuxt-app@2.12.0" with binaries: - create-nuxt-app create-nuxt-app v2.12.0 ✨ Generating Nuxt.js project in composition-sample ? Project name composition-sample ? Project description My primo Nuxt.js project ? Author name Aruneko ? Choose the package manager Yarn ? Choose UI framework Vuetify.js ? Choose custom server framework None (Recommended) ? Choose Nuxt.js modules Axios ? Choose linting tools ESLint, Prettier ? Choose test framework None ? Choose rendering mode Universal (SSR) ? Choose development tools (Press <space> to select, <a> to toggle all, <i> to i nvert selection)TypeScript対応
基本的に公式サイトのやり方に従えばOKですが、簡単に手順を記しておきます。
まずはビルド時にTypeScriptを有効化する設定です。
@nuxt/typescript-build
を入れてから設定ファイルを書き換え、tsconfig.json
を生成するところまでやっておきます。これはあくまでビルド時にしか使わないので、開発用依存パッケージとしてインストールします。$ yarn add --dev @nuxt/typescript-buildインストールが終わったら
nuxt.config.js
を書き換えます。まずbuildModules
に'@nuxt/typescript-build'
を書き加えましょう。nuxt.config.jsexport default { // 前略 buildModules: [ // Doc: https://github.com/nuxt-community/eslint-module '@nuxt/typescript-build', // ここを追加 '@nuxtjs/eslint-module', '@nuxtjs/vuetify' ], // 後略 }そしてこの際なので
nuxt.config.js
をnuxt.config.ts
にリネームして、型を付けます。default export
しているあたりをいったんnuxtConfig
変数に入れて型を付け、後からエクスポートするようにします。$ mv nuxt.config.js nuxt.config.tsnuxt.config.tsimport { Configuration } from '@nuxt/types' import colors from 'vuetify/es5/util/colors' const nuxtConfig: Configuration = { // 中略 } module.exports = nuxtConfig最後に次のような内容で
tsconfig.json
をプロジェクトのトップ階層に設置すれば完了です。tsconfig.json{ "compilerOptions": { "target": "esnext", "module": "esnext", "moduleResolution": "node", "lib": [ "esnext", "esnext.asynciterable", "dom" ], "esModuleInterop": true, "allowJs": true, "sourceMap": true, "strict": true, "noEmit": true, "baseUrl": ".", "paths": { "~/*": [ "./*" ], "@/*": [ "./*" ] }, "types": [ "@types/node", "@nuxt/types" ] }, "exclude": [ "node_modules" ] }続いてランタイム時にもTypeScriptが有効になるようにしていきます。これは
@nuxt/typescript-runtime
を導入して、package.json
を書き換えるだけのお手軽作業です。こちらはランタイムなので、開発用依存としないように注意しましょう。$ yarn add @nuxt/typescript-runtime
package.json
の変更ですが、scripts
セクションを探してその中にある4箇所のnuxt
コマンドを全てnuxt-ts
コマンドに置き換えるだけです。package.json"scripts": { "dev": "nuxt-ts", "build": "nuxt-ts build", "start": "nuxt-ts start", "generate": "nuxt-ts generate", "lint": "eslint --ext .js,.vue --ignore-path .gitignore ." }最後にlintの設定もしましょう。せっかくESLintとPrettierをプロジェクト初期化時に有効にしてありますからね。まずは専用のESLint用定義を入れていきます。
$ yarn add -D @nuxtjs/eslint-config-typescriptインストールが終わったら
.eslintrc.js
でその定義を有効化しておきます。parser
を TypeScript のものに変更し、extends
セクションで導入した定義を有効化してください。.eslintrc.jsmodule.exports = { // 前略 parserOptions: { // ここを変更 parser: '@typescript-eslint/parser' }, extends: [ '@nuxtjs', 'prettier', 'prettier/vue', 'plugin:prettier/recommended', 'plugin:nuxt/recommended', '@nuxtjs/eslint-config-typescript'. // ここを追加 ], // 後略 }最後に
package.json
のscripts
セクションにあるlint
コマンドで.ts
ファイルに対してもlintが走るように対象となる拡張子を追加すれば完了です。package.json"scripts": { "dev": "nuxt-ts", "build": "nuxt-ts build", "start": "nuxt-ts start", "generate": "nuxt-ts generate", "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ." }Composition API の導入
まずは yarn でインストールしてしまいましょう。
$ yarn add @vue/composition-api
インストールできたら plugins ディレクトリの下に
composition-api.ts
を作って、Composition API を有効化します。plugins/composition-api.tsimport Vue from 'vue' import VueCompositionApi from '@vue/composition-api' Vue.use(VueCompositionApi)
nuxt.config.ts
でプラグインを有効化することもお忘れなく。nuxt.config.tsconst nuxtConfig: Configuration = { // 前略 plugins: ['@/plugins/composition-api'], // 後略 }これで導入は完了です。
Composition API による実装の例
コンポーネントの作成
ではVuetifyを選択することによってデフォルトで生成されたコンポーネントを改造する形で、Composition API をどうやって Nuxt.js に組み込んでいくか説明していきます。
まずは
layouts/default.vue
を確認してみましょう。デフォルトの実装は以下のようになっています。なお下記コードでは<script>
内を抜粋しています。layouts/default.vueexport default { data() { return { clipped: false, drawer: false, fixed: false, items: [ { icon: 'mdi-apps', title: 'Welcome', to: '/' }, { icon: 'mdi-chart-bubble', title: 'Inspire', to: '/inspire' } ], miniVariant: false, right: true, rightDrawer: false, title: 'Vuetify.js' } } }これを Composition API + TypeScript で書き換えていきます。まず
<script>
タグにlang="ts"
属性を追加して<script lang="ts">
としてこの中に書かれたコードがTypeScriptであるということを明示してから、コードを書き換えていきます。まずは書き換えた後のコード全体を掲載しますが、おおよそは Composition API のサンプルコード通りとなります。layouts/default.vue<script lang="ts"> // (1) 必要なものをインポート import { createComponent, ref } from '@vue/composition-api' type Item = { icon: string title: string to: string } // (2) createComponentによるコンポーネントの作成 export default createComponent({ setup() { // (3) refを使ったリアクティブ値の生成 const clipped = ref(false) const drawer = ref(false) const fixed = ref(false) // (4) 型の明示 const items = ref<Item[]>([ { icon: 'mdi-apps', title: 'Welcome', to: '/' }, { icon: 'mdi-chart-bubble', title: 'Inspire', to: '/inspire' } ]) const miniVariant = ref(false) const right = ref(false) const rightDrawer = ref(false) const title = ref('Vuetify.js') // (5) Template内で使うものだけまとめて返す return { clipped, drawer, fixed, items, miniVariant, right, rightDrawer, title } } }) </script>コンポーネントの作成
Composition API では、
(2)
で行っているようにcreateComponent
関数によってコンポーネントを作成します。これは@vue/composition-api
から提供されるので、(1)
の箇所で事前にインポートしておきます。さらにsetup
を使ってリアクティブな値の定義を行っていきます。今まではdata()
を使ってこれらの値を定義していましたが、Composition API では変更されていますので注意してください。リアクティブ値
単体のリアクティブ値は
ref
を使って作成します。投入した初期値によって型推論が行われるため、(3)
のように通常は型を明示する必要はありません。ただし、明示的に書きたい場合は(4)
でやっているように<>
を使えばOKです。一方、型の明示が必須な場合もあります。例えば初期値では空配列だけれども後から値が変化するような場合や、
null
が入る可能性があるリアクティブ値を定義する場合には型を明示しなければなりません2。例えば以下のようにすると良いでしょう。const userList = ref<User[]>([]) const nullableValue = ref<User | null>(null)
setup
関数の最後で<template>
内で使う値だけまとめて返してあげれば完了です。これで少なくとも<script>
内では TypeScript による型チェックが働くようになります3。Nuxt固有の拡張機能
Nuxt.js には
asyncData
など便利機能が色々と備わっています。ですが、Composition API と組み合わせた場合これらの機能を使うことは現時点では非常に難しいです。そこで Composition API にある代替機能を使ってこれらの機能を実装していきましょう。ある程度はそれで機能の代わりを果たすことができます。非同期値の取得
API からデータを引っ張ってくるなど非同期な値を持ってきたいことはしばしばあると思います。ただ先にも述べたように
asyncData
は封印されていますので、別の手段を用います。 Composition API にはwatch
という関数が用意されており、これを使ってasync
をラップしてあげることでページ遷移してきたときに1回だけ呼んであげることができます。本来watch
は名前の通りリアクティブ値を監視して、変更があったときに指定した関数を動かすための関数ですが、なぜか4こういった使い方もできるようになっています。const users = ref<User[]>([]) watch(async () => { users.value = await fetch('http://api.example.com/users/') })レイアウトやプロパティの指定
これは従来のやり方がそのまま使えます。
props
もOKです。createComponent
の中でそれぞれに対応した Key-Value を設定してあげましょう。import { createComponent } from '@vue/composition-api' export default createComponent({ layout: 'empty', props: { user: { type: Object, default: null } }, setup() { // 略 } })Nuxt Axios / Auth Moduleとの連携
これも問題なくできますが、少し準備が必要です。まず型付けを正しく行うために、
nuxt.config.ts
を編集します。なお、Auth Module を利用する際は事前にパッケージと型定義を導入しておいてください。あとは型定義をインポートして、declare module
でどのプロパティにどの型を適用するか定義してあげましょう。$ yarn add @nuxtjs/auth $ yarn add -D @types/nuxtjs__authnuxt.config.tsimport { NuxtAxiosInstance } from '@nuxtjs/axios' import { Auth } from 'nuxtjs__auth' const nuxtConfig: Configuration = { // 省略 } // ここのひとかたまりを追加 declare module 'vue/types/vue' { interface Vue { $auth: Auth $axios: NuxtAxiosInstance } }使う側では
setup
メソッドの第2引数に渡されてくるcontext
引数に実装が詰め込まれているので、そこから読むようにします。root
プロパティの中に先ほどinterface Vue
で指定したプロパティが生えているので、呼んであげるだけです。補完もバッチリ効きますので、便利に使うことができます。export default createComponent({ setup(_props, context) { const users = ref<User[]>([]) watch(async () => { // Axios Module を呼ぶ例 users.value = await context.root.$axios.$get('/users') }) const login = async () => { // Auth Module を呼ぶ例 await context.root.$auth.loginWith(/* ユーザー名とパスワードを送信 */) context.root.$router.push('/') } return { users, login } } })
context.root.$router.push
なんかをしれっと使ってますが、だいたい欲しいもの($el
や$store
など)はcontext.root
に生えているので、困ったらまずここを探してみると良いでしょう。何が入っているかは補完機能が全部教えてくれます。ちなみにemit
はcontext.emit
とcontext
直下にぶら下がっています。おわりに
ここまで Nuxt.js に Composition API を導入し、TypeScript で型を付けながら実装する方法を紹介してきました。Composition API 自体がまだちょっと洗練されておらず、特に Nuxt.js が用意している専用機能に関するサポートに関しては手つかずの状況ではあります。しかし、あんまり凝ったことをしないのであれば十分使えるかなといった手応えです。来年Q1にも予定されている Vue.js 3.0 とそれをベースにした新しい Nuxt.js を楽しみにしつつ、年を越したいと思います。
- 投稿日:2019-12-04T17:01:28+09:00
Jestテスト実行時「Unknown custom element」が発生した場合の対処法
問題の現象
Jestテスト実行時、以下のようなVuetifyコンポーネントの
Unknown custom element
の警告が出た場合$ yarn test yarn run v1.17.3 $ jest --config jest.config.js PASS test/Button.spec.js Button ✓ is a Vue instance (18ms) console.error node_modules/vue/dist/vue.runtime.common.dev.js:621 [Vue warn]: Unknown custom element: <v-btn> - did you register the component correctly? For recursive components, make sure to provide the "name" option. found in ---> <Button> <Root> Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.588s Ran all test suites. ✨ Done in 5.91s.前提
package.json
package.json{ "scripts": { "test": "jest --config jest.config.js", }, }コンポーネント
components/Button.vue<template> <v-btn large color="primary" @click="log"> ボタン </v-btn> </template> <script> export default { name: 'Button', methods: { log() { console.log('hoge') } } } </script>テストコード
test/Button.spec.jsimport { mount } from '@vue/test-utils' import Button from '@/components/Button.vue' describe('Button', () => { test('is a Vue instance', () => { const wrapper = mount(Button) expect(wrapper.isVueInstance()).toBeTruthy() }) })対応内容
- JestがVuetifyコンポーネントを認識できるように、警告が出たタグのモックを作成する
test/jest.setup.jsimport Vue from 'vue' import VueTestUtils from '@vue/test-utils' Vue.config.silent = true // Mock Vuetify components VueTestUtils.config.stubs['v-btn'] = '<button><slot /></button>'jest.config.jsmodule.exports = { setupFiles: ['<rootDir>/test/jest.setup.js'], // Load Mock setting moduleNameMapper: { '^@/(.*)$': '<rootDir>/$1', '^~/(.*)$': '<rootDir>/$1' }, transform: { '^.+\\.js$': 'babel-jest', '.*\\.(vue)$': 'vue-jest' }, moduleFileExtensions: ['js', 'json', 'vue'] }参考サイト
- 投稿日:2019-12-04T16:24:29+09:00
条件によってテキストが変わるコンポーネントを分割して共通スタイルを適用する
こちらは、弁護士ドットコム Advent Calendar 2019 - Qiita の 21 日目の記事です。
要望
条件によって中身が変わるからコンポーネントは分けたいけど、スタイルは共通化したい。
JS によるロジックは特にない。具体的なケース
ユーザーの権限によってテキストが変わるコンポーネント。
共通のスタイルを使用したいのですが、一つのコンポーネントにまとめようとすると
v-if
の嵐になってしまい、可読性がかなり落ちてしまいます。イメージ<p v-if="condition" class="style1">◯◯権限を持っているので、☓☓が出来ます。</p> <p v-else class="style1">◯◯権限を持っていないので、☓☓が出来ません。</p> <p v-if="condition" class="style2">△△権限を持っているので、□□が出来ます。</p> <p v-else class="style2">ただし△△権限を持っていないので、□□が出来ません。</p> <p class="style3">どのユーザーも☆☆は出来ます。</p> <!-- どの権限でも同じ -->解決策
条件ごとにコンポーネントを分け、条件分岐・共通スタイル定義を親コンポーネントで行います。
スタイルを共通化させると、
v-if
をコンポーネントの切り替えの 1 つだけにできるので可読性が上がります。実装
親コンポーネントにスタイルを持たせて、条件によって子コンポーネントを切り替えるようにします。
親コンポーネント<template> <div class="wrapper"> <component :is="componentName" v-bind="propData" /> </div> </template> <script> import Component1 from './Component1.vue' import Component2 from './Component2.vue' export default { components: { Component1, Component2 }, props: { condition: { type: String, required: true }, username: { type: String, required: true } }, data() { return { propData: { username: this.username } } }, computed: { componentName() { switch (this.condition) { case 'cond1': return Component1 case 'cond2': return Component2 default: return null } } } } </script> <style scoped> .wrapper >>> .style1 { font-size: 20px; } .wrapper >>> .style2 { font-size: 16px; } .wrapper >>> .style3 { font-size: 16px; font-weight: bold; } </style>Component1.vue<template> <div> <h1>{{ username }}さん</h1> <p class="style1">閲覧権限を持っているので、プロパティの閲覧が出来ます。</p> <p class="style2">ただし編集権限を持っていないので、プロパティの編集が出来ません。</p> <p class="style3">どのユーザーもログインは出来ます。</p> </div> </template> <script> export default { props: { username: { type: String, required: true } } } </script>Component2.vue<template> <div> <h1>{{ username }}さん</h1> <p class="style1">閲覧権限を持っているので、プロパティの閲覧が出来ます。</p> <p class="style2">編集権限を持っているので、プロパティの編集が出来ます。</p> <p class="style3">どのユーザーもログインは出来ます。</p> </div> </template> <script> export default { props: { username: { type: String, required: true } } } </script>注意
>>>
(ディープセレクタ)は子孫要素すべてを対象とするので、scoped にしているからといって同じクラス名を使っているとスタイルがあたってしまいます。
コンポーネント名を prefix として付ける、BEM などの命名規則を適用する、などの対策が必要です。また、SCSS 等を使用している場合は、
>>>
ではなく/deep/
を使用する必要があります。他の選択肢
CSS を外部ファイル化して
@import
で読み込むHTML, CSS, JavaScript が一緒に管理できる SFC の利点が消えてしまうので見送りました。
「全く違う場所で利用するコンポーネントだけどスタイルは共通化させたい」というときは Minxin 的に使えるかもしれません。共通化しない
個々のコンポーネントとして取り扱えたほうがいいことが往々にしてあるので、選択肢としてはありだと思います。
今回は、共通に定義したスタイルを片方だけ変更するということが基本的にないことがわかっていたので、共通化したほうが後々楽だと判断しました。あとがき
自分が実装したケースではこのやり方がフィットしましたが、子コンポーネント側のコードが二重管理になってしまうので、ケースごとに検討することが必要だと思います。
他に同じような悩みを抱えている人の糧になれば幸いです。
参考
- 投稿日:2019-12-04T15:46:52+09:00
Leaflet + Vue.js で 地図の表示位置を切り替えられるサンプルサイトを作ってみた
この記事は、 North Detail Advent Calendar 2019 の4日目の記事です。
弊社は最近地図を扱う業務が増えたので、ここ数ヶ月ほど学習がてらに "Leaflet" を使ってみてます。
もともと "Vue.js" でのサイト構築も多いので、両方を合わせるとどうなるか、というのを試してみたいと思います。デモサイト: https://sample-leaflet-tacck.netlify.com/
ソースコード: https://github.com/tacck/sample-leaflet画面でみると
初回は "位置情報提供の許可" が求められるので、 "許可" の方を選択してください。
「現在地」のボタンをクリックすると、ブラウザ経由で取得した位置情報の地点を地図上で表示します。(ここは弊社)
解説
Vue.js や Leaflet.js それぞれの詳しい扱い方は専用のサイトにお任せします。
ここでは、少し工夫したところを。
情報の更新
現在地取得と反映
Mainコンポーネントの中に、 Tabコンポーネント(ボタンのある領域) と Mapコンポーネント(地図のある領域) を持つ形にしています。
Mapコンポーネントは、Mainコンポーネントから与えられた緯度経度を中心に地図を表示する(変更があれば更新する)だけ、という作りです。緯度経度の変更されるタイミングは、次の二つになります。
- ボタンが押された場合
- ボタンが「現在地」の時にブラウザから与えられた位置情報が
navigator.geolocation.watchPosition()
経由で更新された場合ボタンが押された場合
Tabコンポーネント の中には TabButtonコンポーネント が二つ並んでいます。
TabButtonコンポーネント は、クリックされたら "クリックされたよ" というイベントを Tabコンポーネントに返すだけです。
どちらのボタンがクリックされたかは、 Tabコンポーネント の責任範囲として、ここからさらに Mainコンポーネント へイベントを投げます。
Mainコンポーネントへは "どちらのボタンが押されたか" がわかるものを引数として渡しています。Mainコンポーネントは、受け取った値を使って緯度経度を更新し、 Mapコンポーネントへ(props経由で)情報を渡します。
TabButton.vue(snip) <b-button @click="$emit('click')" size="lg" :variant="status"> (snip)Tab.vue<TabButton @click="clickStation" id="sation" :status="stationVariant" label="札幌駅" ></TabButton> (snip) clickStation() { this.stationVariant = "info"; this.hereVariant = ""; this.$emit("changeActive", "static"); },Main.vue(snip) <Tab @changeActive="setActive"></Tab> (snip) setActive: function(location) { console.log("active:" + location); this.lat = this.geosObject[location][0]; this.lon = this.geosObject[location][1]; }, (snip)地図へ反映
Mapコンポーネントでは、緯度経度の情報が更新されたこと(正確にはpropsで持つ 'lat' の値が更新されたこと)がわかるように、
watch
を使っています。Main.vue(snip) <Map :lat="lat" :lon="lon"></Map> (snip)Map.vue(snip) methods: { updateCurrentPosition: async function() { if (this.map) { this.map.panTo([this.lat, this.lon]); } if (this.currentCircle) { this.currentCircle.setLatLng([this.lat, this.lon]); } if (this.currentPoint) { this.currentPoint.setLatLng([this.lat, this.lon]); } } }, watch: { lat: function() { this.updateCurrentPosition(); } } (snip)まとめ
Vue.js のコンポーネントの一つとして、 Leaflet を使った地図を使うことが簡単にできました。
Mapコンポーネントの外から位置情報を与えられるので、各種サービスと連携して地図表示することも気軽にできそうです。
- 投稿日:2019-12-04T13:57:15+09:00
Vue/Nuxt開発効率を3倍にするVSCode拡張機能セット
この記事を読むと
VSCode拡張機能を入れて、下記のようなことが実現します
・VSCodeとは別にterminal(Command Line)の画面を開かなくても、VSCode内から直接terminal操作ができる(例えばnpm run devも)
・全てのカッコ()[]{}が種類別に色分けされる
・インデントが一眼で分かる
・htmlタグの開始タグと終了タグを同時に修正できる
・Vue.jsやNuxt.jsの構造やファイル操作が楽になる
etc...なぜこの記事を書いたのか
VSCodeは素晴らしいテキストエディタです。
特に、Vue.jsやNuxt.jsを用いて開発している方にとっては最も有力な選択肢でしょう(そして、その選択は間違いではないと保証できます)。さて、VSCodeはそのままでも素晴らしいエディタですが、使用するフレームワークや用途に応じて拡張機能を入れるとさらに、その卓越した機能性を発揮します。VSCodeがなぜ世界中のエンジニアから信頼され、愛されるのかを実感することができるでしょう。
特に、(Nuxtエンジニアとしてまだまだ未熟な)私にとっては、もはやVSCode拡張機能なしの開発は考えられません。
拡張機能を入れてからコードミスが激減し、不要なストレスから開放されたので、長時間のコーディングにおいても疲れを感じにくくなりました。しかしながら、VSCodeに拡張機能を入れていない方も少なくないようです。
「エディタの設定を弄れない駆け出しエンジニア」を思い浮かべたでしょうか?むしろ逆の方を想定しています。WEB開発者やフロントエンジニアとして優れた技術や十分な経験があるからこそシンプルな機能構成を好み、コードを書く補助機能である拡張機能は必要なかったのかもしれません。しかしながら、どんなにハイレベルなエンジニアの方にとってもやはり拡張機能は大きな助けとなると考えます。没入するような長時間の集中のために、少しでも快適な開発環境を整えることは有意義なことです。
そこで今回は、Vue.jsやNuxt.jsを用いた開発を行う方を対象に、VSCodeの拡張機能たちを紹介させて頂きます。
VSCodeへの拡張機能の追加方法
やり方色々
VSCodeは直感的な操作で、簡単に拡張機能を入れることができます。
「VSCode内から直接追加」、「ブラウザ上からダウンロード」、「Command Lineから操作」等々やり方はありますが、基本的にはVSCode内から直接追加するのが一番簡単です。VSCode内から直接追加する方法
画面左下の歯車をクリックして、「extension」(日本語設定なら「拡張機能」)を選択しましょう。
すると、EXTENSIONS:MARKETPLACEの検索窓が表示されます。
例えば「Vetur」を追加したい場合は,「Vetur」と検索、お目当ての「Vetur」が表示されたらinstallすればOKです。簡単ですね。拡張機能の紹介
私は下記を全て入れていますが、必要に応じて取捨選択してください。
一応、ブラウザ上のMarketplaceへのリンクを貼っていますが、VSCode内から直接インストールして頂くのが早いかと思います。基本編
Japanese Language Pack は VS Code にローカライズされた UI を提供します。
日本語化ができます!VSCodeは平易な英語しか使用されていませんが、やはり日本語表示の方が操作が早くなります。
A VSCode extension to fast open html file in browser.
HTMLファイルをブラウザで開けるようになります。
The Material Icon Theme provides lots of icons based on Material Design for Visual Studio Code.
アイコンが分かりやすくなります。
インストールした後、設定を変更する必要があります。インストール後のメッセージダイアログに従うのが一番簡単です。コード整形編
A VS Code extension that allows you to… highlight trailing spaces and delete them in a flash!
インデントをハイライトしてくれます。
A visual source code plugin for maintaining local history of files.
ファイルの変更履歴が保存され、変更前との比較や、復元ができるようになります。安心ですね。
GitLens supercharges the Git capabilities built into Visual Studio Code. It helps you to visualize code authorship at a glance via Git blame annotations and code lens, seamlessly navigate and explore Git repositories, gain valuable insights via powerful comparison commands, and so much more.
gitとVSCodeを強力に連携してくれます。
git関連の拡張機能もたくさんあるのですが、やはりGitLensが一番良いかなあと思います。Automatically rename paired HTML/XML tag, same as Visual Studio IDE does.
ペアとなるHTMLタグを自動でrenameしてくれます。
Automatically add HTML/XML close tag, same as Visual Studio IDE or Sublime Text does.
HTMLの終了タグを自動で追加してくれます。
This extension allows matching brackets to be identified with colours. The user can define which characters to match, and which colours to use.
カッコ毎に色を分けてハイライトしてくれます。こちらを入れてから、VueやNuxtで(){}のネストを多用しても混乱することがなくなりました。
Beautify javascript, JSON, CSS, Sass, and HTML in Visual Studio Code.
javascriptやJSON, CSS, Sass, HTMLコードを選択し、command palletからBeautifyを実行すると綺麗に整えてくれます。
This extension contains code snippets for JavaScript in ES6 syntax for Vs Code editor (supports both JavaScript and TypeScript).
Snippetsとはよく使うコードを自動で予測・保管する機能のことです。
コードを一文字一文字全て書かなくても良いのは本当に楽ですし、コードのミスも減ります!
VSCodeには最初からHTML等ある程度のSnippetsはついていますが、もっと便利になってくれます。
JavaScript(ES6)のSnippetsは是非追加しておきましょう。Vue.js・Nuxt.js編
Run code snippet or code file for multiple languages: C, C++, Java, JavaScript, PHP, Python, Perl, Perl 6, Ruby, Go, Lua, Groovy, PowerShell, BAT/CMD, BASH/SH, F# Script, F# (.NET Core), C# Script, C# (.NET Core), VBScript, TypeScript, CoffeeScript, Scala, Swift, Julia, Crystal, OCaml Script, R, AppleScript, Elixir, Visual Basic .NET, Clojure, Haxe, Objective-C, Rust, Racket, Scheme, AutoHotkey, AutoIt, Kotlin, Dart, Free Pascal, Haskell, Nim, D, Lisp, Kit, V, and custom command
VSCode内から各種対応言語やcommandを実行できます。
デバッグでさえVSCode内で完結してしまいます。個人的にはこれが一番便利かもしれません。Vue tooling for VS Code, powered by vue-language-server.
言わずと知れたVeturですね。VSCodeでVueファイルを操作しているとレコメンドのメッセージが表示されるので、ご存知の方も多いでしょう。VueやNuxtを開発するならもはや必須の拡張機能です。
親を質に入れてでも追加しておきたい(※無料です)。This extension extends Vue code editing with Go To Definition and Peek Definition support for components and filenames in single-file components with a .vue extension. It allows quickly jumping to or peeking into files that are referenced as components (from template), or as module imports (from script).
選択したコンポーネントやファイル名について、「Vueファイルのどこで定義されたか」や「定義しているコード」を表示してくれます。また、どこでも定義されていないのに使われている変数名を特定するのにも役立ちます。
これも本当に便利ですね。Ability to duplicate files and directories in VS Code.
VSCode内で簡単にファイルやディレクトリの複製が可能になります。
Vueファイルをテンプレとして使いまわしたりするときに便利ですね。
Viewing documentation [Vue.js, Vuex, Vue Router, Vue SSR, Vuetify, Nuxt.js, VuePress] in the VS Code!
This extension extends Vue code editing with Go To Definition and Peek Definition support for components and filenames in single-file components with a .vue extension. It allows quickly jumping to or peeking into files that are referenced as components (from template), or as module imports (from script).Vue.jsやVuex, Vue Router, Vue SSR, Vuetify, Nuxt.js, VuePressの公式ドキュメントをVSCode内から直接参照できます。
個人的に、Chromeで開いているタブの多さと集中力は反比例するので、ドキュメントもVSCode内で完結するのはありがたいです。要注意編
A basic spell checker that works well with camelCase code.
The goal of this spell checker is to help catch common spelling errors while keeping the number of false positives low.私が使いこなせていないだけとは思うのですが、非推奨です。
コードのスペルミスを教えてくれるということで一見便利そうですが、英語的に不自然な変数名もエラーと一緒に表示されるので、エラー箇所の特定や修正作業がしづらくなりました。Visual Studio Live Share enables you to collaboratively edit and debug with others in real time, regardless what programming languages you're using or app types you're building. It allows you to instantly (and securely) share your current project, and then as needed, share debugging sessions, terminal instances, localhost web apps, voice calls, and more! Developers that join your sessions receive all of their editor context from your environment (e.g. language services, debugging), which ensures they can start productively collaborating immediately, without needing to clone any repos or install any SDKs.
他人を招待してファイルの共同編集がリアルタイムでできます。イメージとしてはGoogle Documentで文書を共同編集している感じでしょうか。
オンラインでコードレビューをお願いするときも便利で、非常に素晴らしい拡張機能なんですが・・・。
一つ気になることはインストールに際して不具合が少なくない?のかなと。最初僕だけかなと思ったのですが、StackOverFlow等でも同様の投稿がありました。
Questions tagged [vscode-liveshare]なぜ不具合が発生するのかまだきちんと、調べきれていないので推奨するのは一旦保留です。
ただ、本当に便利な機能なので懸念が解決すれば積極的に使っていきたいところです。
※今後調査結果を追記予定おわりに
さて、お好みの拡張機能でVSCodeをカスタマイズして頂けたでしょうか。
他にもお勧めの拡張機能があれば是非コメント欄で教えてください。
- 投稿日:2019-12-04T13:57:15+09:00
通常の3倍のスピードだと!?~Vue.js/Nuxt.jsを開発するVSCode拡張機能セット~
てst
- 投稿日:2019-12-04T13:31:15+09:00
最速のフレームワーク(というのは存在しない)
何日か前にTwitterでこの投稿が話題になりました。
This is why i don't trust frameworks claiming to be fast b/c they score ? benchmarks. This is React in concurrent mode, facing an impossible(!) amount of load (2000 state-connected comps getting re-rendered with fresh props 60 frames/sec). The scheduler makes that a stable 60fps pic.twitter.com/PcuVGheRWX
— Paul Henschel (@0xca0a) November 28, 2019ReactのConcurrent Modeでは、ステートを持つ2000個のコンポーネントを安定した60fpsで再レンダーさせられるようです。一方で、ReactのいわゆるLegacy Modeでは全然60fpsにならない。何人かが、Svelteで同じデモできないかとツイートし、Svelteの創始者Rich Harris氏もデモ作ってくれました。
On the left, we have the React version, running in prod mode. On the right, Svelte (in dev mode, because I forgot to build it. Oh well.) Notice the huge gap in the React version whenever you change the count. With Svelte? Honey badger don't care. pic.twitter.com/lBB1cQ3kSE
— Rich Harris (@Rich_Harris) November 30, 2019dev環境なので60fpsまでは出ないが、React版と違って、コンポーネントの個数を選ぶ度に遅延がないとのことです。(数に関わらず)
しかし、フレームワークの比較はそもそもこれでいいのか?
Here's the only framework performance advice you'll ever need, in 2 parts:
— Mike Sherov (he/him) ? (@mikesherov) December 2, 2019
1. If you find yourself rendering a purple 3D ball of polygons, you probably shouldn't use a FW for that.
2. Instrument your app, find bottlenecks, then fix them. 99/100 times it's not the FW being slow.そもそも上のような数千ポリゴンの3Dボールをレンダーしたい時に、わざわざReactなどのFWを使うのはおかしくないか?実際のアプリと上のデモはかなり違うし、仮に同じものをページに置きたいとしても、Three.jsなどのライブラリですでにできることだから、それをFWで抽象化する意味は何か?ということを考えないといけないです。
何が本当に遅いかというと、ユーザーのコードです。FWのコントリビュータはパフォーマンスの最適化にベストを尽くしているが、任意のユーザーのコードはもちろん最適化できない(が、Svelteの斬新なアプローチで、ステートの更新など、期待できるパターンも最適化の対象にはなっているようです)。あまり例としては現実的ではないのですが、ユーザー(開発者)が無限ループを書いたらFWどころではなくなります。
さらに、実際のアプリで不可欠なIO処理は、ベンチマークでは測られていないです。データの取得とレンダリングはどう設計されているか?という点ではFWは多少違います。たとえば、ReactのConcurrent Modeでは、ネットワークのIO処理を待っている間に、アイドリングしているCPUを有効に使って、一部の要素をプリレンダーできます。IO処理が終わってからレンダリングを始める場合は、必然的に表示が遅くなります。
他にも指摘されたのは、測る過程。マイナーな原因だと思いますが、JSのJITコンパイラ仕様上、上のようなデモでは、1回目のレンダリング(初期ロード・マウント)を測っているか、時間が経ってから測っているかによって差が出るようです。なぜかというと、JITコンパイラが
cold
な(まだ実行されていない)コードをインタプリターとして解釈するけど、よく実行されるhot
なコードを、コンパイルかつ最適化してくれるからです。なので複数回レンダリングされたコンポーネントのコードがコンパイルされ速くなっている可能性もあります。それと比べて、新規マウントが多い実際のアプリでは、cold
なコードが多いです。
- 投稿日:2019-12-04T12:54:26+09:00
ゼロからVue.jsでビジュアルリグレッションテストするまでpart2/3
Part1 https://qiita.com/senku/items/07c3e2859ac90c03867a
Part2 ここ
Part3 https://qiita.com/senku/items/08d547eda2c6ff818108前回はVuex以外の主要なライブラリをStorybookに対応させる手順を紹介しました。
今回はVuexの話をします。ちょっと大変。Summary
Vuex.Store
の引数のObjectをexportしておいて- モックに書き換えて
- Storybookに使わせる
Vuexとのたたかい
とりあえず
vue add vuex
で入れます。$ vue add vuex
src/store/index.js
にVuex Storeの定義が書かれています。
Vuex.Store()
の引数をexportしつつ、適当なストアにまるっと書き換えます。namespaceにも対応しておくよ。src/store/index.jsimport Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export const store = { modules: { test: { namespaced: true, state: { name: '' }, mutations: { setName: (state, payload) => { state.name = payload } }, actions: { async setFoo({ commit }) { commit('setName', 'Foo') }, async setBar({ commit }) { commit('setName', 'Bar') } }, getters: { name: state => state.name } } } } export default new Vuex.Store(store)
src/views/Home.vue
あたりでVuexを使えるようにしてみましょう。src/views/Home.vuediff --git a/src/views/Home.vue b/src/views/Home.vue index fc2e940..807ed24 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,11 +1,13 @@ <template> <div class="home"> + <p>{{ name }}</p> <img alt="Vue logo" src="../assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template> <script> +import { mapGetters } from 'vuex' // @ is an alias to /src import HelloWorld from '@/components/HelloWorld.vue' @@ -13,6 +15,12 @@ export default { name: 'home', components: { HelloWorld + }, + computed: { + ...mapGetters('test', ['name']), + }, + created() { + this.$store.dispatch('test/setFoo') } } </script>
npm run serve
で確認するとFoo
が表示されます。バッチリ。Home用のStoryも作っておきましょう。いつものパターン。
src/stories/Home.stories.jsimport { storiesOf } from "@storybook/vue"; import defaultDecorator from "@/stories/defaultDecorator"; import Home from "@/views/Home.vue"; storiesOf("Home", module) .addDecorator(defaultDecorator) .add("test", () => { return { components: { Home }, template: ` <home /> ` }; });Vuex × Storybook
いつものようにこのStoryもタダでは動きません。
まずはVue Routerと同じように
config/storybook/config.js
でVue.use()
します。config/storybook/config.jsdiff --git a/config/storybook/config.js b/config/storybook/config.js index d8dcd31..db6a520 100644 --- a/config/storybook/config.js +++ b/config/storybook/config.js @@ -2,8 +2,10 @@ import { configure } from '@storybook/vue' import Vue from 'vue' import Router from 'vue-router' +import Vuex from 'vuex' Vue.use(Router) +Vue.use(Vuex) const req = require.context('../../src/stories', true, /.stories.js$/)decoratorでStoreを渡してもいいのですが、Storyごとに異なるモックデータを使いたいケースを考慮して、Storyで渡すようにしておきます。
src/stories/Home.stories.jsdiff --git a/src/stories/Home.stories.js b/src/stories/Home.stories.js index 2902105..802e067 100644 --- a/src/stories/Home.stories.js +++ b/src/stories/Home.stories.js @@ -1,12 +1,16 @@ import { storiesOf } from "@storybook/vue"; import defaultDecorator from "@/stories/defaultDecorator"; import Home from "@/views/Home.vue"; +import Vuex from "vuex"; +import { store } from "@/store/index.js"; storiesOf("Home", module) .addDecorator(defaultDecorator) .add("test", () => { + const mockStore = new Vuex.Store(store); return { components: { Home }, + store: mockStore, template: ` <home /> `Storyでもガッツリ
Foo
が表示されるようになります。gettersのモック
getters で取得する値をストーリーによって変えたいことがありますよね。モックで対応しましょう。
Deep copyを利用したいので、とりあえずlodashを入れます。Deep copyの実装面倒だからね。
$ npm install --save-dev lodashVuex Storeをモックするための独自のクラスをひとつ作って、gettersを上書きするためのユーティリティ関数を作ってしまいます。後のことも考えて適当に共通化しておきます。
また、オリジナルのstoreオブジェクトを改変すると他のStoryで困る可能性があるので、cloneDeep
で複製したObjectを使います。
ここがこの記事の見どころですよ。それにしてもコードフォーマットバラバラだな。src/stories/mockVuex.jsimport cloneDeep from "lodash/cloneDeep"; import Vuex from "vuex"; import { store as originalStore } from "@/store/index.js"; export default class mockVuex { constructor() { // モックするVuex StoreをDeep copy this.mockStore = cloneDeep(originalStore); } /** * Vuex Storeを生成して返却する * * @return {Vuex.Store} store */ getMockStore() { return new Vuex.Store(this.mockStore); } /** * mockStoreのプロパティを上書きする * * @param {string} field actions/getters * @param {string} nameSpace 'foo/bar/baz' 形式 * @param {string} propertyName * @param {function} newFunction */ mockProperty(field, nameSpace, propertyName, newFunction) { let modules = this.mockStore.modules // nameSpace に従ってモジュールの階層を掘る const diggingNameSpaces = nameSpace.split('/') const lastNameSpace = diggingNameSpaces.pop() for (const diggingNameSpace of diggingNameSpaces) { modules = modules[diggingNameSpace].modules } modules[lastNameSpace][field][propertyName] = newFunction } /** * getters を上書きする * * @param {string} nameSpace 'foo/bar/baz' 形式 * @param {string} propertyName * @param {function} newFunction */ mockGetters(nameSpace, propertyName, newFunction) { this.mockProperty("getters", nameSpace, propertyName, newFunction); } }Storyを修正します。Vuexは
mockVuex
に隠蔽したので参照不要になりました。
mockGetters()
関数を使って、gettersが返す値を変更します。
その後getMockStore()
で取得したVuex StoreをStoryで使います。src/stories/Home.stories.jsdiff --git a/src/stories/Home.stories.js b/src/stories/Home.stories.js index 802e067..d1e5e74 100644 --- a/src/stories/Home.stories.js +++ b/src/stories/Home.stories.js @@ -1,16 +1,16 @@ import { storiesOf } from "@storybook/vue"; import defaultDecorator from "@/stories/defaultDecorator"; +import mockVuex from "@/stories/mockVuex"; import Home from "@/views/Home.vue"; -import Vuex from "vuex"; -import { store } from "@/store/index.js"; storiesOf("Home", module) .addDecorator(defaultDecorator) .add("test", () => { - const mockStore = new Vuex.Store(store); + const vuex = new mockVuex(); + vuex.mockGetters('test', 'name', () => 'Baz') return { components: { Home }, - store: mockStore, + store: vuex.getMockStore(), template: ` <home /> `
test/name
のgettersがモックされ、Baz
が表示されるようになりました。実運用では、モック用のデータを別に用意してimportするのがいいと思います。モックサーバとかに使ってるデータがあればそれで。
actionsのモック
actionsもモックしたいですよね。
src/stories/mockVuex
にmockActions()
関数を追加します。共通化してるのでただのラッパー関数です。src/stories/mockVuex.jsに追加/** * actions を上書きする * * @param {string} nameSpace 'foo/bar/baz' 形式 * @param {string} propertyName * @param {function} newFunction */ mockActions(nameSpace, propertyName, newFunction) { this.mockProperty('actions', nameSpace, propertyName, newFunction) }Storyを編集して、
Home.vue
のcreated()
で呼ばれていたtest/setFoo
アクションをモックして、qux
をセットしてみます。src/stories/Home.stories.jsdiff --git a/src/stories/Home.stories.js b/src/stories/Home.stories.js index d1e5e74..27a8fef 100644 --- a/src/stories/Home.stories.js +++ b/src/stories/Home.stories.js @@ -7,7 +7,7 @@ storiesOf("Home", module) .addDecorator(defaultDecorator) .add("test", () => { const vuex = new mockVuex(); - vuex.mockGetters('test', 'name', () => 'Baz') + vuex.mockActions('test', 'setFoo', ({state}) => state.name = "qux") return { components: { Home }, store: vuex.getMockStore(),ガッツリ
qux
が表示されます。やったね。画面表示に必要なデータをモックするのはgettersのモックでだいたいなんとかなるので、actionsをモックするケースはHTTP通信をしたりStorybookで動作しない副作用を制御したい場合がほとんどだと思います。
その場合、actionsはmockVuexのコンストラクタでまとめて無効化してしまうのが楽ちんです。src/stories/mockVuex.jsを編集constructor() { // モックするVuex StoreをDeep copy this.mockStore = cloneDeep(originalStore); // 以下使わないActionを無効化 const NOOP = () => {} this.mockActions('test/setBar', NOOP) }もうVuexのモックも怖くないですね。
明日からVue.jsコンポーネントのStoryを量産できるはずです。おまけ:コンポーネントのdataを操作する
Storyから
$refs
経由でコンポーネントにアクセスすればいいです。mounted()
のタイミングがよさげ。.add("edit name", () => { return { components: { Home }, template: ` <home ref="foo"/> `, mounted() { this.$refs.foo.bar = 'baz' // Homeコンポーネントのbarに'baz'をセット } }; });
- 投稿日:2019-12-04T10:31:10+09:00
Vue.jsでWebRTCによるビデオ通話機能を作ってみた
グレンジ Advent Calendar 2019 6日目担当の raitome です。
グレンジでクライアントエンジニアをやっています。
最近はちょっと違う技術を触ってみたくて、Vue.jsを使って、ビデオ通話機能を作ってみました。
ネットの資料が多いですが、古いものも多くて、すぐ使えるコードが少ないので、
メモのためでもあり、必要なものを簡単にまとめました。WebRTCについて
詳細は下記でご覧いただけます。
https://ja.wikipedia.org/wiki/WebRTC
https://qiita.com/yusuke84/items/286f569d110daede721e使ったライブラリ
とりあえず手軽にビデオ通話機能を作りたくて、下記のライブラリを使いました。
・simple-peer: https://github.com/feross/simple-peer
WebRTCベースで、p2pのビデオ通話を簡単に実装できるライブラリ。ファイルの転送などもできるらしい・ simple-signal-clientとsimple-signal-server: https://github.com/t-mullen/simple-signal
WebRTCでPeer to Peer通信を行う場合、シグナリングサーバと呼ばれる仲介サーバを用意する必要がありますので、simple-peerのためのシグナルサーバー用ライブラリsimple-signalを使いました。simple-signal-clientはクライアント側で、simple-signal-serverはサーバー側で使います。他に、シグナルサーバーはSocket通信を使うため、socket.ioとクライアントとサーバー側のライブラリも使います。
(Skyway: https://webrtc.ecl.ntt.com/ というサードパーティのものを使えばもっと早く構築できるらしいが、有料らしいので、今回は自前で色々作ってみました)
クライアント側の準備
今回は必要な処理だけ紹介します。完成版のコードを割愛します。
1.必要なもののimport
sample_client.jsimport SimplePeer from 'simple-peer' import SimpleSignalClient from 'simple-signal-client' import io from 'socket.io-client'2.Peerの作成
sample_client.jsconst socket = io("シグナルサーバーのurl" , {secure: true}) let signalClient = new SimpleSignalClient(socket) // 下記の方法でチャットルームの感じで作れる signalClient.discover("チャットルームID的な何か") // これを使ってユーザーリストとかを取得できる signalClient.on('discover', receiveUserList) // ユーザーリスト処理のfunctionをここに入れる2.Peerへの接続リクエスト処理
ビデオ通話したい相手のソケットIDが分かれば、下記のfunctionを使えばシグナルサーバーを経由して接続できます。
sample_client.jsconst { peer } = await signalClient.connect("自分のソケットID", "チャットルームID的な何か"})3.Peerの受信処理
sample_client.jssignalClient.on('request', async (request) => { const { peer } = await request.accept() // 接続リクエストを承認 peer.on('connect', () => { peer.send("connected") }) peer.on('stream', (remoteStream) => { // 向こうから受け取ったPeerのストリームを画面に表示させる const videoElement = document.createElement('video') videoElement.autoplay = true videoElement.srcObject = remoteStream container.appendChild(videoElement) // 事前に用意したコンテナに新しく作ったElementを追加する }) peer.on('close', () => { // 停止時の必要処理 }) // 自分のカメラの映像も向こうに送信するので、ストリームを取得して画面に追加する。下記の関数はあとで説明します addLocalCameraStream() })4. カメラの扱い
下記の処理で、ブラウザがカメラの使用権限を取得して、カメラのストームを扱います。
ブラウザによって挙動が少し違うので、最新版のChromeかFireFoxを使うことをオススメします。
一つ注意点としては、Chromeの場合は、セキュリティの関係で、サーバー側はSSL対応をしないと、下記のメソッドはうまく動きません。sample_client.jsaddLocalCameraStream() { var mediaDevices = navigator.mediaDevices || ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia) ? { getUserMedia(c) { return new Promise(((y, n) => { (navigator.mozGetUserMedia || navigator.webkitGetUserMedia).call(navigator, c, y, n); })); } } : null) mediaDevices.getUserMedia({ audio: true, video: true }).then((localStream) => { peer.currentPeer.addStream(localStream) // これで接続した相手にストリームを送る // ここでストリームを画面に表示させる処理を追加してもいい }).catch(err => { }); }ちなみに、mediaDevices.getDisplayMediaを使えば、画面共有機能も作れます。
上記のコードを使えば、クライアント側の最低限の機能が作れます。
シグナルサーバーの準備
シグナルサーバーのコードは割とシンプルになります。
主に必要に応じてユーザーの管理処理をカスタマイズします。index.jsconst io = require('socket.io')(server) var SimpleSignalServer = require('simple-signal-server') var signal = new SimpleSignalServer(io) signal.on('request', (request) => { request.forward() // リクエストをそのまま進ませる }) signal.on('discover', (request) => { // ここでユーザーのソケットIDやルームIDを管理して、request.discoverを使って、全てのpeerの必要な情報を送る }) signal.on('disconnect', (socket) => { // 接続切れる時の処理 })最後に
以上でコードを使えば、必要最低限のビデオ通話機能が作れます。少し調整すれば、画面共有機能も作れます。実際にテストしてみたところ、P2Pの関係でもありますが、割とスムーズなビデオ通話ができました。
また、何か不明なところがありましたら、simple-signalのサンプルコードも一緒にご覧いただければ。
- 投稿日:2019-12-04T09:49:21+09:00
GASとVue.jsとSpreadsheetによるデータ連携
SpreadsheetID SheetName 123456789012345678901234567890123456789012345 UserList
ID Name Department xxx@test.com xxx Sales yyy@test.com yyy Development zzz@test.com zzz Marketing コード.gsfunction doGet() { var html = HtmlService.createTemplateFromFile("index").evaluate(); return html; } function getSS(spreadSheetID, sheetName){ var res = SpreadsheetApp.openById(spreadSheetID) .getSheetByName(sheetName).getDataRange().getDisplayValues(); var keys = res.splice(0, 1)[0]; return value = res.map(function(row) { var obj = {} row.map(function(item, index) { obj[keys[index]] = item; }); return obj; }); } function getUserList() { var SSID = "123456789012345678901234567890123456789012345"; var SN = "UserList; var userList = getSS(SSID, SN); Logger.log(userList); return userList; } function getUserData() { var email = Session.getActiveUser().getEmail(); var userList = getUserList(); return filterdData = userList.filter(function(item, index){ if (item.ID == email) return true; }); }vue.html<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script> <script> var app = new Vue({ el: '#app', data: { loginUserEmail: '', logoinUserName:'', logoinUserDept:'', }, methods:{ setLoginUser: function(loginUser){ this.loginUserEmail = loginUser[0].ID; this.logoinUserName = loginUser[0].Name; this.logoinUserDept = loginUser[0].Department; }, }, created: function(){ google.script.run .withSuccessHandler(this.setLoginUser).getUserData (); }, }) </script>index.html<!DOCTYPE html> <html> <head> <base target="_top"> </head> <body> <div id="app"> <p>User Data</p> <ul> <li>{{ loginUserEmail }}</li> <li>{{ logoinUserName }}</li> <li>{{ logoinUserDept }}</li> </ul> </div> </body> <?!= HtmlService.createHtmlOutputFromFile('vue').getContent(); ?> </html>
- 投稿日:2019-12-04T09:48:23+09:00
GASとVue.jsの連携
vue.html<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script> <script> var app = new Vue({ el: '#app', data: { email: '', }, methods:{ initEmail: function(sessionEmail){ this.email = sessionEmail; }, }, created: function(){ google.script.run .withSuccessHandler(this.initEmail).getLoginID(); }, }) </script>コード.jsfunction doGet() { var html = HtmlService.createTemplateFromFile("index").evaluate(); return html; } function getLoginID() { var email = Session.getActiveUser().getEmail(); Logger.log(email); return email; }index.html<!DOCTYPE html> <html> <head> <base target="_top"> </head> <body> <div id="app"> {{ email }} </div> </body> <?!= HtmlService.createHtmlOutputFromFile('vue').getContent(); ?> </html>
- 投稿日:2019-12-04T08:17:02+09:00
Rails+Vue.jsでバリデーションエラーを各項目の下に表示する
はじめに
Rails側のモデルに定義したバリデーションを使いつつ、
バリデーションエラーを各項目の下に表示するのに苦労したのでまとめておこうと思います。
今回、バリデーション関連以外の説明は省略させていただきます。
ご了承ください。。やりたいこと
このように項目の下にバリデーションエラーを表示するようにします。(見た目は気にしないでください。。)
実装してみる
バリデーションを設定
ここでは必須のバリデーションだけ設定しておきます。
class Company < ApplicationRecord validates :name, presence: true endAPI側の実装
vue.js側で各項目ごとのエラーメッセージを取り出せるようにします。
具体的には下記のようなハッシュになるように加工します。
{項目名: 日本語化されたエラーメッセージ}今回は下記のように実装しました。
@company = Company.new(create_company_params) if @company.save render json: @company, status: :ok else render json: { errors: @company.errors.keys.map { |key| [key, @company.errors.full_messages_for(key)]}.to_h, render: 'show.json.jbuilder' }, status: :unprocessable_entity endここでerrorsにエラーメッセージを設定しています。
errors: @company.errors.keys.map { |key| [key, @company.errors.full_messages_for(key)]}.to_hAPI側はこれで終わりです。
vue.js側で項目の下にエラーが表示されるようにする
まずはtemplateから実装していきます。
<template> <form @submit.prevent="createCompany"> <h2>企業情報</h2> <div> <label>企業名</label> <input v-model="company.name" type="text"> <!-- これでバリデーションエラーがあるときだけ表示される --> <p v-if="!!errors['name']" class="error" style="color: red;">{{ errors['name'][0]}}</p> </div> </form> </template>errorsのkeyの中に表示する項目名が含まれているかどうかで、
表示/非表示を切り替えています。次にscriptです。
<script> import axios from 'axios' import { csrfToken } from 'rails-ujs' axios.defaults.headers.common['X-CSRF-Token'] = csrfToken() export default { data: function () { return { company:{ name: '' }, // バリデーションエラーがあった場合は、このerrorsにセットされます。 errors: '' } }, methods: { createCompany: function(){ axios .post('api/v1/company.json', this.company) .then(response => { this.$router.push('/'); }) .catch(error => { if (error.response.data && error.response.data.errors) { this.errors = error.response.data.errors; } }); } } </script>これでバリデーションエラーがあるときは各項目の下に表示されるようになるかと思います。
まとめ
今回は各項目の下にエラーメッセージを表示する方法をまとめました。
もっといい感じの方法があれば、是非コメントで教えてください。。
- 投稿日:2019-12-04T02:43:29+09:00
nuxt.jsで現在のウインドウサイズをどのコンポーネントからでもリアクティブなオブジェクトとして取得できるプラグインのつくりかた
はじめに
スマホに対応中に、要素の有無をcssの
@media
指定とdisplay:none
じゃなくてv-if
で操作したいけどめんどくさそうだから楽にしたいという欲求からプラグインの作り方から勉強したやつ。とりあえずコード
plugins/window-resize.jsimport Vue from 'vue' Vue.use({ install(Vue) { const $window = Vue.observable({ width: 0, height: 0, }) let queue = null const wait = 100 const getWindowSize = () => { clearTimeout(queue); queue = setTimeout(function () { $window.width = document.documentElement.clientWidth $window.height = document.documentElement.clientHeight }, wait); } global.addEventListener('resize', getWindowSize) getWindowSize() Vue.prototype.$window = $window } })nuxt.config.jsplugins: [ //ウインドウサイズ取得のプラグインであるためSSR時には実行しないようにmode: 'client'を指定 {src: '~/plugins/window-resize.js', mode: 'client'}, ],せつめい
install(Vue)
- Vueのプラグインを作るときに必須のメソッド。詳しくはプラグインの記述(公式ドキュメント)
Vue.observable(object)
- vue.jsおなじみのdata関数の戻り値にも適用されているメソッドで、このメソッドに処理されることによって、戻り値にリアクティブオブジェクトが得られる。Vue.observable( object )(公式ドキュメント)
setTimeout
- resizeのイベントリスナーは数pxリサイズされる毎に発火され、どこからでも使える割に鬱陶しいので上記サンプルでは0.1秒スクロールが止まるまでは発火を抑制するようにしています。
global.addEventListener('resize', getWindowSize)
- ここで、どこで画面をリサイズしても、ウインドウサイズが取得されるようにしている。
Vue.prototype.$window = $window
- ここでどこからでもthis.$window
で使えるようにしている。おわりに
<div v-if="$window.width > 1080">おつかー!</div>
みたいな感じで表示を司ることができました!
vueプラグインなるものを初めて作ってみたのですが、Vue.observable
君が便利ですね・・・
今後もいろいろ作って引き出し増やして行きたいと思ってます!
- 投稿日:2019-12-04T01:51:42+09:00
Vuexのmoduleのstateを呼ぶ時store.modules.◯◯.stateにならない理由
Vuexにはモジュールという機能があり、コンポーネントが複雑化して大きな一つのstoreにstateを管理するには難しくなった時のために、モジュールという単位で分けることで管理をすることが出来ます。
以下はmoduleの例です。Vuexのドキュメントに載っているものの写しです。
https://vuex.vuejs.org/ja/guide/modules.htmlmodules.jsconst moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> `moduleA` のステート store.state.b // -> `moduleB` のステートmoduleAとmoduleBはどちらもstateとmutationsを持っており、
new Vuex.Store
でインスタンス化したオブジェクトをstore変数に入れています。その時modulesプロパティの中でmoduleAをa、moduleBをbというプロパティにセットしています。ここで疑問が浮かんだのですが、moduleA内のstateにアクセスする時は
store.state.a
となります。「aの中のstate」を呼んでいるのに「stateの中からaを呼んでいる」ので、直感的にはstore.modules.a.state
ではないかと思いました。しかしstore.state.a
は間違っていません。理由を探るためVuexのソースを見てみました。結論から言うと、Storeクラス内にモジュール内のstateとは全く別のstateというオブジェクトを作り、モジュール内のstateを代入して置き換えています。
詳しくVuexのコードを見てみます。上のコードの
store
変数はnew Vuex.Store
で作ったオブジェクトなので、Storeクラスの中にstate
オブジェクトがありそうです。Storeクラスはsrc/store.jsに定義されていました。
https://github.com/vuejs/vuex/blob/dev/src/store.js
stateは以下のように定義されていました。関係のある部分だけまとめています。src/store.jsimport ModuleCollection from './module/module-collection' export class Store { constructor(options = {}) { this._modules = new ModuleCollection(options) const state = this._modules.root.state } }https://github.com/vuejs/vuex/blob/ba2ff3a3de394a4c5c9a72ed7314ad3bb52f6a53/src/store.js#L53
Storeクラスのstate変数に
this._modules.root.state
が入っています。これが一番上の例でいうstore.state
に該当します。Storeをインスタンス化する時引数に入ったオブジェクトを
options
に入れています。このoptionsが一番上の例の{modules: {a: moduleA, b: moduleB}}
の部分となります。
optiosを引数に入れたModuleCollection
クラスを初期化して_modules
に入れ、中のroot.state
を取り出しているようです。ここで注意するべきはstore.jsの中に
modules
という変数はありません。したがってstore.modules.state.a
と書いてしまうとstore.modulesがundefinedであるためにエラーになります。では
store.state
にセットしているroot
は何なのか、moduleAとmoduleBはどのように取り出しているのかさらに掘り下げるためにModuleCollectionクラスを見てみます。こちらも関係ある部分だけを抜粋しました。
https://github.com/vuejs/vuex/blob/dev/src/module/module-collection.jssrc/module/module-collection.jsimport Module from './module' import { forEachValue } from '../util' export default class ModuleCollection { constructor (rawRootModule) { // register root module (Vuex.Store options) this.register([], rawRootModule, false) } register (path, rawModule, runtime = true) { if (process.env.NODE_ENV !== 'production') { assertRawModule(path, rawModule) } const newModule = new Module(rawModule, runtime) if (path.length === 0) { this.root = newModule } else { const parent = this.get(path.slice(0, -1)) parent.addChild(path[path.length - 1], newModule) } // register nested modules if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { this.register(path.concat(key), rawChildModule, runtime) }) } }
ModuleCollection
をnewするときに引数にnew Vuex.Store
の引数と同じものを入れており、それを使ってregister
関数を実行しています。
register関数内でModule
クラスをnewしており、this.root
に代入しています。これが先ほどstore.jsでみたthis._modules.root
となります。もしmodulesがあれば下の方の
if(rawModule.modules)
の中身が通るため、中のオブジェクトループで回して再帰でregister関数を実行しているようです。このrawChildModule
に上の例でいうmoduleA
、moduleB
が入ってきます。newしているModuleクラスのコンストラクタもみてみます。
https://github.com/vuejs/vuex/blob/dev/src/module/module.jssrc/module/module.jsexport default class Module { constructor (rawModule, runtime) { this.runtime = runtime // Store some children item this._children = Object.create(null) // Store the origin module object which passed by programmer this._rawModule = rawModule const rawState = rawModule.state // Store the origin module's state this.state = (typeof rawState === 'function' ? rawState() : rawState) || {} } }このrawModuleには上の例でいうmoduleA、moduleBが入るため、その中のstateを新たにthis.stateに代入しているようです。これで
store.state
の中に各モジュール内のstateの中身を移すことが出来ました。
- 投稿日:2019-12-04T00:45:36+09:00
rails install:webpackerができない件について
環境
- rails 5.2.3
- ruby 2.6.3
- yarn 1.19.2
エラー内容
railsでvue.jsを追加するために、rails install:webpackerをしようとしたところ
rails aborted! Sprockets::Railtie::ManifestNeededError: Expected to find a manifest file in `app/assets/config/manifest.js` But did not, please create this file and use it to link any assets that need to be rendered by your app: Example: //= link_tree ../images //= link_directory ../javascripts .js //= link_directory ../stylesheets .css and restart your server /Users/user/railsProjects/vuejs-on-rails/config/environment.rb:5:in `<main>' /Users/user/railsProjects/vuejs-on-rails/bin/rails:9:in `<top (required)>' /Users/user/railsProjects/vuejs-on-rails/bin/spring:15:in `require' /Users/user/railsProjects/vuejs-on-rails/bin/spring:15:in `<top (required)>' ./bin/rails:3:in `load' ./bin/rails:3:in `<main>' Tasks: TOP => app:template => environment (See full trace by running task with --trace)このようなエラーがでました。
対応
調べたところ、【Rails】rails webpacker:install に失敗する場合の対処法にたどり着き、書いてある通りにconfigにwebpacker.ymlを追加したところ、エラーが変わりませんでした?
その後、yarnのupgradeをしても変わりませんでした?
下記のエラーを確認
Expected to find a manifest file in `app/assets/config/manifest.js`app/assets/config/manifest.jsに
app/assets/config/manifest.js//= link_tree ../images //= link_directory ../javascripts .js //= link_directory ../stylesheets .cssを追加
無事にrails install:webpackerが通りました。
参考
- 投稿日:2019-12-04T00:10:25+09:00
CSSとVue.jsで遊ぶ【少し連想配列】
1.はじめに
今回は連想配列の復習とCSSの勉強をかねて静的サイトを作ってみました。
2.完成体
※写真は全て私の私物です。自分で試すときは好きな画像をお使いください。3.解説
index.html<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>MENU BAR</title> </head> <body> <div class="maincontent" id="app"> <ul class="contentList"> <li v-for="content in list" :key="content.id" class="contentListChir"> <div class="img-box"> <a v-bind:href="content.url" target="_blank" class="atag"> {{content.name}} <img v-bind:src="content.img" class="scale-img"> </a> </div> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> new Vue({ el:'#app', data:{ list:[ { id:1, name:'kobe', url:'#', img:'IMG_5375.jpeg' }, { id:2, name:'matsudo', url:'#', img:'IMG_1839.jpeg' }, { id:3, name:'asakusa', url:'#', img:'IMG_1849.jpeg' }, { id:4, name:'asakusa', url:'#', img:'IMG_1851.jpeg' }, { id:5, name:'skytree', url:'#', img:'IMG_0390 2.jpeg' }, { id:6, name:'rikugien', url:'#', img:'IMG_2077.jpeg' }, { id:7, name:'kyufurukawa', url:'#', img:'IMG_2068.jpeg' }, ] } }) </script> </body> <style> body{ background-color: black; } .maincontent{ width: 805px; text-align: center; margin: 30px auto; } .contentList{ display: flex; padding: auto; flex-wrap: wrap;/*.maincontentのwidthの範囲で自動的に折り返す*/ margin: 70px auto; } .img-box{ /*外枠*/ margin: 20px 20px; /*左右中央*/ background-color: lightgray; /*背景色*/ height: 140px; /*縦*/ width: 200px; /*横*/ position: relative; /*外枠に対して中の写真を中央に寄せる為、設定*/ overflow: hidden; /*外枠からはみ出した部分を隠す*/ border-radius: 5px; /*外枠の角に丸み*/ text-align: center;/*aタグの文字を左右中央へ*/ } .scale-img{ position: absolute; /*position:relativeに対して絶対的な位置を決定*/ top: 0; bottom: 0; left: 0; right: 0; /**top,bottom,left,rightを0にするとrealtiveを 設定したタグに対して*左右上下中央に位置します*/ width: 200px; /*外枠と同じ高さを設定*/ height: 140px; /*外枠と同じ幅を設定*/ transition-duration: 0.4s; /*変化の速度*/ } .scale-img:hover { /* :hoverを付けるとカーソルが上に来た時のレイアウトを 設定できる*/ transform: scale(1.3,1.3); /*拡大処理(横、縦)*/ opacity: .3; /*透過させて外枠に設定した背景色を浮かせている*/ cursor: pointer; /*カーソルをポインターへ変化*/ } .atag{ text-decoration: none;/*リンク下線部を消去*/ font-weight: bold;/*太文字*/ color: black;/*白文字*/ line-height: 140px;/*文字を上下中央に置くには親タグの高さに揃える*/ font-size: 25px;/*文字サイズ*/ } </style> </html>CSSの動き、解説についてはソースの中にコメントで記述してあるのでそれを参考にして下さい。
4.連想配列と配列の違い
配列は配列の中にvalueのみを収納し、自動的にindexが割り振られ、取得する際はそのindexを使ってvalueを取得します。
例:let array =['りんご','バナナ','いちご']
console.log(array[0]); ==>'りんご'一方、連想配列はvalueと共にkeyを一緒に配列の中に入れてあげます。
keyと共に入れて何が良いのかというと取得する際にkeyによって可読性があがるというメリットがあります。
例:let obj ={name:'田中', hobby:'soccer',age:24}
console.log(obj.name)==>'田中'
例のように書けばobjの名前を取ってきているんだなと見てわかりますよね。今回の完成体の中の連想配列を見てみると、、、
{
id:1,
name:'kobe',
url:'#',
img:'IMG_5375.jpeg'
}
画像一つの情報を連想配列に入れて、いくつもの画像のidとurl,imgをVueインスタンスのデータの配列の中に入れて上げることでそれぞれ画像を表示しているわけです。5.最後に
今回、vueだけでなくcssを整えてみて、cssができると作品の質が一気nにあがるような気がします。プロダクトは機能も大事ですが使い手にとって心地の良いデザインを考えながらやっていきたいという想いがより強くなりました。