ウェブエンジニア珍道中

日々の技術的に関する経験を書いていきます。脱線もしますが助けになれば幸いです。

vuexっぽい仕組みをvuex使わずにRxjsで実装してみた

github.com

久しぶりの更新です。

今回はvuexを使わずに下の図を参考にして、それっぽい仕組みでマークダウンエディタを作ってみました。

f:id:te-nu:20171223185355p:plain

コンポーネントに対する通知等はrxjsを使って実装しています。

サンプル

テキストを入力すると、HTMLでプレビューを表示するだけのものです。

typescriptで書いてます。

登場人物

※ ソースは端折って貼ってます。

コンポーネント

ViewModelです。今回は単一ファイルコンポーネントを使ってます。

  • Root(根本のコンポーネント)
    • WriteArea(書く所)
    • ViewArea(見る所)

WriteArea

書くところです。入力イベントをObservable.fromEventで実装してますが、正直ここは@clickでも良かった気がします。

v-model="text"とすることでthis.text="aaaaa"と、ブラウザから入力する以外にも値を入れることができるようにしています。(編集機能を作る時に必要)

<template lang="pug">
    textarea(ref="inputArea" v-model="text")
</template>

<script lang="ts">
import Vue from 'vue';
import {Observable} from 'rxjs/Rx';
import {Component, Prop} from "vue-property-decorator"
import {IndexStore} from "../stores/indexStore"

@Component({})
export default class WriteArea extends Vue {
    @Prop()
    private store_: IndexStore;

    private text: string = "";

    mounted(){
        Observable.fromEvent(<HTMLElement>this.$refs.inputArea, 'input').subscribe((event) => {
            this.store_.inputText(event['target'].value);
        });
        this.store_.onChangedText.subscribe((text) => {
            this.text = text;
        });
    }
}
</script>

ViewArea

<template lang="pug">
    div(v-html="text")
</template>

<script lang="ts">
import Vue from 'vue';
import {Observable} from 'rxjs/Rx';
import {Component, Prop} from "vue-property-decorator"
import {IndexStore} from "../stores/indexStore"
import marked from "marked"

@Component({})
export default class ViewArea extends Vue {
    @Prop()
    private store_: IndexStore;

    private text: string = "";

    created(){
        this.store_.onChangedText.map((text) => {
            return marked(text)  // 送られてきたtextをhtmlに変換している
        }).subscribe((text) => {
            this.text = text;
        });
    }
}
</script>

store

vuexの働きをする人です、ユーザアクションを受け取ってデータを保存、各コンポーネントに通知しています。

WriteAreaとViewAreaはこのクラスのインスタンスであるtextの変更に反応して見た目を変えるようにRxjsで組んでいます。

  • IndexStore
export class Store {

    // mutation
    private changeTextSubject_: BehaviorSubject<string> = new BehaviorSubject("");

    // state
    private text: string;
    
    constructor(text: string = "") {
        this.mutateText(text);
    }

    // actions
    public inputText(value): void {
        // APIを叩く処理はここに入れる(今は無い)
        this.mutateText(value); // commit
    }

    // mutation
    public mutateText(value): void {
        this.text = value; // mutate
        this.changeTextSubject_.next(this.text); // render
    }

    public get onChangedText(): Observable<string> {
        return this.changeTextSubject_.asObservable();
    }
}

constructorでmutateしているのは初期データがあった際に対応するためです。(今はないけど編集機能を作る際に必要)

storeのインスタンスを作る時にnew IndexStore("hoge")とすることで各コンポーネントにhogeが入った状態が作られます(BehaviorSubjectを使っているのもこれが理由ですがここでは説明は省略)

まとめ

データはWriteAreaで入力された値がStoreに送られてその値が保存され、各コンポーネントに保存された値が送られるといった流れで回っていきます。値の表示の仕方は各コンポーネントに任せれば良いし、サーバへの保存の処理にはStoreに保存された値を使えば良いので拡張はそこそこしやすそうかなと思います。

ってことで状態の管理をVuexを使わずにRxjsを使った独自のクラスで実装してみました。とりあえず今はいい感じに動いていますが、また改善点等あれば書きたいと思います。

勉強はしましたがvuex自体は使ったことがまだ無いため「全然構造ちげーよ」とかあれば指摘していただければ幸いですmm