カテゴリーアーカイブ TypeScript

著者:杉浦

【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)
著者:杉浦

【TypeScript】TypeScriptの型定義の仕方

 TypeScriptはJavaScriptの拡張言語です。Microsoftがメインのコントリビューターでサポートも充実しています。TypeScriptはAltJSの中でも穏やかな方でJavaScriptに独自の型定義機能と変数、プロパティと結合した型宣言機能といった型関連の機能を付けただけです。変化が少なく汎用的なためかVue.js+TypeScriptやRact+TypeScriptといったフレームワークとの結合を解決するパッケージも盛んに開発されています。型が付くことによりIDEの予測や補完、バグの潜在性アラートなどが強力に働きます。

TypeScript – JavaScript that scales.
microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

 始めるならQuick Start · TypeScript試すならTypeScript playgroundがやりやすかったです。特にクイックスタートでは各パターンのテンプレがGitリポジトリに用意されており、それをひな型に手を動かすだけで慣れていけます。
 TypeScriptは基本的に変数、プロパティ、関数の宣言時に宣言部の後に:をつけて型を決めます。

const a: number = 1;

function hoge(): string {
    return 'hoge'
}

class Bar {
}

interface FooBar {
    foo: Date;
    bar: Bar;
}

class Fuga implements FooBar {
    foo!: Date;
    // !はnullになることはないとTypeScriptに伝える.
    // フレームワーク中のコンストラクタっぽいもので初期化される時とかに便利.
    bar: Bar;

    constructor(bar: Bar) {
        this.bar = bar;
    }
}
type StringOrStrings = string|string[];
const char: StringOrStrings = 'ch';
const chars: StringOrStrings = ['c', 'h'];

 この様にすると、それぞれの部分に型が定義され値の型を追うことができます。上記のみならずEnum等さらに拡張することもできます。
 厳密な型定義から外れますが利便性のために型定義を変数を介さず文中に入れたい時があります。そういった時は次のようにします。

(document.head.querySelector('meta[name="csrf-token"]') as HTMLMetaElement).content

 この様にすると括弧でくくった部分の型を定義できます。セレクタで特定の種類の要素(metaタグ、inputタグなど)を参照するときなど特に便利です。

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