kama0244
@kama0244 (カマ ちゃん)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【初心者です】Vue.js 複数の入力フォームの数値を合計表示したい

Q&A

Closed

解決したいこと

Vue.jsで点数計算するアプリを制作中です。

ボタンを押すと、入力フォーム内の数値が増減する機能を実装出来ました。

現在は、複数の入力フォームに入力された数値を合計表示する機能を実装中です。
(打数の合計を計算して、総打数の欄に表示させたい)
しかし、いろいろと試してみたのですが、まったく実装することが出来ません。
どのようにすれば、数値の合計機能を実装出来るか、教えていただきたいです。

※ソースコードにはCSSが適用されていないので表示が崩れています。

該当するソースコード

<!-- HTML -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">

    <!-- Vue.js(開発用) -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <table class="table table-bordered">
        <!-- 見出し行 -->
        <thead>
            <tr>
                <th class="col-sm"></th>
                <th class="col-lg bg-light">ゲート①</th>
                <th class="col-lg">ゲート②</th>
                <th class="col-lg bg-light">ゲート③</th>
                <th class="col-lg">ゲート④</th>
                <th class="col-lg bg-light">ゴール</th>
                <th class="col-md">総打数</th>
                <th class="col-md bg-light">加算点</th>
                <th class="col-md">Total</th>
            </tr>
        </thead>
        <!-- 1行目 -->
        <tbody class="counter">
            <tr>
                <th>A</th>
                <th class="bg-light">
                    <counter></counter>
                </th>
                <th>
                    <counter></counter>
                </th>
                <th class="bg-light">
                    <counter></counter>
                </th>
                <th>
                    <counter></counter>
                </th>
                <th class="bg-light">
                    <counter></counter>
                </th>
                <th>
                    <div class="sum"></div>
                </th>
                <th class="bg-light">
                    <div class="addition"></div>
                </th>
                <th>
                    <div class="total"></div>
                </th>
            </tr>
        </tbody>
    </table>

    <!-- Test.js -->
    <script src="test.js"></script>
</body>
</html>

// JavaScript
const counter = {
    data() {
        return { 
            count: 0,
        };
    },
  methods: {
        countUp() {
            if ( this.count < 0 ) {
                this.count = 0;
            } else {
                this.count += 1;
            }
        },
        countDown() {
            if ( this.count <= 0 ) {
                this.count = 0;
            } else {
                this.count -= 1;
            }
        },
    },
    template: `
        <form class="ml-2 mr-2">
            打数
            <div class="form-group input-group">
                <div class="input-group-prepend">
                    <button type="button" @click="countUp" class="btn btn-primary">+</button>
                </div>
                <input type="number" v-model="count" min="0" disabled class="form-control">
                <div class="input-group-append">
                    <button type="button" @click="countDown" class="btn btn-secondary">-</button>
                </div>
            </div>
        </form>
    `,
};

new Vue({
    el: '.counter',
    components: {
        'counter': counter,
    },
});

自分で試したこと

コンポーネント内のinput要素にv-model="count"としているので、countという名前でフォームに入力された数値のデータを取得出来ると思ったですが、出来ませんでした。

Vue.jsの公式リファレンスでは、コンポーネント内でv-modelを使用するときは、propsや:valueや@inputを使用するとあったので、そのとおりにやってみたけどダメでした。

0

2Answer

まず最初に動くコードを載せます。

HTML(変更部分のみ)

<tbody class="counter">
    <tr>
        <th>A</th>
        <th class="bg-light">
            <counter v-model="countGate1"></counter>
        </th>
        <th>
            <counter v-model="countGate2"></counter>
        </th>
        <th class="bg-light">
            <counter v-model="countGate3"></counter>
        </th>
        <th>
            <counter v-model="countGate4"></counter>
        </th>
        <th class="bg-light">
            <counter v-model="countGoal"></counter>
        </th>
        <th>
            <div class="sum"></div>
        </th>
        <th class="bg-light">
            <div class="addition"></div>
        </th>
        <th>
            <div class="total">{{ total }}</div>
        </th>
    </tr>
</tbody>
// JavaScript
const counter = {
    props: ['value'],
    methods: {
        countUp() {
            this.$emit('input', this.value + 1);
        },
        countDown() {
            if ( this.value > 0 ) {
                this.$emit('input', this.value - 1);
            }
        },
    },
    template: `
        <form class="ml-2 mr-2">
            打数
            <div class="form-group input-group">
                <div class="input-group-prepend">
                    <button type="button" @click="countUp" class="btn btn-primary">+</button>
                </div>
                <input type="number" v-model="value" min="0" disabled class="form-control">
                <div class="input-group-append">
                    <button type="button" @click="countDown" class="btn btn-secondary">-</button>
                </div>
            </div>
        </form>
    `,
};

new Vue({
    el: '.counter',
    data() {
        return {
            countGate1: 0,
            countGate2: 0,
            countGate3: 0,
            countGate4: 0,
            countGoal: 0
        }
    },
    computed: {
        total() {
            return this.countGate1 + this.countGate2 + this.countGate3 + this.countGate4 + this.countGoal;
        }
    },
    components: {
        'counter': counter,
    },
});

Vueでは今回の例のように「子コンポーネントと親コンポーネントの間での値のやり取り」で躓くことがよくあります。
(今回の例では new Vue({...}) と書いてるところが親コンポーネント、 counter が子コンポーネントになります)

「Vue 親から子」や「Vue 子から親」などで検索して勉強すると理解できると思いますので、要点だけ書いておきます。


親コンポーネントの値を変えるときは子コンポーネントに v-model="XXX" という形でdataを渡すと便利

<counter v-model="countGate1"></counter>

<counter v-bind:value="countGate1" v-on:input="countGate1 = $event"></counter>
と同じことをやってくれます。
参考:Vue.js | コンポーネントで v-model を使う

こういった書き方は動作の隠蔽をしているとも言えるので理解の妨げになるのですが、Vueではよくある書き方なのであえてこれで書かせてもらいました。


Vueでは親のデータを子に渡して表示するという形が一般的

なぜ親のdataにcountXXXを全部書いているかというと、Vueは基本的に「データは親が持つもの、子は親から渡されたデータを表示するもの」という設計になっているからです。
一応子のdataを親で取得することもできなくはないですが、大変泥臭い書き方になってしまいます。


子コンポーネント内で親のデータを変更してはいけないので $emit を使ってデータを親の手で変更してもらう

Vueでは親が持つデータを子が直接書き換えることはできません。そこでイベントを使ってどうにか親の手でデータを変更してもらいます。

今回で言うと
countUp/Down内の $emit で子コンポーネントのinputイベントを呼び出す

親が子のinputイベントを監視していて(v-on:input)、発生したら countGate1 = $event する

親のcountGate1が更新されたら子コンポーネントが自動で再描画されるので、表示値が更新される
という流れになっています。


ちなみにもし親子関係のネストがどんどん深くなって、この親子間での値のやり取りのルールが面倒になってきたら「Vuex」の出番です。

2Like

Comments

  1. @kama0244

    Questioner

    うおおお!
    実装出来た!

    propsや$emitは公式リファレンスにありましたが、自分でやってみても上手くいかず・・・。
    かれこれ1週間くらい悩んで挫折しそうでした。

    コードの解説も分かりやすいです。
    実際に動くコードを元にした解説なので、私でも理解することが出来ました。

    本当にありがとうございます。
  2. 解決の糸口が見えないと心が折れそうになりますよね……

    今回の質問は
    ・やりたいことがはっきりと分かる
    ・そのままで動くソースコードを提示してくれている
    という点でこちらとしても回答を作りやすかったです。

    これからも頑張ってください!

作成したコンポーネントにv-modelを設定し、
そのコンポーネントの中から、値の変更があった際に
本体のvueへ送る仕組みになれば良いと思います。

// 雑に書くと、
<counter></counter>
// これが、こんな感じ(インデックス指定しなくちゃいけないかもですが)
<counter v-model="count" />

// コンポーネント内の値の変更があった際に親に返す
// このコンポーネント自体をinputタグのように動かす
this.$emit('input', this.count);

あとは、それぞれのコンポーネントが返す値を集計する感じで動くと思います。

0Like

Comments

  1. @kama0244

    Questioner

    回答ありがとうございました。

Your answer might help someone💌