コンテンツへスキップ

【Vue.js】Vuetify v.3.xでv-stepperがなくなってたのでステッパーコンポーネントを自作する

  • 未分類

入力フォームなどのユーザビリティ向上のため、入力ステップを可視化する「ステッパー」というものがあります。

こんなやつです。

Vue.jsのデザインフレームワークの一つであるVuetifyでは、バージョン2.xまで「v-stepper」というコンポーネントが存在したのですが、バージョン3.xからはなくなっていたため今回は簡単なステッパーを自作したいと思います。

用意するコンポーネント

  • 親コンポーネント・・・Stepperコンポーネントを呼び出すコンポーネント
  • Stepperコンポーネント・・・親コンポーネントに呼び出されるステッパー表示用のコンポーネント

今回はステッパーの実装方法だけをまとめていきたいので、用意するコンポーネントは親コンポーネントとStepperコンポーネントの2つだけにしておきましょう。

コンポーネントの内容

親コンポーネント

<template>
  <v-container
    class="h-100"
    fluid
  >
    <v-row
      v-if="!myData"
      class="h-100"
      align="center"
      justify="center"
    >
      <v-col
        cols="12"
        md="5"
      >
        <v-card>
          <v-card-title
            class="bg-primary text-h6"
          >登録</v-card-title>
          
          <!-- ステッパー呼び出し -->
          <Stepper :currentStep="currentStep" :stepNumber="stepNumber"></Stepper>

          <v-card-actions>
            <v-btn
              v-if="currentStep == 1"
            >
              クリア
            </v-btn>

            <v-btn
              v-if="currentStep > 1"
              @click="toPrevStep"
            >
              戻る
            </v-btn>
            <v-spacer></v-spacer>
            <v-btn
              @click="toNextStep"
            >
              次へ
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
// コンポーネント
import Stepper from '@/components/molecules/Stepper'

export default {
  name: 'mypage-page',
  components: {
    Stepper: Stepper
  },
  data() {
    return {
      myData: null,
      currentStep: 1,
      stepNumber: 3
    }
  },

  /* methods */
  methods: {
    // toPrevStep
    toPrevStep() {
      if(this.currentStep > 1) {
        this.currentStep--
      }
    },

    // toNextStep
    toNextStep() {
      if(this.currentStep < this.stepNumber) {
        this.currentStep++
      }
    }
  }
}
</script>

親コンポーネントでは、currentStepという変数(現在のステップ数)とstepNumberという変数(最大ステップ数)をStepperコンポーネントに渡しています。

ステップの進退については、methodsで定義しているtoPrevStepメソッドとtoNextStepメソッドにてコントロールしていきます。

Stepperコンポーネント

<template>
  <v-row
    class="pa-5"
    justify="center"
  >
    <v-col
      v-for="step in stepNumber" :key="step"
      class="step-number-wrapper text-center"
      cols="2"
    >
      <v-chip
        class="step-number"
        :class="{ 'bg-primary font-weight-bold': step == currentStep }"
        rounded
      >{{ step }}</v-chip>
    </v-col>
  </v-row>
</template>

<script>
export default {
  name: 'molecules-stepper',
  props: {
    stepNumber: Number,
    currentStep: Number
  }
}
</script>

<style scope lang="scss">
.step-number-wrapper {
  .step-number {
    border-radius:100%;
    position:relative;
    overflow:visible;

    &:after {
      content: "・・・";
      font-weight:bold;
      position:absolute;
      left:140%;
      color:#aaa;
    }
  }

  &:nth-last-of-type(1) {
    .step-number {
      &:after {
        content:"";
      }
    }
  }
}
</style>

Stepperコンポーネントでは、propsで親コンポーネントから渡された2つの変数(currentStepとstepNumber)を受け取り、stepNumberの回数分だけ.step-number-wrapperをループ出力しています。

現在のステップに該当する.step-numberには、動的にstyleを充てて背景色を付けるようにしています。

出来上がったステッパーがこちら

こうして出来上がったステッパーがこれです。

「次へ」ボタンを押して入力ステップを進めていくと、ステッパーの色付きナンバーが変わっていきます。

Vuetify v.2.xにあったv-stepperのように縦横スタイルに切り替えが柔軟にできるわけではないですが、シンプルなステッパーの実装としては十分かなと思ってます。

注意点:Vue.jsのpropsの型宣言は頼れない

正しい書き方

export default {
  name: 'molecules-stepper',
  props: {
    stepNumber: Number,
    currentStep: Number
  }
}

Stepperコンポーネントでは、親コンポーネントからpropsで受け取る変数を数値型で受け取りたいため型宣言をしています。

  data() {
    return {
      myData: null,
      currentStep: 1,
      stepNumber: 3
    }
  },

親コンポーネントで各変数の値を定義する際も数値型で定義しています。
これは正しい状態で、Stepperコンポーネントは想定通りの動きをしてくれます。

間違った書き方

  data() {
    return {
      myData: null,
      currentStep: "1",
      stepNumber: "3"
    }
  },

ところが親コンポーネントでの定義の際、値をクォーテーションで囲んで定義すると変数の方は文字列型になります。

すると今回作成したステッパーは、表示の際にループ表示してくれません。

mounted() {
  console.log(typeof this.stepNumber)
}

現状確認のため、Stepperコンポーネント側でstepNumberの型を確認してみます。

Number型で型宣言をしていましたが、String型として値を持っていることを確認しました。

つまり、子コンポーネントでpropsの変数に型宣言をしても親コンポーネントから渡された型のまま変数が利用されます。

一応、デベロッパーツールのコンソール上では、宣言した型と渡された型が違うことでのメッセージは表示されますが、それがエラーとして扱われることはないので気付きにくいかもしれません。

propsで値を受け渡しする際は、コンポーネント間での型違いによる不都合が生じないよう注意して取り扱いましょう。