- 投稿日:2020-02-23T22:51:51+09:00
computedによるclass変更
はじめに
公式にある基礎なのですが、最近Vueを仕事で使っていなくて忘れてしまうので、ちょっと書いておきます。
何かしらのアクションで、styleを変更する事はよくあります。
例えば、clickなどアクションへの反応、validationのエラー、処理の成功失敗などです。
公式にあるようにv-bind:class
を使う方法もありますが、computed(算出プロパティ)
を利用するのが一般的とのこと。
実際やってみると、computed利用の方が、状態をdataプロパティ、処理をcomputedにまとめられるのでコード全体がわかりやすくなると思いました。流れ
- イベントでメソッド実行
- dataのプロパティの値を変更
- computed(算出プロパティ)でclassを変更
- styleのclassが変わる
サンプル
ボタン押下でclassが変わります。
See the Pen Change class by 'computed' by H.N (@H_Naito) on CodePen.
参考
- 投稿日:2020-02-23T18:07:22+09:00
Vue.jsのフィルターについて
フィルター
- テキストをフォーマットするようなもの
- 表示したいものを大文字にする、とか小文字にする、とか
例
全て大文字で表示したい時の例
templateタグの中の
title
だけでなく
subtitle
まで表示させたい時、
scriptタグの中のcomputed
を2回書かないと表示できないが...Home.vue<template> <div> <p v-border:solid.round.shadow="{width:'3px',color:'brown'}"></p> <h2>{{upperCaseTitle}}</h2> <p>{{subtitle}}</p> </div> </template> <script> export default { data(){ return{ tmpData:"Hello", title:"Welcome to Tokyo", subtitle:"Tokyo is a great city" }; }, // 全て大文字にして表示させる computed:{ uepperCaseTitle(){ return this.title.toUpperCaseTitle(); } }, computed:{ subTitle(){ return this.title.subTitle(); } } </script>フィルターを使うと
main.jsVue.filter("upperCase",function(value){ return value,toUpperCase(); });Home.vue<template> <div> <h2>{{title | upperCase }}</h2> <p>{{subtitle | upperCase}}</p> </div> </template> <script> export default { data(){ return{ title:"タイトル", subtitle:"サブタイトル" }; }, computed:{ upperCaseTitle(){ return this.title.toUpperCaseTitle(); } } }; </script>templateタグのの
{{ }}のなかに、
|(パイプ記号)と、upperCaseと書くと、
computedは一回の記述でOK!ローカルに登録する場合
Home.vue//ローカルに登録する場合 <script> export default { filters:{ lowerCase(value){ return value.toLowerCase(); } } }; </script>※フィルターではthisは使えない!
もしthisでアクセスしたい場合は、computedやmethodを使う!フィルターは連結できる
どうやってやるかというと・・・
Home.vue<template> <h2>{{title | lowerCase | upperCase}}</h2> </template>という風に、先に書いた|(パイプ記号)の後に、さらに|を追加して、その後追加したいプロパティを追記します!
この結果は、全て大文字になります。
なぜかというと、
先にtitle | lowerCase
が実行され、
その後にupperCase
が実行されるからですcomputed とfilterの違いは??
動的なデータを表現する時に使う
coomputed
オプションとfilter
とでは何が違うのか??その前にcomputedオプションとmethodオプションとでは何が違うのか??
computed
- 依存関係に基づいてキャッシュされる
- いつその関数が実行されるかに注目する必要がある
- 参照先の値が変わった時のみ、実行される
- リアクティブな依存関係に基づきキャッシュされる
method
- clickイベントの中にmethodを書いたら、クリックした時に、イベントが発生する
- {{ }} のなかにmethodを書くと、いつ実行したら良いかわからない
- methodsの中身が新しく変わったら{{ }} の中身を書き換える必要があるが、templateの中身が少しでも変わって再描画された時、{{ method名 }}は、毎回実行されてしまう
filter
- 実行されるたびに再描画されてしまう
- なので、再描画が頻繁に行われる場所では使わない方がいい
- 何度も同じような処理を書きたいけど、computedだと冗長なコードになってしまう場合はfilterを使う
- 投稿日:2020-02-23T16:16:34+09:00
Vue.jsのリアクティブシステムについて
Vueインスタンス
- 二つ作れる
- けど、なるべく引く数のインスタンスは使わない方がいい(分かりにくくなるから)
後から追加したプロパティはリアクティブにはならない
index.html<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <div id="app1"> <p>{{message}}</p> <p>{{name}}</p> <button @click="message = 'ボタンから変更'">ボタン</button> </div>main.jsvar vm = new Vue({ el:'#app1', data:{ message:'メッセージ1' } }) vm.message = '変えました' // これはリアクティブにはならない(かけなくはないが) vm.name= '名前'そもそもインスタンスにmessageはあるので
@click="message"は変わるけど、インスタンス内になく、外から追加した
name
は変わらない、ということですリアクティブにしたいものはインスタンスのdata内に置いておきましょう、ということです
なぜかというと、
- インスタンスないでリアクティブシステムが実行されてる
- Vue.jsは、インスタンスが作成された瞬間に全てのプロパティそれぞれに、getterとsetterを用意します。(watcher)
getter
その変数が参照された時に実行される
setter
messageが書き換わった時に関数を実行
watcher
- setterをトリガーに何かの関数を実行したい
- データを再描画させるとか
- 投稿日:2020-02-23T15:45:04+09:00
Vue.js options APIの型推論について調べてみた
始めに
Vue.jsは今だと
Vue.extend
を呼ぶことでobject形式でも型が利くようになり、クラス形式じゃなくてもTypeScriptは機能するようになってきました。
ただ具体的な原理が分からなかったので調べてみることにしました。バージョンは2.6.11をみています。import Vue from 'vue'; export default Vue.extend({ data() { return { value: 10, }; }, mounted() { this.value.slice(); // number型なのでエラー! }, });型推論の基本原則
推論の流れをみていく前に、基本的なところをおさらいします。
generics
型推論はgenericsを使ってパラメータから何の型かを推論します。
T
が何らかの型で、それは受け取ったパラメータに応じて変化します。function head<T>(arr: Array<T>): T | undefined { return arr[0]; } // number型と解釈される const num = head([1, 2, 3]); // string型と解釈される const str = head(['one', 'two', 'three']); // 型が分からない場合は明示的に書くこともできる const list: any = [1, 2, 3]; const item = head<number>(list);オーバーロード
同じメソッド名を定義して、引数に応じて対応したメソッドの定義を参照します。
class Twicer { // オーバーロードメソッドのインターフェイス twice(num: number): number // 数を2倍する処理のインターフェイス twice(str: string): string // 文字列を2回繰り返す処理のインターフェイス twice<T>(arr: T[]): T[] // 配列要素を2倍にする処理のインターフェイス twice(value: any): any { // 具体的な実装 } } const twicer = new Twicer(); twicer.twice(10); // twice(num: number): numberをみる twicer.twice('abc'); // twice(str: string): stringをみる twicer.twice([1, 2, 3]); // twice<T>(arr: T[]): T[]をみる(ここではT=number)実装は共通のメソッド一つでやらないといけないとか、TypeScriptだと色々制約があるのですが、今回の話とは関係ないので詳細は以下などを参照してください。
TypeScript: オーバーロードメソッドを定義する方法 - QiitaVue.jsの推論
ここから推論の流れについてみていきたいと思います。
Vue.extend
の型定義はこちらになります。定義が長い上オーバーロードが5つもあってなんか発狂したくなりますね・・・。extendの定義export interface VueConstructor<V extends Vue = Vue> { // propsが名前指定の場合 extend<Data, Methods, Computed, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>; // propsをオブジェクトで指定した場合(これを調べる) extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>; // 関数型のpropsが名前指定の場合 extend<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>; // 関数型のpropsをオブジェクトで指定した場合 extend<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>; // それ以外 extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>; }https://github.com/vuejs/vue/blob/v2.6.11/types/vue.d.ts#L86-L90
とりあえず、今回知りたいのは以下のようなoptionを渡したことにしたいので、2番目のextendの型について調べたいと思います。
import Vue from 'vue'; export default Vue.extend({ props: { hoge: String, }, data() { return { value: 10, }; }, computed: { _double() { return 2 * this.value; }, }, methods: { action() { console.log('Action!!'); }, }, });今回見たいものだけを抜粋して整理すると、以下のようになります。
extendの定義export interface VueConstructor<V extends Vue = Vue> { // propsをオブジェクトで指定した場合 extend<Data, Methods, Computed, Props>( options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props> ): ExtendedVue<V, Data, Methods, Computed, Props>; }
- Data, Methods, Computed, Propsを推論
- 推論するために
ThisTypedComponentOptionsWithRecordProps
でラップExtendedVue
を返す推論する道のりがあまりにも長いので、上記のパラメータを渡した際に推論された結果を頭に入れた上で話を進めていきたいと思います。
推論された結果Data = { value: number }; Methods = { action(): void }; Computed = { _double: number }; Props = { hoge: string };返り値の型
まず返ってくる型を見ます。元々のVueの情報に推論された結果を結合して同じ
VueConstructor
を返します。これでextendを継続して使えますね。ExtendedVueexport type ExtendedVue<Instance extends Vue, Data, Methods, Computed, Props> = VueConstructor<CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue>;
CombinedVueInstance
は簡単で、単純にDataやMethodsなどを結合しているだけです。CombinedVueInstanceexport type CombinedVueInstance<Instance extends Vue, Data, Methods, Computed, Props> = Data & Methods & Computed & Props & Instance;引数側の型
続いて引数側の
ThisTypedComponentOptionsWithRecordProps
について見ていきます。
更にComponentOptions
でラップされて泣きそうになりますが、先に3つ目のThisType
について説明します。これはthisの型を定義しており、this.value
みたいなアクセスができるようになります。CombinedVueInstance
は返り値の型の説明でもした全部の型を結合するやつですね。ThisTypedComponentOptionsWithRecordPropsの型定義export type ThisTypedComponentOptionsWithRecordProps<V extends Vue, Data, Methods, Computed, Props> = object & ComponentOptions<V, DataDef<Data, Props, V>, Methods, Computed, RecordPropsDefinition<Props>, Props> & // this.~で書ける型の定義 ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Props>>>;さらに入って
ComponentOptions
の定義をみます。ここでようやくdataとか、propsとか見慣れたプロパティを目にします。ここで書かれた内容が推論されることになります。
genericsでデフォルトの型が代入されていますが、親からは全部渡されているので今回は無視して大丈夫です。
次から各プロパティについて簡単な順で見ていきます。ComponentOptionsexport interface ComponentOptions< V extends Vue, Data = DefaultData<V>, Methods = DefaultMethods<V>, Computed = DefaultComputed, PropsDef = PropsDefinition<DefaultProps>, Props = DefaultProps > { data?: Data; props?: PropsDef; propsData?: object; computed?: Accessors<Computed>; methods?: Methods; // 他は省略 }methodsの推論
methodsが一番簡単で、methodsプロパティに書かれた内容がそのまま
Methods
の型として推論されます。// こういうプロパティだと { methods: { action() { console.log('Action!!'); }, }, } // こういう風に推論される Methods = { action(): void };余談ですが何もチェックしていないのでここに値をセットしてもエラーにならないですね(笑)。genericsの
Methods
に何かしらextendsしていたらメソッドだけとか縛れそうですけどね。{ methods: { hoge: 'not error!', // エラーにならない action() { console.log('Action!!'); }, }, }dataの推論
Data
って書かれてますが、実際親から呼ばれているのはDataDef
になっているので注意してください。
そのDataDef
は以下のような定義になっています。DataDeftype DataDef<Data, Props, V> = Data | ((this: Readonly<Props> & V) => Data)そのまま書いた場合はそれを
Data
の型にして、メソッドの場合はthisにProps
とVueの型を持ってData
を返すという定義になっています。そういえばdataプロパティのメソッドではpropsの値も使えましたね。その辺の定義もキチンとしているようです。
computedの推論
computedは
Accessors<Computed>
で推論されます。
Accessorsの定義は以下です。Accessorsexport type Accessors<T> = { [K in keyof T]: (() => T[K]) | ComputedOptions<T[K]> } export interface ComputedOptions<T> { get?(): T; set?(value: T): void; cache?: boolean; }各プロパティにおいてメソッドで書くかget, setのようなオブジェクトを更に含めるかの判定をしているようです。よくこれで推論できますね・・・。
余談ですが、推論するのが難しいため返り値には型を書くように推奨されていますね。
TypeScript のサポート — Vue.jspropsの推論
最後にpropsですが、推論の道は長いです(汗)。
まずgenericsの型にはPropsDef
とProps
があるのですが、PropsDef
の方を見れば良さそうです。(よく分かっていませんが、Props
の方はrenderのところで使われていました)
PropsDef
の方は親からはRecordPropsDefinition<Props>
で定義されているので、これの定義をみると以下のようになります。関連する型も載せましたが多いですね・・・。RecordPropsDefinitionexport type RecordPropsDefinition<T> = { // なぜかqiita上で表示されなかったので先頭に#をつけています。実際はないので注意してください。 # [K in keyof T]: PropValidator<T[K]>; } export type PropValidator<T> = PropOptions<T> | PropType<T>; export interface PropOptions<T=any> { type?: PropType<T>; required?: boolean; default?: T | null | undefined | (() => T | null | undefined); validator?(value: T): boolean; } export type PropType<T> = Prop<T> | Prop<T>[]; export type Prop<T> = { (): T } | { new(...args: never[]): T & object } | { new(...args: string[]): Function }詳細は省きますが、最終的には
Prop<T>
のところまで行き、コンストラクタの型をみることになります。ちなみに
Prop<T>
の型定義があんまりよくなくって、1つ目の{ (): T }
がコンストラクタじゃないせいでJSのDate型はstring型と解釈されてしまいます・・・。余談
propsDataというプロパティがありましたが、どうやらテストの時に便利なプロパティらしいです。
https://qiita.com/ykhirao/items/43397531e58664ef4645
補足資料
今回の型推論を理解するために手書きのノートを書いているのでそれも載せておきます。
型推論調査の目的
以上がoptionsを渡した時の推論の流れでした。なかなか難しいですね・・・。
今更ながらこれを調べた理由を説明しますと、以下の2つありました。プラグインを作った際にoptionsで定義したものを使いたい
僕は定数をtemplate側でも使えるようなプラグインを作ったのですが、Vue.jsのプロパティを今はとりあえずanyにしちゃっていて、できればoptionの設定から見れたないいなぁって思っていました。
@wintyo/vue-constants-injection-plugin - npm
vue-constants-injection-pluginの型定義// extend option declare module 'vue/types/options' { interface ComponentOptions<V extends Vue> { C?: { [key: string]: any; } } } // extend property declare module 'vue/types/vue' { interface Vue { $C: { // できればComponentOptionsから定義したものを使いたい! [key: string]: any; }; } }まぁ今回調べた結果だとほぼ無理ですね(笑)。extendsを更にオーバーロードさせたらもしかしたらできるかもしれませんが、あれ以上カオスにはしたくないですね・・・。
$data, $propsに型をつけたい
昔以下の記事を投稿しましたが、
$data
,$props
を使っていきたいという気持ちをずっと持ち続けています。しかしVue.jsの型定義をみると
Record<string, any>
になって全く型がつかないのが現状です。https://github.com/vuejs/vue/blob/v2.6.11/types/vue.d.ts#L33-L34
今回調べた感じだと
Props
とかを普通に$props
にも適応してあげれば解決しそうですが、元々がanyなので結局エラー扱いにはできないというもどかしさがあります・・・。
これはいつかまた別な記事で書くかもしれません。終わりに
Vue.jsのoptionsを渡した時の推論の流れについて調べましたが、かなり大変でした・・・。
Vue.js 3.0からはComposition APIがリリースされる予定ですが、こうした背景からoptions APIは使われなくなるのかなぁって思ってきました。ただComposition APIだと$router
とかその辺の注入が一切なくなってしまうので移行は結構大変そうだなぁって思いました。Classベースがネイティブでサポートされたらいいんですけどね・・・。
- 投稿日:2020-02-23T11:50:37+09:00
「何か食べたいけど何が食べたいのか分からない」あなたへ
きょう何食べようか悩んでいるあなたへ
5つの質問に答えるだけで、料理ジャンルをサジェストしてくれるサービスを公開しました
https://kyounanitaberu.appspot.com/
きっかけ
きっかけは一つの動画。
私は美味しいものを食べることが大好きなんですが、好きが故に何を食べるかめちゃくちゃ悩んで時間を消費してしまいます。
この動画を見て「同じことを思っている人がいるんだ」と気づけたので
2択で今の気分を選ぶと、料理ジャンルをサジェストしてくれるWebアプリケーションを作りました。システム
システムとしてはVue.jsで作ったアプリをGCP(GoogleCloudPlatform)にデプロイしているちょーシンプルな構成です。
画面構成も”NavBarコンポーネント”と”Cardコンポーネント”と至って簡単。
$ tree -L 2 --matchdirs src src ├── App.vue ├── components │ ├── Card.vue │ ├── NavBar.vue │ ├── Search.vue ←食べログとUberEatsのリンク │ └── Share.vue ←SNSのリンク ├── data │ ├── questions.js ←”質問・回答”と”次の質問・回答”or”サジェストする料理ジャンルID”のマッピングファイル │ └── results.js ←”料理ジャンルID”と”ジャンル名や画像”とのマッピングファイル ├── main.js ├── store │ ├── actions.js │ ├── index.js │ └── mutations.js └── views └── Main.vueちなみに表示するカードの切り替えにはVuex、UIライブラリにはBootstrapを使っているので、Vueを勉強したての人が一通り復習するのにいいかも、と思いました
また、GCPへのデプロイは、Vue.jsで作成したSPAなアプリをGoogle App Engineへデプロイするを参考にさせていただきました、ありがとうございます。
感想
昨日さっそく使ってみたのですが、いつも30分くらい悩むところを(悩みすぎ)5分くらいでパパッと決められて満足です。
改善点は色々ありますが、質問や回答を自分で考えてファイルとして定義しているので、こういうところで機械学習を使っていきたいなーと思いました。
最後に、私はVueもGCPも初心者ですが1〜2日(ずっと開発してたわけではない)でゼロから公開までできました。
VueやGCPに興味をお持ちの方に「このくらいのものは作れるんだ」と思ってもらえると嬉しいです
- 投稿日:2020-02-23T01:43:57+09:00
Vue.js備忘録
Vue.jsに関する個人的な備忘録です
一つの記事にまとめるほどじゃない情報等をまとめていきます。
もしかしたら切り出して別途記事にするかもしれませんが。Vue.jsの思想・特徴
- データ駆動(データドリブン)、リアクティブプログラミング
- コンポーネント志向(HTML/CSS/JSを部品単位で扱う)
- Atomic Design
- MVVMパターン(Model-View-ViewModel)
- 仮想DOM
- テンプレート構文(複雑な処理を書きたい場合は描画関数やJSXで実装することが公式でおすすめされていました)
- Scoped CSS(CSS in JSでも記載できるみたいです(Vueで使えるCSS in JSを調べてみた))
- vue自体はコア機能のみ
コーディング規約
- templateの中はチェインケース
- scriptの中はアッパーキャメルケース
Vueのフォーマット
vueファイル
算出プロパティ (computed property)
複雑な計算はtemplateではなく、computedプロパティに切り出してロジックを記載することで
可視化・保守性を向上するディレクトリパスの@
- @/folder : アットマーク1つはソースディレクトリのルートを示す
- @@/folder : アットマーク2つはプロジェクトディレクトリのルートを示す ## export vueの機能ではないですが、importで呼び出すために利用します export
main.js
- vueを動かす起点となる
- mainクラスみたいなもの
App.vue
- 親となるコンポーネント
- App.vueとすることがお作法?
Vue.config.productionTip
trueで開発者向けのメッセージがコンソール出る
< router-view / >
routerで紐づいたtemplateを表示する個所を指定する
Vuex
Stateパターンとその他機能を実現するライブラリ
その他
仮想DOM
- DOM全体を操作するJQueryとは異なる思想
- 仮想DOMを生成し変更前との差分を更新していく:diff/patch
参照ページ
vueの思想・特徴
他のフレームワークとの比較
描画関数とJSX
Vueで使えるCSS in JSを調べてみた
算出プロパティとウォッチャ仮想DOM