331
304

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue.jsってどうやったら綺麗に使えるのよ

Last updated at Posted at 2017-09-25

前置き

・LaravelついでにVue.jsを使うことになった
・公式のドキュメントを流し読み
・とりあえずコンポーネントで分けてみるか
・カオスwwww

というわけで、根本的にどうVue.jsを使っていいか分かっていないようなので、
どうすれば「俺はVue.jsを使ってるぜっ!」
と言えるのか試行錯誤してみる。

事の始まり

簡単な検索ページでも作ってみる

スクリーンショット 2017-09-22 14.56.08.png
<div>
    id<input type="text">
    name<input type="text">
</div>
<div>
    <input type="button" value="検索">
</div>
<br />
<div>
    <table border="1">
        <tr>
            <th>id</th>
            <th>name</th>
        </tr>
        <tr>
            <td>1</td>
            <td>たかし</td>
        </tr>
    </table>
</div>

どうコンポーネント分けようかな

こ・・こんな感じ?

スクリーンショット 2017-09-22 15.02.07.png

とりあえずツリー風に書いて、役割を明確に・・

スクリーンショット 2017-09-22 15.16.36.png

▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ

クールになるんだ

OK落ち着け俺。
何か根本的に間違っている。

情報を整理してリトライしよう。

Vue.jsはMVVM

(M)odel

データだったり、ロジックだったり。

Vue.jsでいうと、dataオブジェクトやmethodsのこと。

props、computed、watchもModelに入るのかな?

(V)iew

DOM要素のこと。

Vue.jsでいうと、elのこと。

templateやrenderもそうなのかな?

(V)iew(M)odel

ModelとViewの仲立ちをもの。

Vue.jsでいうと、Vueインスタンスのこと。

考察

Vueインスタンスにdataオブジェクトを内包できるから勘違いしたような気がする。
→明確にViewModelとModelを分けてみよう。

ViewModelであるVueインスタンスと、Modelは切り離すべきではなかろうか( ̄ヘ ̄)ウーン
→用意するModelは、入力と結果の二つってところか。

検索ロジックってどっちに入れよう・・。
入力値も使うし、結果も格納しないといけないんだよな・・。
→ロジック用のModelを用意してみる。

データ用のModelにはSetterGetterを用意すればいいな。
んで、ロジックModelで入力と結果のModelに入れ込めばいいんだ。

スクリーンショット 2017-09-22 16.00.54.png

こんなところか。

Viewに関しては、コンポーネントの分け方の問題なのかなぁ・・。

コンポーネント

ぼくのかんがえるこんぽーねんと

なんか・・部品・・ですよね・・。

はい、この曖昧さが悲劇を生んだような気がします。

公式より

基本的な HTML 要素を拡張して再利用可能なコードのカプセル化を助けます。
高いレベルでは、コンポーネントは Vue.js のコンパイラが指定された振舞いを加えるカスタム要素です。
場合によっては、特別な is 属性で拡張されたネイティブな HTML 要素の姿をとることもあります。

使い回しが聞きやすいように、独自タグ作るやーつ。
と読める。

使い回しが効きそうなところはコンポーネントにしましょう。
サイトのデザインって統一感持たせたいんだから、
言ってしまえば全部使い回しが効くように作るべきだよね。

じゃあ
「使い回しが効く部品」
で考えることにしよう。

コンポーネント再検討

スクリーンショット 2017-09-22 16.41.51.png

・画面全体のテンプレート
・入力部(ラベル+テキストボックス)
・ボタン
・テーブル

に分けてみました。

全体像を再検討

スクリーンショット 2017-09-22 16.52.51.png

コンポーネントとモデルを繋げる線がViewModel(Vueインスタンス)になるのかな・・?

作ってみる

入力モデル

model_input.js
require('./bootstrap');

window.Vue = require('vue');

module.exports = new Vue({
    data: {
        id: '',
        name: ''
    },
    methods: {
        getId: function() {
            return this.id;
        },
        setId: function(val) {
            this.id = val;
        },
        getName: function() {
            return this.name;
        },
        setName: function(val) {
            this.name = val;
        }
    },
    setMethods: function() {
        return this.methods;
    }
});

解説)
特にありません。
再検討したものをそのまま実装してます。

検索結果格納用モデル

model_result.js
require('./bootstrap');

window.Vue = require('vue');

module.exports = new Vue({
    data: {
        list: []
    },
    methods: {
        getList: function() {
            return this.list;
        },
        setList: function(val) {
            this.list = val;
        }
    },
    setMethods: function() {
        return this.methods;
    }
});

解説)
特にありません。
再検討の時と違うのは、結果は配列で持たせるように変更してます。

ロジック用モデル

logic_search.js
// 不要としました

解説)
検索ロジックはViewModelに依存させました。

なぜか?
多くの値(Model等)を用いたり、返したりするロジックを外出しすると、
ViewやViewModelの記載が複雑になりました。

一意性の高いロジックは外出しせずに、都度その場で定義した方が良さそうです。

複雑になった例
<!-- モデルをコンポーネントに渡さなければならないため、記載や可読性が複雑になる -->
<my-button label="検索" v-on:prop-click="search(modelInput, modelResult)"></my-button>

入力部コンポーネント

text.vue
<template>
    <span>
        {{label}}<input type="text" v-model="value">
    </span>
</template>

<script>
    export default {
        props: ["label"],
        data: function() {
            return {value: ""};
        },
        watch: {
            value: function(val, oldval) {
                this.$emit("prop-setter", val);
            }
        }
    }
</script>

解説)
入力値が変わるたびに、親から受けとったModelのsetterを実行。

ボタンコンポーネント

button.vue
<template>
    <input type="button" v-bind:value="label" v-on:click="click()">
</template>

<script>
    export default {
        props: [ "label" ],
        methods: {
            click: function() {
                this.$emit("prop-click");
            }
        }
    }
</script>

解説)
ボタンを押したら、受け取ったメソッドを実行するだけ。

テーブルコンポーネント

table.vue
<template>
    <div>
        <table border="1">
            <tr>
                <th v-for="header in propHeaders">{{header}}</th>
            </tr>
            <tr v-for="data in propDatas">
                <td>{{data[0]}}</td>
                <td>{{data[1]}}</td>
            </tr>
        </table>
    </div>
</template>

<script>
    export default {
        props: [ "prop-headers", "prop-datas" ]
    }
</script>

解説)
受け取ったヘッダと値を使ってテーブル表示。

(app.js)ViewModelの定義等

app.js
require('./bootstrap');

window.Vue = require('vue');

import ModelInput from './model_input.js';
import ModelResult from './model_result.js';
//import LogicSearch from './logic_search.js';

Vue.component('my-text', require('./components/text.vue'));
Vue.component('my-button', require('./components/button.vue'));
Vue.component('my-table', require('./components/table.vue'));

const input = new Vue({
    el: '#input',
    data: ModelInput.$data,
    methods: ModelInput.$options.setMethods()
});

const button = new Vue({
    el: '#button',
    data: {
        modelInput: ModelInput,
        modelResult: ModelResult
    },
    methods: {
        search: function() {
            var result = [];

            result.push([this.modelInput.getId(), this.modelInput.getName()]);
            result.push([2, "たかし"]);
            
            this.modelResult.setList(result);
        }
    }
});

const table = new Vue({
    el: '#table',
    data: ModelResult.$data
});

解説)
各コンポーネントがモデルを使用できるように、VueオブジェクトとModel紐づけてます。

ビュー

<div id="input">
    <my-text label="id" v-on:prop-setter="setId"></my-text>
    <my-text label="name" v-on:prop-setter="setName"></my-text>
</div>
<div id="button">
    <my-button label="検索" v-on:prop-click="search"></my-button>
</div>
<br />
<div id="table">
    <my-table v-bind:prop-headers="['id', 'name']" v-bind:prop-datas="list"></my-table>
</div>

解説)
コンポーネントを使って描画。

まとめ

・最初に比べるとだいぶマシになった。
・誰か参考になる情報やリンクを教えてください。

331
304
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
331
304

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?