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で表計算するアプリを制作中です。

ボタンを押すと入力フォーム内の数値が増減し、その数値の合計を表示するものです。
これを行単位でコンポーネントを作成し、1行しかない表であれば問題無く動作します。

ただし、このコンポーネントを2行目、3行目・・・と使い回そうとすると、思ったとおりに動作しません。

発生している問題・エラー

現状は1行目のボタンを押すと、1行目の入力フォームだけではなく、2行目の入力フォームの数値も増減してしまう状態です。
これを行単位で動作するようにしたいです。
(1行目のボタンを押すと、1行目の入力フォームの数値だけが増減、2行目のボタンであれば、2行目の入力フォームの数値だけが増減するようにしたいです)

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

該当するソースコード

<!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-hit v-model="countGate1"></counter-hit>
                </th>
                <th>
                    <counter-hit v-model="countGate2"></counter-hit>
                </th>
                <th class="bg-light">
                    <counter-hit v-model="countGate3"></counter-hit>
                </th>
                <th>
                    <counter-hit v-model="countGate4"></counter-hit>
                </th>
                <th class="bg-light">
                    <!-- <counter-hit v-model="countGoalA"></counter-hit> -->
                    <counter-hit v-model="countGoal"></counter-hit>
                </th>
                <th>
                    <!-- <div class="sum">{{ sumA }}</div> -->
                    <div class="sum">{{ sum }}</div>
                </th>
                <th class="bg-light">
                    <div class="addition"></div>
                </th>
                <th>
                    <div class="total"></div>
                </th>
            </tr>
            <!-- 2行目 -->
            <tr>
                <th>B</th>
                <th class="bg-light">
                    <!-- <counter-hit v-model="countGate5"></counter-hit> -->
                    <counter-hit v-model="countGate1"></counter-hit>
                </th>
                <th>
                    <!-- <counter-hit v-model="countGate6"></counter-hit> -->
                    <counter-hit v-model="countGate2"></counter-hit>
                </th>
                <th class="bg-light">
                    <!-- <counter-hit v-model="countGate7"></counter-hit> -->
                    <counter-hit v-model="countGate3"></counter-hit>
                </th>
                <th>
                    <!-- <counter-hit v-model="countGate8"></counter-hit> -->
                    <counter-hit v-model="countGate4"></counter-hit>
                </th>
                <th class="bg-light">
                    <!-- <counter-hit v-model="countGoalB"></counter-hit> -->
                    <counter-hit v-model="countGoal"></counter-hit>
                </th>
                <th>
                    <!-- <div class="sum">{{ sumB }}</div> -->
                    <div class="sum">{{ 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>

const counterHit = {
    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,
            /* countGoalA: 0,

            countGate5: 0,
            countGate6: 0,
            countGate7: 0,
            countGate8: 0,
            countGoalB: 0, */
        };

    },
    computed: {
        sum() {
            return this.countGate1 + this.countGate2 + this.countGate3 + this.countGate4 + this.countGoal;
        },
        /* sumA() {
            return this.countGate1 + this.countGate2 + this.countGate3 + this.countGate4 + this.countGoalA;
        },
        sumB() {
            return this.countGate5 + this.countGate6 + this.countGate7 + this.countGate8 + this.countGoalB;
        }, */
    },
    components: {
        'counter-hit': counterHit,
    },
});

自分で試したこと

該当するソースコードの中のコメントアウトしてあるコードのように、v-modelの名前を1行目と2行目では別の名前にすると、思ったように動作しました。
しかし、これではコードが長くなるし、これから3行目、4行目・・・と行を増やしていくと、さらにコードが長くなります。
そのため、現実的な方法では無いのかなと感じています。

0

1Answer

まず、あなたがコメントアウトしたコード内で行った試行は正しいです。

なぜなら、各行に対して v-model でバインディングさせる変数を、
countGate1 1つにこだわったままでは本件は解決しないからです。

1行目で

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

と書き、2行目でも

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

と書いているのだから、同じ値(countGate1に入っている値)が画面に表示されるのは当然です。

ここで2行目のために、countGate1 の値を変更すると、1行目に表示済みの値にも即反映されてしまうのです。

1行目の表示は変化させたくないのだから、それはもう
「1行目と2行目とでは、バインディングさせる変数を異なる変数にする(2つ持つ)」という解決しかありません。

 

これではコードが長くなるし、これから3行目、4行目・・・と行を増やしていくと、さらにコードが長くなります。

それは「配列を使おう」と思い付けていないためです。

data

    data() {
        return {
            rows: [
                {
                    countGate1: 0,
                    countGate2: 0,
                    countGate3: 0,
                    countGate4: 0,
                    countGoal: 0,
                },
                    countGate1: 0,
                    countGate2: 0,
                    countGate3: 0,
                    countGate4: 0,
                    countGoal: 0,
                },
            ]
        };

のようにして、

        <tbody class="counter">
            <tr>

この <tr> を、v-for を使って rows の数だけ回せばスッキリ書けます。

rows のループ変数なので名前は row で良いでしょう。

v-for="row in rows"

そして、この row を使って

                    <counter-hit v-model="row.countGate1"></counter-hit>

と書けるように試行してみてください。

その結果、1行目の row.countGate1 と2行目の row.countGate1
「row.countGate1」という見かけは一緒ですが、実際には
rows 配列内の異なるインデックスに入っている値同士であり、
「1行目と2行目は、異なる変数(インスタンス)とバインディングしている」という意味になるので、意図通りに動作するはずです。

 
上記はあくまで考え方を示したサンプルであり、
実際の rows は上記のように data 内でガチガチに定義するのでなく、
とりあえず [] にしておいて、create() 内などで各行用の中身を
push() していったほうが良いです。

2Like

Comments

  1. @kama0244

    Questioner

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

    ははあ、なるほどー。
    v-forなどは基礎的な学習で学んでいたのですが、実際にこういった場面で用いるんですね。

    正直私には難易度高いように思いますが、回答内容に倣ってやってみます!
  2. @kama0244

    Questioner

    v-forを使って、意図通りに動作しました!
    HTMLはだいぶスッキリしました。
    ありがとうございます。

    ただし、rowsはdata内でガチガチに定義している状態です。
    これだと、行が増えるたびにどんどんコードが長くなっていきますよね。

    前回の回答にあった、
    「rows は上記のように data 内でガチガチに定義するのでなく、とりあえず [] にしておいて、create() 内などで各行用の中身をpush() していったほうが良いです」
    この方法ならdata内もスッキリしたコードになるのでしょうか?

Your answer might help someone💌