VUE3 父子组件传参、事件传递

最近由于刚接触使用VUE3开发需求,涉及到VUE3父子组件传参、事件传递情景,在开发中做一些积累。

应用场景

需要实现点击顶部栏的菜单按钮时,实现左侧菜单展开/关闭。
侧边菜单展开

侧边菜单收起
很显然,页面分为顶部栏、左侧菜单栏、内容这三大部分,实现此需求需要跨组件传参。
页面布局如下:

<template>
  <div id="layout-container">
    <!-- 左侧菜单 -->
    <LeftMenu />
    <div id="layout-right">
      <!-- 顶部导航栏 -->
      <TopBar/>
      <div>
        <router-view />
      </div>
    </div>
  </div>
</template>

思路:父组件作为枢纽,掌控数据的状态和操作,父组件将展开状态同步到顶部栏、左侧菜单栏,顶部栏icon点击时触发事件,将要修改成的状态传递给父组件。

枢纽-父组件

父组件向子组件传参

核心在于父组件: 绑定数据到自组件子组件props接收

1、父组件 -> 左侧菜单栏

父组件到左侧菜单栏
父组件代码:

<template>
  <div>
    <!-- 左侧菜单 绑定数据 -->
    <LeftMenu :isCollapse="isCollapse" />
  </div>
</template>

左侧菜单栏代码:

<template>
  <div>
    <el-menu default-active="1" :collapse="isCollapse">
      <el-menu-item v-for="menu in  menuList" :key="menu.id" :index="menu.id">
        <el-icon>
          <component :is="menu.icon" />
        </el-icon>
        <div v-if="!isCollapse" class="menu-title"> {{ menu.title }}</div>
      </el-menu-item>
    </el-menu>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'LeftMenu',
  // 接收参数
  props: {
    isCollapse: {
      type: Boolean
    }
  },
  setup() {
    const menuList = [
      { id: '1', icon: 'Connection', title: 'Connection' },
      { id: '2', icon: 'Iphone', title: 'Connection' }
    ]
  
    return {
      menuList
    }
  },

})
</script>

2、父组件 -> 顶部栏

父组件到顶部栏
父组件代码:

<template>
  <div>
    <!-- 顶部导航栏 绑定数据 -->
      <TopBar :isCollapse="isCollapse" />
  </div>
</template>

顶部栏代码:

<template>
  <div>
    <el-button link>
      <el-icon>
        <Fold v-if="isCollapse" />
        <Expand v-else />
      </el-icon>
    </el-button>
    <span></span>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: "TopBar",
  // 接收数据
  props: {
    isCollapse: {
      type: Boolean
    }
  }
})
</script>

子组件触发父组件事件

子组件向父组件传递参数主要通过事件上传时的参数实现,emit

顶部栏 -> 父组件

顶部栏到父组件
父组件代码:

<template>
  <div>
    <div id="layout-right">
      <!-- 顶部导航栏 绑定对应事件 -->
      <TopBar @changeCollapse="changeCollapse" />
    </div>
  </div>
</template>
<script lang="ts" >
import { defineComponent, ref } from 'vue'
import TopBar from '@/components/TopBar.vue';
export default defineComponent({
  name: "Layout",
  components: {
    TopBar
  },
  setup() {
    const isCollapse = ref(false)
    // 事件
    const changeCollapse = (val) => {
      isCollapse.value = !isCollapse.value
    }
    return {
      isCollapse,
      changeCollapse
    }
  }
})
</script>

顶部栏代码:

<template>
  <div>
    <el-button @click="changeCollapse" link >
      展开/关闭
    </el-button>
    <span>顶部导航栏</span>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: "TopBar",
  emits: ["changeCollapse"],
  setup(props, context) {
    const changeCollapse = () => {
    	//触发父组件事件 
      context.emit("changeCollapse")
    }
    return {
      changeCollapse
    }
  }
})
</script>

综合

最终,结合这几个点,就能实现此需求了。
完整代码如下,注意此项目配置了i18n多语言,具体可参考 本博主VUE2多语言使用,不过本篇使用的是VUE3与多语言结合,可能会有部分出入。
父组件:

<template>
  <div id="layout-container">
    <!-- 左侧菜单 -->
    <LeftMenu :isCollapse="isCollapse" />
    <div id="layout-right">
      <!-- 顶部导航栏 -->
      <TopBar :isCollapse="isCollapse" @changeCollapse="changeCollapse" />
      <div class="padding20">
        <router-view />

      </div>
    </div>
  </div>
</template>
<script lang="ts" >
import { defineComponent, ref } from 'vue'
import LeftMenu from '@/components/LeftMenu.vue';
import TopBar from '@/components/TopBar.vue';
export default defineComponent({
  name: "Layout",
  components: {
    LeftMenu,
    TopBar
  },
  setup() {
    const isCollapse = ref(false)
    const changeCollapse = () => {
      isCollapse.value = !isCollapse.value
    }
    return {
      isCollapse,
      changeCollapse
    }
  }
})
</script>
<style lang="scss" scoped>
#layout-container {
  display: flex;

  #layout-right {
    width: 100%;
  }
}
</style>

左侧菜单:

<template>
  <div id="menu-box">
    <el-menu :default-active="currentActiveIndex" class="el-menu-vertical-demo" :collapse="isCollapse"
      @select="handleSelect">
      <el-menu-item v-for="menu in  menuList" :key="menu.id" :index="menu.id">
        <el-icon>
          <component :is="menu.icon" />
        </el-icon>
        <div v-if="!isCollapse" class="menu-title"> {{ menu.title }}</div>
      </el-menu-item>
    </el-menu>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n' // 多语言
import router from '@/router/index'
import { Menu } from "@/config/interface"


export default defineComponent({
  name: 'LeftMenu',
  props: {
    isCollapse: {
      type: Boolean
    }
  },
  setup() {
    const { t } = useI18n()
    const menuList = [
      { id: '1', icon: 'Connection', title: t('tenant.label1'), path: '/tenant' },
      { id: '2', icon: 'Iphone', title: t('android.label1'), path: '/android' },
      { id: '3', icon: 'Iphone', title: t('androidManage.label1'), path: '/androidManage' },
      { id: '4', icon: 'Iphone', title: t('orderManage.label1'), path: '/orderManage' },

    ]
    // 高亮同步路由
    // const currentActiveIndex = ref((menuList.find((menu) => menu.path === window.location.pathname) as Menu).id);
    const currentActiveIndex = ref((menuList.find((menu) =>  ('#' + menu.path) === window.location.hash) as Menu).id);

    /**
     * @param index
     * 菜单选中路由跳转
     */
    const handleSelect = (index: string) => {
      let path = menuList[Number(index) - 1]['path'];
      if (router.currentRoute.value.path !== path) // 目标路由与当前路由不同则跳转
        router.push(path)
    }

    return {
      menuList,
      currentActiveIndex,
      handleSelect
    }
  },

})

</script>
<style lang="scss" scoped>
#menu-box {
  margin-right: 10px;

  .menu-title {
    width: 130px;
  }
}
</style>

顶部栏:

<template>
  <div :style="{ boxShadow: 'var(--el-box-shadow-lighter)' }" id="topBar">
    <el-button @click="changeCollapse" link id="bar-menu-icon">
      <el-icon>
        <Fold v-if="isCollapse" />
        <Expand v-else />
      </el-icon>
    </el-button>
    <span class="vertical-middle">{{ $t('common.label1') }}</span>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: "TopBar",
  props: {
    isCollapse: {
      type: Boolean
    }
  },
  emits: ["changeCollapse"],
  setup(props, context) {
    const changeCollapse = () => {
      context.emit("changeCollapse")
    }
    return {
      changeCollapse
    }
  }
})
</script>
<style lang="scss" scoped>
#topBar {
  padding: 10px 20px;

  #bar-menu-icon {
    font-size: 20px;
  }
}
</style>
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐