[TypeScript] Vue.jsでVuexを使わないでstate管理をしたい

2018/01/27

こんにちは。きんくまです。

前回 Vue.js+TypeScriptのパターンを書きました。
>> [TypeScript] Vue.jsをTypeScriptで使ってみたい -> 成功した

今回は、「Vuexを使わないでstate管理をしたい!」です。

Vuexは、fluxパターンのReact向け実装のReduxをVue向けに作ったものです。
>> What is Vuex?

で、Vueを使った時の状態管理の定番みたいに扱われています。

ただ、ReduxもVuexもチュートリアルをやったり、実際にそれを使って書かれたソースを読んでみたのですが、どうにも好きになれませんでした、、。好きな方がいたらすみません。
理由としてはいくつかあるのですが、一番の理由は冗長なこと。後からソースを追うのに関係ファイルが増えすぎること。
たぶん数学的なのかプログラム的にはとても素晴らしいライブラリだと思うのですが、私にはオーバースペックでした。
で、普通のクラスでStoreを作って状態管理をしたくなりました。

自前でやるとき、fluxパターンの肝となるのは、

1. イベントの方向性
viewからイベント -> api -> store -> view更新

2. storeの外でstateを操作しない
(例えばcomponent内で直接いじるのはNGで、store内に操作メソッドを作る)

だと思いました。なのでこれを守れば大丈夫なのではないかと。

※2018/01/31 クラス名を変更したり、stateのアクセス方法をgetter,setterに変更しました

クラスでStore作って状態管理

いろいろと試してみて、失敗ばかりだったのですが、公式ドキュメントに載っている方法でうまくいきました。
>> State Management

できたもの

Storeクラス

Storeクラスです。Modelのルートになります。
newしたインスタンスをexportしているので、どこからでもアクセス可能なのですが、基本は親コンポーネントからpropsで子供コンポーネントに渡していく方が良いかと思います。

countプロパティを保持しているだけで特別なことはしていません。

Store.ts

import Count from "./Count";

export class Store {
    count:Count = new Count();

    constructor(){
    }
}

const store = new Store();
export default store;

Countクラス

これが一番わからなかったところです。上の公式ドキュメントを見てようやくできました。
ポイントはstateというObject型のプロパティを持たせること。
クラスに直接、countプロパティ、tickプロパティを持たせるとうまくいきませんでした。

getter, setterをもたせて直接stateにさわらなくてもよいようにしていますが、面倒だったらstateのみで直接さわってもよいのかも。ただ、getter,setterがある方が、デバッグしやすそうなのと、コーディングのときも別のところからアクセスするのに.stateを書かない分、楽そうな気がします。

Count.ts

export default class Count {
    state = {
        count: 0,
        tick: 10
    };

    get count():number { return this.state.count }
    set count(count:number){ this.state.count = count }

    get tick():number { return this.state.tick }
    set tick(tick:number){ this.state.tick = tick }

}

AppView.vue + AppView.ts

子供のCounterコンポーネントにCountを渡しています。

AppView.vue

<template>
    <div class="app-container">
        <h1>カウンター</h1>
        <counter-view :count="store.count" />
    </div>
</template>

<style lang="scss" src="../../common/reset.scss" />
<style lang="scss" src="./AppView.scss" scoped />
<script lang="ts" src="./AppView.ts" />

AppView.ts

import Vue from 'vue';
import CounterView from "../counter/CounterView.vue";
import {Store} from "../../store/Store";

const AppView = Vue.extend({
    props:{
        store:{
            type:Store
        }
    },
    components:{
        CounterView
    },
    data(){
        return {

        }
    },
    methods:{
    }
});
export default AppView;

CounterView.vue + CounterView.ts

CounterView.vue

<template>
    <div class="counter">
        <p class="count-label">{{ state.count }}</p>
        <div class="input-area">
            <input type="button" :value="`-${state.tick}`" @click="onMinusButtonClick" />
            <input type="button" :value="`+${state.tick}`" @click="onPlusButtonClick" />
        </div>
    </div>
</template>
<style lang="scss" src="./CounterView.scss" scoped />
<script lang="ts" src="./CounterView.ts" />

dataにstateというプロパティを持たせて、propsから受け取ったcountのstateをセットします。

CounterView.ts

import Vue from 'vue';
import Count from "../../store/Count";

const CounterView = Vue.extend({
    props:{
        count:{
            type:Count
        }
    },
    data(){
        return {
            state: this.count.state
        }
    },
    methods:{
        onMinusButtonClick(){
            const newCount = this.count.count - this.count.tick;
            this.count.count = newCount;
        },
        onPlusButtonClick(){
            const newCount = this.count.count + this.count.tick;
            this.count.count = newCount;
        }
    }
});
export default CounterView;

});
export default Counter;

main.ts

エントリファイルです。storeをAppに渡します。

main.ts

import Vue from 'vue';
import AppView from "./components/app/AppView.vue"
import store from "./store/Store";

new Vue({
    el: '#app_container',
    render: h => h(AppView, {
        props: { store }
    }),
});

感想とソースコード

propsとかdataとかcomputedとか公式ドキュメント見ながら、いろいろと試してうまくいかなかったので、あきらめかけていたのですが、上のState Managementの公式ドキュメントでなんとかなりました。良かったです。

ソースコード一式
>> KinkumaDesign/vue-ts-counter

読みやすいソース(追記の余談)

クラス名は、それをみたときにModelなのかViewなのかControllerがすぐにわかる方が良いと思っています。
今までは次みたいに全部つけるパターンが好きだったんだけど

1. AbcData、AbcView、AbcContrller
-> Model、View、Controllerにそれぞれつける

xxxDataというのがいらないのかなと思い、

2. Abc、AbcView、AbcContrller
-> ViewとControllerだけにつけて、Modelには何もつけない

というのでいいのかなと思い始めた次第です。

クラス名やプロパティ名、メソッド名など、名前は作っている途中に気になるとすぐに変えてしまいます。(特に開発初期)何をしているものなのかを、すぐに把握できる、わかりやすい名前にしようと思っています。

これは、プログラムは書くことよりも、読むことの方が大変だからです。

年単位でメンテ、機能追加していくことが多いので、忘れた頃にソースを読み返すことになります。そのときに、ぱっと見てソースの流れが追えることが重要です。難しい書き方でワンライナーになるよりも、多少冗長でもわかりやすく書いた方が後から楽になります。(でもコピペの冗長さはダメです)

例えばメソッド内で一時的に定義する変数も、pとかnとか一文字にするんじゃなくて、どういうものなのかを名前でちゃんと表してあげた方がよいと思います。(for文のiとかは別)

あと、名前とは違うのですが、ファイルの行数もある程度長くなってきたら分割した方が後から読みやすくなります。私の場合だとだいたい500-600行を越えると分割しています。分割方法は階層構造を持たせたりして、機能グループごとにファイルに分割します。

なんでこんなことを書いているかっていうと、わかりやすいソースを書く人が増えた方が、そのソースが自分の手元にきたときに読みやすくなって嬉しいなっていうだけです(私欲)、、。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

LINEスタンプ作りました!
毎日使える。とぼけたウサギ。LINEスタンプ販売中! 毎日使える。とぼけたウサギ

ページトップへ戻る