vue3 + vite + ts + setup , 第三练 setup ts语法糖中如何使用全局组件,递归组件,动态组件
vue3+setup+ts Vue3 使用setup语法糖+ts中如何使用全局组件,如何使用递归组件,setup语法糖组件调用自身的问题如何解决
全局组件
全局组件就是 在一个地方注册在项目中任何地方都可以使用
创建一个组件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标记一下
更多推荐
所有评论(0)