カテゴリーアーカイブ Vue

著者:杉浦

【Vue.js】コンポーネント外で定義した値をテンプレート内で使う

 Vue.jsの単一ファイルコンポーネントはコンポーネントを構成するHTMLテンプレート、JavaScript、CSSを一ファイルにまとめる仕組みです。コンポーネントの構成要素全体を把握しやすい点で優秀なのですが、テンプレート部で扱うことのできる変数が若干不透明で時として直感的でない振る舞いをします。
 次の例ではコンポーネント内全体がスコープっぽいところで変数hogeStrを宣言してテンプレート内で使用しようとしますが、これはhogeStrが未定義扱いされます。

<template>
  <div>{{ hogeStr }}</div>
</template>

<script lang="ts">
const hogeStr = 'hoge';

import Vue from 'vue';
export default {};
</script>

 実際に扱うときにはVueインスタンスの内部に含まれなければいけません。変化のない定数でも必須です。そのため実際は次のようにします。

<template>
  <div>{{ hogeStr }}</div>
</template>

<script lang="ts">
const hogeStr = 'hoge';
import fuga from 'fuga';

import Vue from 'vue';
export default {
  data() {
    return {
      hogeStr // 変化する値はdata
    };
  },
  computed: {
    nearMsgEnum, // 変化せず、getterのみでいいならcomputed
  },
};
</script>

 単一ファイルコンポーネントの作りではテンプレート、JavaScript、CSSがタグごとに分かれますが、結局のところtemplateタグもVueインスタンスの一部でありVueインスタンス外部を見るスコープにないようです。
 余談ですがdata, computedで同じ名前の値を読み込んでもVue.jsは動き続けます。外部から値を読み込むように作り替えた時に想定外の挙動が起きたならば、これが起きているのではないかと疑えます。

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

【Vue.js】スプレッド構文と外部定義によるコンポーネントファイルの縮小化

 Vue.jsの単一ファイルコンポーネントはコンポーネントを構成するHTMLテンプレート、JavaScript、CSSを一ファイルにまとめる仕組みです。コンポーネントの構成要素全体を把握しやすい点で優秀なのですが、コンポーネントの構成要素全体を含むが故にファイルの肥大化を招きがちです。よくある肥大化を防ぐ方法の一つはコンポーネントに分割する粒度を細かくしていく方法です。理解しやすい方法なのでこれができるならこちらの方がいいでしょう。しかし分割困難かつ複雑なロジックを持つコンポーネントが現れる時があります。この状況に対応するため、ロジックを外部ファイルに定義、コンポーネントはロジックを呼び出すのみ、呼び出し方はスプレッド構文で安全に簡単に記述、という方法を考えます。
 スプレッド構文はJavaScriptの記法の一つです。正直滅多に使いません。
スプレッド構文 – JavaScript | MDN
 ここではObjectに関するスプレッド構文を扱います。動作は次の通り。public property限定の継承のといってもそう間違っていない印象があります。

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }

// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals
// スプレッド構文 - JavaScript | MDN#Object リテラルで使う

 注意点として浅いコピーなためコピーしたObjectとコピー元Objectの両方がネスト先の参照を共有する点があります。まあ不変な対象をコピーするならばshallow copyもdeep copyも変わらないのでそういう時は何も気にせず記述して大丈夫です。
 スプレッド構文を利用してVue.jsのコンポーネント中で定義を呼び出します。例えば、VuexのmapHoge系です。

computed: {
  // オブジェクトスプレット演算子で、外のオブジェクトとこのオブジェクトを混ぜる
  ...mapState({
    // ...
  })
  // コンポーネント個別のcomputedメソッド
  localComputed () { /* ... */ },
}
// https://vuex.vuejs.org/ja/guide/state.html#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E6%BC%94%E7%AE%97%E5%AD%90
// ステート | Vuex

 Vuexのステート参照用メソッドを返すmapState関数の結果をスプレッド構文でコピーして、コンポーネント独自のcomputedメソッドと混ぜ合わせます。これでcomputedの中は次のように書いたのと同じになり楽できます。

computed: {
  // mapStateの結果が展開
  storeComputed1 () { /* ... */ },
  storeComputed2 () { /* ... */ },
  storeComputed3 () { /* ... */ },
  // コンポーネント個別のcomputedメソッド
  localComputed () { /* ... */ },
}
// https://vuex.vuejs.org/ja/guide/state.html#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E6%BC%94%E7%AE%97%E5%AD%90
// ステート | Vuex
  • この記事いいね! (1)
著者:杉浦

【Vue.js】定義を外から注入することでコンポーネントのコードを減らす

 Vue.jsはJavaScriptのフレームワークでページを部品単位で切り分けて定義することが得意です。一方、何かしらのモデル定義を作ったり、Vue.js用状態管理ツールVuexの定義する状態ストアの様にVue.jsの定義するコンポーネントの外に集約が存在するコードを書くこともあります。後者のパターンに入りながらもコンポーネントに囚われていると共通化すべき部分を個々のコンポーネントに重複して記述してしまう場合が出てきます。この記事ではそういった外部で共通化されたコードを注入する方法を紹介します。
 コードは次です。

// AppConfigStore.ts
/** 状態定義を記述したコードを省略 **/
export const lineColor = {
    // 簡素なsetter, getterを持つObjectを定義。このstoreに密に関わる部分のためstoreの役割にも合っている。
    /**
     * @return {number}
     */
    get() {
        return store.state.AppConfigStore.lineColor;
    },
    /**
     * @param {string} newColor
     */
    async set(newColor: string) {
        await store.dispatch('AppConfigStore/setLineColor', newColor);
    },
};
// Hoge.vue
import {lineColor} from '@/store/MapStore';
import Vue from 'vue';

Vue.extend({
    computed: {
        lineColor: lineColor, // importから読み込んだsetter,getterをあてはめ
    }
});

 コンポーネント外に定義を書いてコンポーネント内でimportするのみです。例ではcomputedですがpropsなど他にも色々使えます。注意点はコンポーネントを指すthisを外に持ち出そうとしないことです。これは難解になってミスが起きやすい上、外部コードに責務外のことまで任せることになりがちだからです。
 

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

.envファイルの読み方色々

 .envは環境変数を定義するファイルとしてよく使われているファイル名です。この記事はいくつかの言語、フレームワークにおける.envの読み方の紹介をします。

今どきの環境は.envがあることを前提として作られている機能があります。例えば、PHPのフレームワークであるLaravelやJavaScript実行環境であるNode.jsです。

// Laravel
$version = env('VERSION');
// Node.js
const version = process.env.VERSION;

 dockerに至っては暗黙の裡に取り込んでしまいます

# laradockから引用
# .env
PHP_VERSION=7.2

# docker-compose.yml
### PHP Worker ############################################
    php-worker:
      build:
        context: ./php-worker
        args:
          - PHP_VERSION=${PHP_VERSION}

 AltJSをコンパイルする環境ならば特定の接頭辞の環境変数を定数をあてはめる形で実装されていることが多いです。

// Vue用開発ツールVue CLIならばVUE_APP_ほげほげ
const version = process.env.VUE_APP_VERSION
// Laravel用webpack拡張のLaravel MixならばMIX_ほげほげ
const version = process.env.MIX_VERSION;

 Pythonの様にdotenvというパッケージがある場合もあります(Rubyもそうらしい)。

dotenv_path = join(dirname(__file__), '.env') # .envへのパスを定義
load_dotenv(dotenv_path) # 環境変数に.env中の値を追加
version = environ.get("VERSION") # 環境変数から値を取得

 ちょっと手間ですがbashの中で読むこともできます。

script_dir=$(cd "$(dirname "${BASH_SOURCE:-$0}")" || exit; pwd)
cd "${script_dir}" || exit

eval "$(cat .env <(echo) <(declare -x))"
  • この記事いいね! (0)
著者:杉浦

【Vue.js】Vuexで情報の永続化と読み込み

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

 Vuexは状態管理パターン + ライブラリであり複数のコンポーネント間を横断する状態を示すデータが多くあるような状況で真価を発揮します。Vuexを使った場合、状態の表現はコンポーネントから分離されます。そのためVuexを使っているとコンポーネントとは違った単位で状態をまとめることを考えやすくなります。例えばアプリの設定です。この記事ではアプリの設定の保存、読み込みを例にしてVuexで情報の永続化と読み込みの楽なやり方を紹介します。
 情報の永続化と読み込み管理で主となるVuexの機能はプラグインです。
プラグイン | Vuex
 プラグインはstoreとのやり取りが発生した時に発火するイベント内容を定義する関数を追加する機能です。これによってVuex初期化時に設定ファイルからデータ読み込み、データ更新時に設定ファイルを更新、をします。コードは以下になります。

// プラグイン定義
const repositoryPlugin = async (store:Store<any>) => {
    try {
        // アプリ設定のモジュールに設定ファイルからデータ読み込み
        const content: AppConfigStoreState = JSON.parse(
            fs.readFileSync(path.join('./app.conf'), {encoding: 'utf8'})
        );
        // 解釈した設定ファイルに基づいて状態を更新. 値がundefinedの場合は更新しない
        content.LineColor && await store.dispatch('AppConfigStore/setLineColor', content.LineColor);
        content.updateIntervalMilliSec && await store.dispatch('AppConfigStore/setUpdateIntervalMilliSec', content.updateIntervalMilliSec);
    } catch (e) {
        if (!(e.toString().match(/ENOENT: no such file or directory/g))) {
            // 設定ファイルが存在しない以外のエラーの場合は握りつぶさずに上へ投げる
            throw e;
        }
    }
    // 更新が起きるたびにイベント発火
    store.subscribe((mutation, state) => {
        if (mutation.type.match(/AppConfigStore/g)) {
            // 更新が起きた状態が設定モジュールの時のみ外部の設定ファイルに出力
            fs.writeFileSync(path.join('./app.conf'), JSON.stringify(state.AppConfigStore), {encoding: 'utf8'});
        }
    });
};
// store.ts Vuexインスタンス化コード部
export default new Vuex.Store({
    state: {},
    mutations: {},
    actions: {},
    modules: {
        AppConfigStore,
        HogeStore,
    },
    plugins: [repositoryPlugin], // プラグイン登録
});

 上のコードでは外部ファイルに吐き出すようにしましたが、実際はデータベースでもローカルストレージでも自由です。重要なのはプラグインでストアの初期化時、更新時に任意の動作を簡単に仕込めるという点です。

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

【Vue.js】vue-cliでElectronとVueによるデスクトップアプリを作る

 Electronはweb開発に同様のコードでデスクトップアプリを作成するためのプラットフォームです。もともとweb系の開発者ならば新たな言語を増やさずにデスクトップアプリを作ることができます。Electronは便利なのですがただ使うだけでもそれなりに設定が必要になります。vue-cliとつなげるとVue用のボイラープレートがそろった段階で開発を始められます。
 Electron | Build cross platform desktop apps with JavaScript, HTML, and CSS.
 vue-cliはVue.js回りの開発を行うための様々なものが詰め込まれたツールです。GUIが備わっているのでなんとなく色々触るだけでも結構使い方がわかります。
Vue CLI
用意されている中で特に便利なのがプラグインです。Vueと何か他の抽象的な大がかりなパッケージ(TypeScript、ESLint、Jestなどなど)を組み合わせる時に面倒になる周辺設定をパパっと済ませてくれます。Electronもこれに含まれます。
 まずはVue.jsが含まれているプロジェクトを開きます(npm i -g @vue/cliでインストール。vue uiでGUIに移動。新しいプロジェクトの作成でもインポート[既存のvueコード読み込み]でもOK)。後はプロジェクトプラグインからvue-cli-plugin-electron-builderを選んでインストールするだけです。

 するとElectronを扱うのに必要な環境設定コードと実行スクリプトがダンプされます。


vue uiの画面を使えばビルドの設定もUIでできます。これでビルドスクリプトを実行すればデスクトップアプリの完成です。

 

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

【Vue.js】コンポーネントの持つ値をCSSに伝える

 Vue.jsで小さなコンポーネントを作っていると時折デザインの一部をコンポーネントを呼び出す側で自由に決定させたい時があります。この記事ではそのやり方を紹介します。
 コンポーネントの持つ値をCSSに伝えるために重要な機能はCSSにおける変数であるカスタムプロパティです。
 カスタムプロパティ (–*): CSS 変数 – CSS: カスケーディングスタイルシート | MDN
 CSS カスタムプロパティ (変数) の使用 – CSS: カスケーディングスタイルシート | MDN
 カスタムプロパティは次のように用います。カスタムプロパティの適用対象は定義したHTMLElementの子方向の要素全てです。

div.hoge-box {
    --hoge-box-width: 3em; /* --hogeで宣言 */
    width: var(--hoge-box-width); /* var(--変数名)で使用 */
}

div.hoge-box > div {
    width: calc(var(--hoge-box-width) / 6) /* calcでも使える */
}

html {
    --global--base-color: hsl(0, 0, 0); /* html, :root, bodyの様な広い範囲で指定するとグローバル的に使える */
}

 コンポーネントのルートエレメント上でコンポーネントの持つ値を用いてカスタムプロパティを定義することでCSSへ任意の値を与えます。これは例えば次のようにpropsで与えられた値をcomuputedでまとめ、まとめた値をルートエレメントであるdiv.check-boxのスタイルとして定義、定義した変数をCSS内で扱う、というやり方です。

<template>
  <div
    :style="styleVariables"
    class="check-box"
  >
    <div class="check"/>
  </div>
</template>
<script>
export default Vue.extend({
  props: {
    checkboxWidth: {
      type: String,
      default: '3em',
    },
  },
  computed: {
    /**
     * このコンポーネント中で用いるCSS変数をまとめる.
     * @return {object}
     */
    styleVariables() {
      return {
        '--checkbox-width': this.checkboxWidth,
      };
    },
  },
};
</script>
<style lang="scss" module>
  .check-box {
    --checkbox-width: 3em;// 事故防止のデフォルト値
    border: 2px solid #9e9e9e;
    border-radius: 2px;
    width: var(--checkbox-width);
    min-height: var(--checkbox-width);
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .check{
    position: relative;
    top: calc(var(--checkbox-width) / 6); // def=0.5em
    left: calc(var(--checkbox-width) / -20);// def=-0.15em
    transform: rotate(-45deg);
    transform-origin: left top;
    border-left: 2px solid #000;
    border-bottom: 2px solid #000;
    width: calc(var(--checkbox-width) / 2.4 * 1.618); // def=2.0225em
    height: calc(var(--checkbox-width) / 2.4); // def=1.25em
  }
</style>

 上記コードの動作は次のデモの通りです。

 いたるところでサイズをemで指定していますが変数を介しているためデザインを崩さずデザインを容易に変更できます。変数はコンポーネントのプロップで決定しているため、コンポーネントを呼び出す側が安全にデザインを変えられます。この変数渡しのやり方に加えて、アニメーションや状態の定義と変化を用いることで自由に簡単に扱える最小単位のコンポーネントを作りやすくなります。
 余談ですが、カスタムプロパティを用いる際、CSS独立ファイルでは未定義の変数名を呼び出そうとするか、定義セレクタを多量にコピペすることになり辛くなります。SCSSの様な階層構造を定義できるCSS拡張言語を用いると安全に簡潔に記述できます。

// scssコード
div.hoge-box {
    --hoge-box-width: 3em; // --hogeで宣言
    width: var(--hoge-box-width); // var(--変数名)で使用

    div { // div.hoge-box divと同じ意味になる
        width: calc(var(--hoge-box-width) / 6) // calcでも使える
    }
}
  • この記事いいね! (1)
著者:杉浦

【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)