Search code examples
vue.jscomponentsvuejs3emit

I want to use emit to switch between page components


I have a Header and a Main page on Top page now, and I have three Tab functions in the Header component. I want to switch displays(A, B, C) in the Main page when I press each Tab (A, B, C). I am trying to change it using Emit but it doesn't work. If you could give me some advice, I really appreciate it.

Header.vue

<script setup >
const tabs = [
  {name: 'A', comp: A},
  {name: 'B', comp: B},
  {name: 'C', comp: C}
];

interface Emits {
  (e: "clicked", value: any);
}

const clicked = defineEmits<Emits>();
</script>

<template>
<div class="tabs" >
        <button v-bind:class="['tab-button', A]" @click="$emit('clickedA',tabs[0])">A</button>
        <button v-bind:class="['tab-button', B]" @click="$emit('clickedB',tabs[1])">B</button>
        <button v-bind:class="['tab-button', C]" @click="$emit('clickedC',tabs[2])">C</button>
</div>
</template>

Mainpage.vue


<script>
let currentTab = A

function getValue(event) {
    currentTab = event
}


</ script>

<template>
  <Header />
  <div>
    <component :is="currentTab" @clicked="getValue"></component> 
  </div>
</template>

Solution

  • I would have the 3 buttons all call the same emit method, passing in the component String as a parameter of that emit:

    <button v-bind:class="['tab-button']" 
        @click="$emit('myClicked', tabs[0])">
        A
    </button>
    <button v-bind:class="['tab-button']" 
        @click="$emit('myClicked', tabs[1])">
        B
    </button>
    <button v-bind:class="['tab-button']" 
        @click="$emit('myClicked', tabs[2])">
        C
    </button>
    

    Then, in the parent component, you could listen for that emit and that one alone:

    <Header @myClicked="getValue" />
    

    and in script:

    function getValue(tab) {
      currentTab.value = tab.name;
    }
    

    For example:

    Mainpage.vue

    <template>
      <div>
        <Header @myClicked="getValue" />
        <div>
          <component :is="currentTab"></component>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref } from "vue";
    import Header from "./Header.vue";
    
    let currentTab = ref("A");
    
    function getValue(tab) {
      currentTab.value = tab.name;
    }
    
    function getTab(tab) {
      currentTab.value = tab.name;
    }
    </script>
    

    Header.vue

    <script setup>
    const tabs = [{ name: "A" }, { name: "B" }, { name: "C" }];
    
    const emit = defineEmits(["myClicked"]);
    </script>
    
    <template>
      <div class="tabs">
        <button v-bind:class="['tab-button']" 
            @click="$emit('myClicked', tabs[0])">
          A
        </button>
        <button v-bind:class="['tab-button']" 
            @click="$emit('myClicked', tabs[1])">
          B
        </button>
        <button v-bind:class="['tab-button']" 
            @click="$emit('myClicked', tabs[2])">
          C
        </button>
      </div>
    </template>
    

    A, B, and C.vue:

    <template>
      <h2>Component A</h2>
    </template>
    
    <template>
      <h2>Component B</h2>
    </template>
    
    <template>
      <h2>Component C</h2>
    </template>
    

    main.js

    import { createApp } from 'vue'
    import App from './App.vue'
    import './index.css'
    
    import A from "./components/A.vue";
    import B from "./components/B.vue";
    import C from "./components/C.vue";
    
    const app = createApp(App);
    app.component("A", A);
    app.component("B", B);
    app.component("C", C);
    
    app.mount('#app')
    

    OCD cleaning up code a little more.
    Using v-for in the Header.vue file to simplify the creation of buttons, and passing the name field into the emit's method parameter changes the following vue files:

    Header.vue

    <script setup>
    const tabs = [
      // assuming that in "real" code, 
      // the display name and component name values would likely be different
      { name: "A", compName: "A" },
      { name: "B", compName: "B" },
      { name: "C", compName: "C" },
    ];
    
    const emit = defineEmits(["myClicked"]);
    </script>
    
    <template>
      <div class="tabs">
        <button
          v-for="tab in tabs"
          :key="tab.name"
          @click="$emit('myClicked', tab.compName)"
        >
          {{ tab.name }}
        </button>
      </div>
    </template>
    

    Mainpage.vue

    <template>
      <div>
        <Header @myClicked="getValue" />
        <div>
          <component :is="currentTab"></component>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref } from "vue";
    import Header from "./Header.vue";
    
    let currentTab = ref("A");
    
    function getValue(tabName) {
      currentTab.value = tabName;
    }
    
    function getTab(tab) {
      currentTab.value = tab.name;
    }
    </script>