カテゴリーアーカイブ Vue

著者:杉浦

【Vue】Vuexことはじめ

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。
Vuex とは何か? | Vuex

 Vuexは上記の様にVue.jsで作成したプログラム中の状態管理を担うライブラリです。乱暴な言い方をするとVuexは管理しやすいグローバル変数を提供してくれます。Vuexのやることはコードと合わせてを見るとわかりやすいです。

// Vuexのストア(状態管理定義)の記述
export const HogeStore:Module<any, any> = {
    // 管理する状態。グローバル変数の本体的なモノ。
    // これを好き勝手弄らせないが、どこからでも参照できるようにする。
    state: { 
        foo: [],
        bar: false,
    },
    // 管理する状態を直接弄ることのできる関数群。ここにある記述以外ではstateを変更できない。
    // store等を介して、commit()関数で呼び出してのみ実行される。
    // 下記の様に思いっきり抽象化すると扱いやすい。
    mutations: {
        setFoo(state, newFoo){// state.fooを変更する
            state.foo = newFoo;
        }
    },
    // 具体的なcommitの内容を記述する。
    // コンポーネント中でstore.dispatch(アクション名)とやると直接commitするより安全安心。
    actions: {
        setFoo(context, newFoo){// state.fooを変更する.mutationsと名前がかぶってもOK
            context.commit('setFoo', newFoo);
        },
        pushFoo(context, newFoo){// state.fooに追記的な
            context.commit('setFoo', newFoo);
        },
        async resetFoo(context){// state.fooをAPI等のリポジトリから取得して改めてセット
            const newFoo = await FooRepository.get();
            context.commit('setFoo', newFoo);
        }
    }
}
// コンポーネント側の記述
// 参照の際には算術プロパティを利用すると便利
computed: {
    foo: {
      get() {
        return store.state.HogeStore.foo;
      },
      set(newFoo) {
        store.dispatch('setFoo', newFoo);
      },
    },
},
methods: {
    async reset() {
        await store.dispatch('resetFoo');// dispatch('アクション名')でactions中のメソッドを呼び出す
    }
}

 公式を読むともっと多彩なことができますが、とりあえず上記のだけでも十分に機能を発揮します。setterが厳格でgetterが自由なグローバル変数な印象を受けました。getterは追記できるため同じリソースを多角的に読みたい際も複雑さがそれほど増しません。
 Vuexを用いるタイミングですが、コンポーネント間のemitが長大になる時でしょう。例えば、あるコンポーネント群(メニューとか検索ボックス)はリソースを取得、あるコンポーネント群(表とかグラフとか)はリソースを表示、あるコンポーネント群(編集モーダルとか)はリソースを加工、とかやりだすと値の変更を4つ5つ越しのコンポーネントに伝播させる必要が出たり、その伝播のルートが3つ股4つ股になったりと煩雑になります。Vuex抜きにこれを整理しようとすると各コンポーネント群の重心であるメインコンポーネント(コンポーネントを呼び出すコンポーネント。App.vueとかPages/Fuga.vueとか)の中に処理が集中しだします。こうなるとメインコンポーネントのやっていることがまさしくVuexのやるべきことになります。
 Vuexはコンポーネント間の依存が直列や一方向の様な整然とした場合は不要ですが、そうでない時は値のやりとり最低限になりすっきりします。また純粋なTypeScriptで記述ができるため動作を明確にしやすいです。先述の例の様にVueアプリが複雑な場合、どこかしらでVuexの役割を既に担おうとしている部分があります。ソースコードが複雑化し、もうどうにもならない時はいっそ後付けでVuexを追加する良いです。

  • この記事いいね! (1)
著者:杉浦

【Vue.js】Vue.extend()でVue.jsらしい記述のままTypeScriptを適用する

 Vue.jsは素のwebページ用コード(HTML, JavaScript, CSS)の雰囲気を保ったままいい感じに動いてくれるJavaScriptフレームワークです。特に単一ファイルコンポーネントはHTMLをHTMLのまま、VueオブジェクトをVueオブジェクトのまま、CSSをCSSのまま表現してまとまったデザインのコンポーネントを作り上げます。

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
module.exports = {
  data: function () {
    return {
      greeting: 'Hello'
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

単一ファイルコンポーネント — Vue.js
 これは便利なのですが、TypeScriptとはどうにも相性が悪いです。少なくとも2点問題点がありそれはJavaScriptコード部で定義された値をテンプレートに当てはめた時に型定義を持ち越すのが難しい点とJavaScript部の定義がほとんどが素朴なオブジェクトのためTypeScriptの型構文を記述する余地が少ない点です。
 上記の問題を解決する方法の一つとしてvue-property-decoratorがあります。下記の引用の様にclass構文と@以下によるデコレートでVueコンポーネントであることを表現します。

    @Component
    class Test extends Vue {
      @Prop(Number) [propertyName]!: number // @Propでpropsの一つであることを表現
    }

 kaorun343/vue-property-decorator: Vue.js and Property Decorator
 これはこれでありですし言語実装としては簡単(何がどこに現れるのか、どこからどこまでが区切りなのか厳密)なのでしょうが、Vue.jsからいくらか離れた記法を用いる点でIDE、ESlint、storybookなどと組み合わせることと機能の把握に面倒が起きます。こうなるならばReactを使って新たにJavaScriptでまとめ切る方が便利な気がします。
 vue-property-decoratorの難点を解決する方法としてVue.extend()というやり方があります。これはvue-property-decoratorからいくらか後の実装で追加されたようです(また聞きで一次ソース未確認)。
 API — Vue.js#Vue-extend
 このやり方とTypeScriptを組み合わせると次のように記述できます。素の状態では素朴なオブジェクトしか返せませんでしたがVue.extendの引数としての型であるComponentOptionsを得ました。

<template>
  <p>{{ greetingToTarget }}</p>
</template>

<script lang="ts">
import Vue, {ComponentOptions, PropType} from 'vue';

type HelloWorldDataType = {
    greeting: string,
    modalLevel: string,
}
export default Vue.extend({
  props: {
    target: {type: String as PropType<string>, required: true},
  },
  data: function (): HelloWorldDataType {
    return {
      greeting: 'Hello' // data全体の返り値の型を決めるのでなく、ここでas stringとするのも手
    }
  },
    computed: {
      greetingToTarget():string {
        return `${this.greeting} ${this.target}!`;
      }
  },
} as ComponentOptions);
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

 上述のようにするとIDEの型に関する機能や予測補完が強く働きだし便利な上、想定外の挙動をした時には厳しく警告を飛ばしてくれます。

 webpackのプロダクト用変換などの方法で純粋なJavaScriptに圧縮変換するとなんやかんや動くJavaScriptの特徴も復活させられます。

  • この記事いいね! (1)
著者:杉浦

【Vue.js】@vue-cliのuiコマンドが便利

 @vue-cliは主にVue.jsプロジェクトのひな型を生成するためのプログラムです。npm install -g @vue/cliでグローバル下にインストール、vue create [プロジェクトのパス]と命令して対話式にプロジェクトの内容を設定する方法が基本の使い方です。
 Vue CLI

 基本の使い方でも上画像の様に扱いやすいのですが、uiコマンドはそれ以上に便利で継続的に扱えます。

vue ui

と命令すると徐にローカルサーバが立ち上がり次のwebページを開きます。

 作成から新規プロジェクトの作成、インポートから既存プロジェクトのインポートができます。
 uiコマンドが特に便利なのがここからでnpmやyarnと組み合わさってCLIから行う何があるかもわからない機能の内、重要なものをわかりやすく分類し詳しく説明した上でUI化してくれます。Vueの拡張パッケージをプラグイン、npmリポジトリにある任意のパッケージを取り扱う依存、各設定ファイルの詳細をUI化した設定、package.json中のscriptを走らせるタスクが用意されています。


 
 IDEもコードと直結して即座に使う分にはなかなか便利なのですが、専用機能というだけあってvue-cliのuiはリッチで便利です。設定次第でちょっとしたCI(継続的インテグレーション)としても使えます。

  • この記事いいね! (0)
著者:杉浦

【Vue】改行とv-htmlとXSS対策

 Vue.js上で文字列を展開したい時があります。例えばそれは誰かが投稿したコメントをコンポーネント上に表示する時です。こういった時コメントの改行が反映されていない場合、見栄えが悪くなります。改行を行う必要があります。
 HTML上の改行と言えばbrタグです。PHPにはnl2brという改行コードを改行タグに変換する組み込み関数があるぐらいです。
PHP: nl2br – Manual
 改行の実現でありがちで危険なアンチパターンはこのnl2br関数を用いたコメントをそのまま表示しようと考えるものです。Vue.jsにはv-htmlというディレクティブがあり、これを用いると普段かかっている安全装置のHTMLエスケープを外し、HTMLとしてパース、実行します。brタグとv-htmlを用いることで改行コードを改行タグに変換して表示できます。
 v-htmlはXSSをまあまあ容易に招く危険性を持ちます。まあまあ危険というのは単純な

<script>alert('XSS')</script>

ぐらいならVueの仕組み上実行されず済むからです。とはいえ

<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>

の様な多少の変化球(scriptタグをbase64形式でエンコード、埋め込んだコード中でデコードして実行)であっさり破られるのでやはり危険です。

XSS 脆弱性を容易に引き起こすので、ウェブサイトで動的に任意のHTMLを描画することは、非常に危険です。信頼できるコンテンツにだけ HTML 展開を利用してください。ユーザーから提供されたコンテンツに対しては決して使用してはいけません。

テンプレート構文 — Vue.js

 これの対策は実現したい機能に関しての実装を生のHTMLに頼るのでなく個々の別手法を用いるのが一番でしょう。
 例えば改行に関してはCSSのwhite-space実現できます。white-spaceは要素内のホワイトスペースをどう扱うか決定するスタイルであり、ホワイトスペースの一種(少なくとも正規表現の\sグループでまとめられる)である改行文字の制御もこれでできます
white-space – CSS: カスケーディングスタイルシート | MDN
 上記リンクから引用した次の表の’改行を残す’スタイルを用いれば
と生のHTML実行を用いるまでもありません。

  改行 空白とタブ文字 テキストの折り返し 行末の空白
normal まとめる まとめる 折り返す 除去
nowrap まとめる まとめる 折り返さない 除去
pre 残す 残す 折り返さない 残す
pre-wrap 残す 残す 折り返す ぶら下げ
pre-line 残す まとめる 折り返す 除去
break-spaces 残す まとめる 折り返す 折り返す
  • この記事いいね! (0)
著者:杉浦

【Vue】jQueryとVueを共存させる

 デザインはjQueryで作られる、実装はVue.jsで行う。なんやかんやあってこの方式でwebページを作る必要が出てくることがあります。一貫していた方が作りやすいはずなのですが、なんやかんやあってこうなります。
 デザインの実装の際、jQueryとVue.jsを読み込ませるとデザイン時に動いていたイベントが消える時があります。これはVueがテンプレートエンジンも兼ねていることが原因で起きがちです。例えば次の様なシナリオで起きます。

// デザイン時
HTML読み込み完了
↓
jQueryによるイベント付与実行
↓
全てのHTMLに付与されるべきイベントが付与されている

// 実装時
HTML読み込み中、Vue構築中
↓
HTML読み込み完了
↓
jQueryによるイベント付与実行
↓
Vue構築完了。Vue内で定義されたHTML構築を持ってきて描画

 特にVueでAPIの結果をHTMLの一領域に描画する仕組みを作った時はイベントの再定義の絡みで大体こうなります。こうなる時にはVueの中でVueが読み込み完了した場合に実行されるイベントでjQueryによるイベントを定義すると良いです。

export default {
  components: {
  },
  props: {
  },
  data() {
    return {
      
    };
  },
  mounted() {
    // jQueryのイベント付与処理
  },

 このように記述するとコンポーネントが読み込まれるたびにmounted関数が実行され、テンプレート内にjQueryのイベントが付与されます。
 Vue.jsとjQueryの同時実行はJavaScriptファイルが巨大になり初期読み込みが遅くなるため積極的に行うべきでないのですが、デザイナーからいただけるデザインにjQueryでアニメーションがついていることがしばしばあります。そういった際にこのやり方を知っておくと、Vue.jsの便利さから離れないままアニメーションを崩すことも再構築することもなくデザインを再現できます。

  • この記事いいね! (1)
著者:杉浦

【Laravel】【Vue】LaravelとVueを結びつけるパッケージは割と多い

 LaravelはPHPのフレームワークです。PHPのフレームワークですがプロジェクトを建てた時点で一緒に使えと言わんばかりにフロントエンド側のVue.jsのコードと設定が渡されます。

 このためか巷にはLaravelとVue.jsを同時に使うことを前提としたパッケージが少なくないです。
 例えば、npmです。`vue laravel`でググると170件出てきます。

 一番上のlaravel-vue-pagination – npmはLaravelのクエリビルダのpaginateメソッドをレスポンスにしたAPIとVueを組み合わせてページネーションを実現するコンポーネントの様です。こういうのを作ろうとすると端の方のページを見る時の作りが面倒になりますが、作る手間を省けます。
 Packagistにはvueタグ付きのライブラリが144件出てきます。laravel-vue-generatorsは名前だけでもう相当ですね。Laravel内のコードからVueのコードを生成する様です。

 GitHubともなるとほぼ両者の和集合に加えて、草の根開発まで加わりさらに増えます。

  • この記事いいね! (1)
著者:杉浦

【Vue.js】VueとCSSアニメーションを組み合わせる

 Vue.jsは便利です。テンプレートエンジン同然の動的なDOM操作が簡潔に記述できます。例えばv-ifの様な条件付レンダリングです。
条件付きレンダリング — Vue.js

 デモの様に10行に満たないテンプレートHTMLとVueインスタンス定義で切り替えが実現できます。これは本当に簡潔な記述で、他の記述が紛れる余地がありません。このためDOM操作の際の細やかアニメーションを自らの手で付けようとしてしまいますが(少なくとも自分はそうしました)、実はVue.js自身にアニメーションをサポートする機能がついています。
Enter/Leave とトランジション一覧 — Vue.js
transition – CSS: カスケーディングスタイルシート | MDN
 VueのEnter/Leave とトランジション一覧に記述されている機能は主にCSSのtransitionにDOM操作アニメーションを明け渡すための機能です。発展形としてJavaScriptによる処理を描画中に挟む、データの変化をアニメーションにする、といったものもあります。詳しくはVue公式ガイドのトランジションとアニメーション関連を参照です。
 具体的には

<transition name="fade">
    <p v-if="show">hello</p>
</transition>

 の様にv-ifの様な条件付レンダリング要素をtransitionタグで括ることが第一歩です。transitionタグはVue組み込みのコンポーネントのタグです。このように括るとtransitionタグ内部のv-if属性付きのタグにアニメーションをつけるための要素が適宜定義されます。定義は次の画像にまとまっています。
 
 Enter/Leave とトランジション一覧 — Vue.js#トランジションクラスより引用
 この画像のvの部分にtransitionタグのname属性の値が当てはまり、その文字列のクラスがv-ifの様な表示、描画操作対象タグにつきます。具体的には次の様に使えます。

 CSSのtransitionらしく起点、終点、起点から終点への移動の仕方、のアニメーションを指定します。それぞれ無印、to、activeが当てはまります。デモのslideならばmax-heightの値を0.5秒で0から5em、5emから0へ線形的に変形させています。

  • この記事いいね! (2)
著者:杉浦

【Vue】propsで渡ってきたObjectをキーについてバリデーションする

 扱う値があまりに煩雑になってくると系統だった値をまとめてオブジェクト化して扱う様になります。Vue.jsではpropsという仕組みで値をコンポーネント間でやりとりします。基本は次の様に受け渡されるコンポーネント側で型を検査することによって致命的エラーを防ぎます。

  props: {
    memberId: {
      type: Number,
      required: true,
    },
    pageInfo: {
      type: Object,
      required: true,
    },
  },

 この方法を用いた場合、Object型の通過ルールはあまりに緩すぎます。必要なプロパティがそろっていない空のObjectでも通ってしまいます。バリデーションを用いることでこれを防げます。
 プロパティ — Vue.js
 リンク先のカスタムバリデーションがそれです。これはvalidator関数の返す値の真偽によってバリデーションの結果を判定する仕組みです。次のvalidator関数の例は必要なkeyが全てあるかないかの判断になります。

  props: {
    pageInfo: {
      type: Object,
      required: true,
      validator(value) {
        const valueKeys = Object.keys(value);
        let isIncludeAllNeedKey = true;
        ['currentPage', 'from', 'to', 'total', 'lastPage'].forEach((needKey)=>{
          isIncludeAllNeedKey = isIncludeAllNeedKey && valueKeys.includes(needKey);
        });
        return isIncludeAllNeedKey;
      },
    },
  },

 与えられた値valueのキーを配列化、必要なキーについて一つ一つincludes関数で存在の有無を確かめます。どこかで無いとわかった時、isIncludeAllNeedKey = isIncludeAllNeedKey && falseとなり、isIncludeAllNeedKey===falseの状態となり、isIncludeAllNeedKey = false && valueKeys.includes(needKey);が実行されるようになります。これでオブジェクトのキーの有無を確かめるための簡単なバリデータを作れます。

  • この記事いいね! (0)
著者:杉浦

【Laravel】【Vue】propsを使わずVue上で組んだページ部にLaravelのBladeから値を表示する

 LaravelからVueに値を渡す時によくやる手法は次です。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
            v-bind:hoge="{{json_encode($hoge)}}"
            v-bind:fuga="{{json_encode($fuga)}}"
            ...
            ...
        ></upload-image>
<template>
  <div>
    {{ hoge }}
    {{ fuga }}
    ...
    ...
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
    hoge: {type: String, required: true},
    fuga: {type: String, required: true},
    ...
    ...
  },

 引数をjson化してpropsとして渡し、Vue内のテンプレートに埋め込みます。やり方はシンプルなのですが情報が増えるにつれpropsの肥大化がひどいことになります。なんらかのオブジェクトにまとめるのも手ですが、それはそれでバリデーションが辛くなります。この問題の回答の一つがVueのslotの仕組みを使うことです。
スロット — Vue.js
 slotは指定された位置のHTMLコードをVueコンポーネントを呼び出す階層から決定するための仕組みです。そのため先ほどの例ならば次の様に書けます。

        <upload-image
            v-bind:dist-url="{{json_encode(route('hoge.update'))}}"
        >
          <div slot="slot-no-namae">
              {{ $hoge }}
              {{ $fuga }}
              ...
              ...
          </div>
        </upload-image>
<template>
  <div>
    <slot name="slot-no-namae"/>
  </div>
</template>
export default {
  props: {
    distUrl: {type: String, default: ''},
  },

 Blade上のHTMLエスケープエコーの短縮構文{{}}を実行してHTMLコードを生成。それのHTMLコードをslotに割り当てます。これにより、propsを介することなくPHP内部で扱っていた変数の値を持つHTMLコードをVue上で組んでwebページのテンプレート内に組み込めました。propした値を後から変更しない、Vue内の関数で参照する予定が無い、といった値は大体この方法に組み込むとスッキリしつつ問題のないコードを作るのに貢献してくれます。

  • この記事いいね! (1)
著者:杉浦

Chromeのtextareaでスクロール位置の変更が上手くいかないことがある

 次のデモはVueを使ったtextarea中のクリック、キーアップ、キーダウンに反応してscrollTopを取得、表示するデモです。
 
 これを動かすと少々直感的でない動作とそのscrollTopの値が現れます。例えば次です。デモはclick, key up, key downそれぞれについていますが動画はclick, key downをまとめた一つのみです。

 screenTop:339になってからEnterを押して改行を実行すると、textareaのカーソルが画面内のてっぺんになり、screenTopが更新されません。どういうわけか度々この現象が起きます。マウスでスクロールぐりぐり、上下に余裕のある場所へクリックでカーソル配置、改行で度々再現します。
 これに近い正常な現象は次の様にすると見えます。カーソルをセット、画面外に追いやってからEnterで改行をすると、textareaのカーソルがtextarea内のてっぺんになります。こちらの場合はscrollTopが更新されます。

 この二つから推測されるのは、「何らかの原因で期待するscrollTopがとれず、仮にscrollTop=0と扱って改行を実行。正常な動作同様にtextareaのカーソルが画面内のてっぺんになるようにscrollTop変更。」という動作です。Chrome自体のコードは非公開ですし、詳しい内容はわかりませんがなかなか面白い現象です。
 余談ですがChromeのscrollTopは少数以下も取っていますがFireFox, Microsoft Edgeは少数以下を切り捨ててInt型の様に振る舞っていました。また、Operaは少数ありでした。加えてOperaもカーソルの挙動が不穏でした。少数絡みで何かしらあるのかもしれませんね。

  • この記事いいね! (0)