全局组件

全局组件就是 在一个地方注册在项目中任何地方都可以使用

创建一个组件components/Card/index.vue

<template>
  <div class="card">
    <div class="card-header">
      <div class="card-title">{{title}}</div>
      <div class="card-t-title">{{twoTitle}}</div>
    </div>
    <div v-if="contents" class="card-conten">
      {{contents}}
    </div>
  </div>
</template>

<script setup lang="ts">
type cardData = {
  title:String,
  twoTitle?:String,
  contents?:String
}
defineProps<cardData>()

</script>

<style scoped lang="less">
@border: red;

.card {
  border: 1px solid @border;

  &-header {
    display: flex;
    justify-content: space-between;
    padding: 20px;

  }
  .card-conten{
    border-top: 1px solid #ccc;
    padding: 20px;
  }
}
</style>

全局注册

在main.ts中注册全局组件

import { createApp } from 'vue'
import App from './App.vue'
import './assets/css/reset.css'

import Card from "./components/Card/index.vue"

const Vue = createApp(App)
Vue.component('Card',Card)
Vue.mount('#app')

注意:无论是注册组件还是注册全局自定义指令都需要在挂载前,这是一个链式调用 在mount前注册

在其他组件中无需引入直接使用

<template>
  <div class="content">
    <div class="list-item" v-for="item in 100">
      <Card title="我是标题" twoTitle="二级标题" :contents="item" />
    </div>
  </div>
</template>

递归组件

递归组件类似于js的递归,自己调用自己,通过特殊条件结束调用

父组件

<template>
  <div class="menu">
    菜单
  
    <Tree @on-click="treeClick" :treeList="treeList" />
  </div>
</template>

<script setup lang="ts">
import Tree from "../../components/Tree/Tree.vue"
import { reactive, ref } from 'vue';

type TreeDatas = {
  name: string,
  icon?: string,
  nums?: number,
  children?: TreeDatas[] | []
}

let treeList = reactive<TreeDatas[]>([
  {
    name: "item1",
    nums: 2,
    children: [
      {
        name: "item1-1",
        nums: 3,
        children: [
          {
            name: "item1-1-1",
            children: [
              {
                name: "item1-1-1-1"
              }, {
                name: "item1-1-1-2",
                children: []
              }
            ]
          }
        ]
      },
      {
        name: "item2",
        children: [
          {
            name: "item2-1",
            children: [
              {
                name: "item2-1-1"
              },
              {
                name: "item2-1-2",
                nums: 5
              }
            ]
          }
        ]
      },
      {
        name: "item3"
      },
      {
        name: "item4",
        nums: 66,
        children: [
          {
            name: "item3-1"
          }
        ]
      }
    ]
  }
])

let treeClick = (e:TreeDatas)=>{
console.log("menu:tree-click",e)
}


</script>

<style lang="less" scoped>
.menu {
  width: 220px;

  border-right: 1px solid #ccc;
}
</style>

递归子组件:/components/Tree/Tree.vue

<template>
  <div style="margin-left:10px;">
    <div @click.stop="itemClick(item)" v-for="(item, index) in treeList" :key="index">
      {{ item.name }}
      <Tree @on-click="itemClick" v-if="item?.children?.length" :treeList="item.children"></Tree>
    </div>

  </div>
</template>

<script setup lang="ts">
import { TreeDatas } from "../../utils/ts-type"
// import TreeItem from './Tree.vue'

type Props = {
  treeList?: TreeDatas[]
}


withDefaults(defineProps<Props>(), {
  treeList: () => [{ name: '123' }]
})
let emit = defineEmits(['on-click'])
let itemClick = (item: TreeDatas) => {
  // console.log("item--", item)
  emit('on-click', item)
}
</script>
<!-- <script lang="ts">
export default {
  name:"TreeItem"
}
</script> -->

<style scoped>
</style>

我们可以把下面这个定义 抽离出来放到单独的  ts-type.ts 文件中,在使用的地方引入,就不用重复定义了

ts-type.ts :

type TreeDatas = {
  name: string,
  icon?: string,
  nums?: number,
  children?: TreeDatas[] | []
}

注意:vue3 使用setup语法糖  递归组件 组件调用自身问题

1、组件定义的名称非index.vue,例如 Tree/Tree.vue 在自己调用自身的时候可以直接使用当前文件名,如下:

Tree.vue :

<template>
  <div class="menu">
    菜单
    <h2>{{ title }}</h2>
    <div>{{ dataList }}</div>
    <button @click="clickSend">向父级传参</button>
    <br>
    <br>
    <br>
    <Tree @on-click="treeClick" :treeList="treeList" />
  </div>
</template>

2、组件名称是index.vue,如 Tree/index.vue 这样直接文件名调用自身会报错,解决办法需要手动引用自身或者定义一个非setup语法糖的<script>标签导出name

<template>
  <div style="margin-left:10px;">
    <div @click.stop="itemClick(item)" v-for="(item, index) in treeList" :key="index">
      {{ item.name }}
      <Tree @on-click="itemClick" v-if="item?.children?.length" :treeList="item.children"></Tree>
    </div>
  </div>
</template>
<script setup lang="ts">
import { TreeDatas } from "../../utils/ts-type"
import TreeItem from './Tree.vue'
type Props = {
  treeList?: TreeDatas[]
}
withDefaults(defineProps<Props>(), {
  treeList: () => [{ name: '123' }]
})
let emit = defineEmits(['on-click'])
let itemClick = (item: TreeDatas) => {
  // console.log("item--", item)
  emit('on-click', item)
}
</script>

<template>
  <div style="margin-left:10px;">
    <div @click.stop="itemClick(item)" v-for="(item, index) in treeList" :key="index">
      {{ item.name }}
      <Tree @on-click="itemClick" v-if="item?.children?.length" :treeList="item.children"></Tree>
    </div>
  </div>
</template>

<script setup lang="ts">
import { TreeDatas } from "../../utils/ts-type"
type Props = {
  treeList?: TreeDatas[]
}
withDefaults(defineProps<Props>(), {
  treeList: () => [{ name: '123' }]
})
let emit = defineEmits(['on-click'])
let itemClick = (item: TreeDatas) => {
  // console.log("item--", item)
  emit('on-click', item)
}
</script>
<script lang="ts">
export default {
  name:"TreeItem"
}
</script>

动态组件

在文件夹中创建 cpA.vue、cpB.vue、cpC.vue,在组件中引入

<template>
  <div class="content">
    <div class="componts-active">
      <div class="tab-box">
        <div :class="{ 'active': nowCom.name == item.name }" @click="setTab(item)" v-for="item in comps"
          :key="item.name">
          {{ item.name }}
        </div>
      </div>
      <keep-alive>
        <component :is='nowCom.comName'></component>
      </keep-alive>

    </div>
  </div>
</template>

<script setup lang="ts">
import { markRaw, reactive, ref } from 'vue';
import cpA from './cpA.vue';
import cpB from './cpB.vue';
import cpC from './cpC.vue';

type Tabs = {
  name: string,
  comName: any
}

let comps = reactive<Tabs[]>([
  {
    name: "组件A",
    comName: markRaw(cpA)
  },
  {
    name: "组件B",
    comName: markRaw(cpB)
  },
  {
    name: "组件C",
    comName: markRaw(cpC)
  }
])

let nowCom = reactive<Tabs>({
  comName: markRaw(cpA),
  name: '组件A'
})

let setTab = (item: Tabs) => {
  nowCom.comName = item.comName
  nowCom.name = item.name

}
</script>

<style lang="less" scoped>
.content {
  flex: 1;
  margin: 20px;
  border: 1px solid #ccc;
  overflow: auto;

  .componts-active {
    width: 100%;
    height: 400px;
    border: 1px solid green;
    box-sizing: border-box;

    .tab-box {
      display: flex;

      &>div {
        padding: 10px 20px;
        border: 1px solid #ccc;
        border-right: none;
        cursor: pointer;
        &:last-child {
          border-right: 1px solid #ccc;
        }
      }
      .active {
        background: yellowgreen;
      }
    }
  }
}
</style>

主要注意的是:

<keep-alive>
    <component :is='nowCom.comName'></component>
</keep-alive>


动态绑定 切换组件
let nowCom = reactive<Tabs>({
  comName: markRaw(cpA),
  name: '组件A'
})

let setTab = (item: Tabs) => {
  nowCom.comName = item.comName
  nowCom.name = item.name
}

如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with `markRaw` or using `shallowRef` instead of `ref`. 
Component that was made reactive: 

这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者  markRaw 跳过proxy 代理

markRaw:  将一个对象标记为不可被转为代理的对象。返回也是该对象。

reactive会把组件转为代理对象,通过markRaw标记一下

Logo

前往低代码交流专区

更多推荐