Open4

Vue 動的コンポーネント切り替え

ナイトウナイトウ
App.vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { Ref, Component } from 'vue'
import NotePad from './NotePad.vue'
import Calculator from './Calculator.vue'
import Counter from './Counter.vue'

// コンポーネント配列を定義し、空でない配列を保証
const availableComponents: [Component, ...Component[]] = [NotePad, Calculator, Counter]

// 現在のインデックスを状態として管理
const currentIndex = ref(0)

// 現在のコンポーネントを取得
const currentComponent = computed(() => availableComponents[currentIndex.value])

// 前のコンポーネントに切り替える関数
const prevComponent = () => {
  if (currentIndex.value > 0) {
    currentIndex.value--
  } else {
    currentIndex.value = availableComponents.length - 1
  }
}

// 次のコンポーネントに切り替える関数
const nextComponent = () => {
  if (currentIndex.value < availableComponents.length - 1) {
    currentIndex.value++
  } else {
    currentIndex.value = 0
  }
}
</script>

<template>
  <div class="container">
    <div>
      <component :is="currentComponent">{{ currentIndex }}</component>
    </div>
    <div class="navigation">
      <button 
        @click="prevComponent"
        class="nav-button"
      >
        &lt;
      </button>
      {{ currentIndex }}
      <button 
        @click="nextComponent"
        class="nav-button"
      >
        &gt;
      </button>
    </div>
  </div>
</template>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.navigation {
  margin-top: 16px;
  display: flex;
  gap: 16px;
}

.nav-button {
  background-color: #ffde57;
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  border: none;
  transition: background-color 0.3s;
}

.nav-button:hover {
  background-color: #306998;
}

.nav-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

NotePad.vue
<script setup lang="ts"></script>

<template>
  <div>
    <slot />
    ほげほげ
  </div>
</template>

Calculator.vue
<script setup lang="ts"></script>

<template>
  <div>
    <slot />
    ふがふが
  </div>
</template>

Counter.vue
<script setup lang="ts"></script>

<template>
  <div>
    <slot />
    ぴよぴよ
  </div>
</template>

ナイトウナイトウ

モチベーションは不明だけど、コンポーザブルに切り出してみる

useComponentSwitcher.ts
// composables/useComponentSwitcher.ts
import { ref, computed } from 'vue'
import type { Ref, Component } from 'vue'

export function useComponentSwitcher(components: [Component, ...Component[]]) {
  const currentIndex = ref(0)

  // 現在のコンポーネントを取得
  const currentComponent = computed(() => components[currentIndex.value])

  // 前のコンポーネントに切り替える関数
  const prevComponent = () => {
    if (currentIndex.value > 0) {
      currentIndex.value--
    } else {
      currentIndex.value = components.length - 1
    }
  }

  // 次のコンポーネントに切り替える関数
  const nextComponent = () => {
    if (currentIndex.value < components.length - 1) {
      currentIndex.value++
    } else {
      currentIndex.value = 0
    }
  }

  // 現在のインデックスと切り替え関数を返す
  return {
    currentComponent,
    currentIndex,
    prevComponent,
    nextComponent,
  }
}

App.vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useComponentSwitcher } from "./useComponentSwitcher"
import type { Ref, Component } from "vue"
import NotePad from './NotePad.vue';
import Calculator from './Calculator.vue';
import Counter from './Counter.vue';

const availableComponents: [Component, ...Component[]] = [NotePad, Calculator, Counter];

const { currentComponent, currentIndex, prevComponent, nextComponent } = useComponentSwitcher(availableComponents)
</script>

<template>
  <div class="container">
    <div>
      <component :is="currentComponent">{{ currentIndex }}</component>
    </div>
    <div class="navigation">
      <button 
        @click="prevComponent"
        class="nav-button"
      >
        &lt;
      </button>
      {{ currentIndex }}
      <button 
        @click="nextComponent"
        class="nav-button"
      >
        &gt;
      </button>
    </div>
  </div>
</template>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
}



.navigation {
  margin-top: 16px;
  display: flex;
  gap: 16px;
}

.nav-button {
  background-color: #ffde57; /* Pythonの黄色をイメージ */
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  border: none;
  transition: background-color 0.3s;
}

.nav-button:hover {
  background-color: #306998; /* Pythonの青をイメージ */
}

.nav-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>
ナイトウナイトウ

ChatGPT に聞いたら頭よすぎ実装してくれた
全く理解できてない

const prevComponent = () => {
  currentIndex.value = (currentIndex.value - 1 + components.length) % components.length
}

const nextComponent = () => {
  currentIndex.value = (currentIndex.value + 1) % components.length
}