20200223のvue.jsに関する記事は6件です。

computedによるclass変更

はじめに

公式にある基礎なのですが、最近Vueを仕事で使っていなくて忘れてしまうので、ちょっと書いておきます。

何かしらのアクションで、styleを変更する事はよくあります。
例えば、clickなどアクションへの反応、validationのエラー、処理の成功失敗などです。

v-bind:classを使う方法もありますが、公式にあるようにcomputed(算出プロパティ)を利用するのが一般的とのこと。
実際やってみると、computed利用の方が、状態をdataプロパティ、処理をcomputedにまとめられるのでコード全体がわかりやすくなると思いました。

流れ

  1. イベントでメソッド実行
  2. dataのプロパティの値を変更
  3. computed(算出プロパティ)でclassを変更
  4. styleのclassが変わる

サンプル

ボタン押下でclassが変わります。

See the Pen Change class by 'computed' by H.N (@H_Naito) on CodePen.

参考

https://jp.vuejs.org/v2/guide/class-and-style.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
Vue.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を使う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
var 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をトリガーに何かの関数を実行したい
  • データを再描画させるとか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: オーバーロードメソッドを定義する方法 - Qiita

Vue.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を継続して使えますね。

ExtendedVue
export type ExtendedVue<Instance extends Vue, Data, Methods, Computed, Props> =
  VueConstructor<CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue>;

CombinedVueInstanceは簡単で、単純にDataやMethodsなどを結合しているだけです。

CombinedVueInstance
export 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でデフォルトの型が代入されていますが、親からは全部渡されているので今回は無視して大丈夫です。
次から各プロパティについて簡単な順で見ていきます。

ComponentOptions
export 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は以下のような定義になっています。

DataDef
type DataDef<Data, Props, V> = Data | ((this: Readonly<Props> & V) => Data)

そのまま書いた場合はそれをDataの型にして、メソッドの場合はthisにPropsとVueの型を持ってDataを返すという定義になっています。

そういえばdataプロパティのメソッドではpropsの値も使えましたね。その辺の定義もキチンとしているようです。

computedの推論

computedはAccessors<Computed>で推論されます。
Accessorsの定義は以下です。

Accessors
export 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.js

propsの推論

最後にpropsですが、推論の道は長いです(汗)。
まずgenericsの型にはPropsDefPropsがあるのですが、PropsDefの方を見れば良さそうです。(よく分かっていませんが、Propsの方はrenderのところで使われていました)
PropsDefの方は親からはRecordPropsDefinition<Props>で定義されているので、これの定義をみると以下のようになります。関連する型も載せましたが多いですね・・・。

RecordPropsDefinition
export 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

補足資料

今回の型推論を理解するために手書きのノートを書いているのでそれも載せておきます。

3b46028f-5efe-4355-bd34-a759db9c4405.jpg
120afc6f-fefa-447d-ac77-e985652d3db1.jpg
beb1e0a5-0789-445f-92a8-65d08afe0de6.jpg

型推論調査の目的

以上が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のプロパティを分かりやすくする - Qiita

しかし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ベースがネイティブでサポートされたらいいんですけどね・・・。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「何か食べたいけど何が食べたいのか分からない」あなたへ

きょう何食べようか悩んでいるあなたへ

5つの質問に答えるだけで、料理ジャンルをサジェストしてくれるサービスを公開しました :hugging:
https://kyounanitaberu.appspot.com/
スクリーンショット 2020-02-23 11.03.41.png スクリーンショット 2020-02-23 11.03.22.png

きっかけ

スクリーンショット 2020-02-23 2.59.29.png
きっかけは一つの動画。
私は美味しいものを食べることが大好きなんですが、好きが故に何を食べるかめちゃくちゃ悩んで時間を消費してしまいます。
この動画を見て「同じことを思っている人がいるんだ」と気づけたので
2択で今の気分を選ぶと、料理ジャンルをサジェストしてくれるWebアプリケーションを作りました。

システム

システムとしてはVue.jsで作ったアプリをGCP(GoogleCloudPlatform)にデプロイしているちょーシンプルな構成です。

画面構成も”NavBarコンポーネント”と”Cardコンポーネント”と至って簡単。

image.png
↓ファイル構成はこんな感じ。

$ 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を勉強したての人が一通り復習するのにいいかも、と思いました :thinking:

また、GCPへのデプロイは、Vue.jsで作成したSPAなアプリをGoogle App Engineへデプロイするを参考にさせていただきました、ありがとうございます。

感想

昨日さっそく使ってみたのですが、いつも30分くらい悩むところを(悩みすぎ)5分くらいでパパッと決められて満足です。

改善点は色々ありますが、質問や回答を自分で考えてファイルとして定義しているので、こういうところで機械学習を使っていきたいなーと思いました。

最後に、私はVueもGCPも初心者ですが1〜2日(ずっと開発してたわけではない)でゼロから公開までできました。
VueやGCPに興味をお持ちの方に「このくらいのものは作れるんだ」と思ってもらえると嬉しいです :thumbsup:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

なぜ仮想DOMという概念が俺達の魂を震えさせるのか

Vue.jsの仮想DOMと差分レンダリングの仕組み

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む