Vue学习记录之八(局部组件,全局组件,递归组件,动态组件)

一、局部组件

在src\components\Card.vue 建立一个文件,代码如下:

html 复制代码
<template>
    <div class="card">
        <header>
            <div>标题</div>
            <div>副标题</div>
        </header>
        <section>
            内容
        </section>
    </div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'

</script>
<style scoped lang="scss">
$border: #ccc;
.card{
    border: 1px solid $border;
    width: 400px;
    header{
        display: flex;
        justify-content: space-between;
        padding: 5px;
        border-bottom: 1px solid $border;
    }
    section{
        padding: 5px;
        min-height: 300px;
    }

}
</style>

然后在要使用的文件中引入并使用。

html 复制代码
<template>
    <div>
    	<!--2、使用-->
        <Card></Card>
    </div>
</template>
<script setup lang='ts'>
//1、引入
import Card from './components/Card.vue';
</script>

二、全局组件

上面局部变量是谁使用,谁应用。而全局变量在配置中一次引入,任何地方都可以随时使用。

在main.ts 文件引入

ts 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
//1、引入组件
import Card from './components/Card.vue';

const app = createApp(App)
//2,注册组件为全局变量
app.component('Card',Card)
app.use(createPinia())
app.use(router)

app.mount('#app')

然后在任意需要的地方,使用 即可。如果有何多组件,可以使用批量注册组件的方法进行。例如我们在element UI 中导入Icons下:

ts 复制代码
import * as ElementPlusIconsVue from '@element-plus/icons-vue' 
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

三、递归组件

1)、父传子数据

html 复制代码
<template>
    <div>
      <!--给子组件传递一个名为datas的变量,该变量数据下面已定义-->
      <Tree :datas="data"></Tree>
    </div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import Tree from './components/Tree.vue';
interface Tree{
  name:string,
  checked: boolean,
  children?: Tree[]
}
const data = reactive<Tree[]>([
  {
    name:"1",
    checked:false,
    children:[
      {
        name:"1-1",
        checked:false,
      }
    ]
  },
  {
    name:"2",
    checked:true,
    children:[
      {
        name:"2-1",
        checked:false,
      }
    ]
  },
  {
    name:"3",
    checked:false,
    children:[
      {
        name:"3-1",
        checked:false,
        children:[
          {
            name:"3-1-1",
            checked:false,
          },
          {
            name:"3-1-2",
            checked:false,
          },
        ]
      }
    ]
  },
])

</script>

2)、开始递归

在子组件中(Tree.vue)开始编写递归代码

1、直接使用组件名称来当成循环体
html 复制代码
<template>
    <div v-for="item in data" class="tree">
    <input type="checkbox" v-model="item.checked"><span>{{ item.name }}</span>
    <!--
        下面开始递归:
        可以直接使用文件名当组件名,也就是自己用自己的文件名称来递归。
        绑定的数据要和上面循环的数据名称一致,既用上面的 :data,
        而且使用v-if来判断数组的长度,为真时递归
    -->
    <Tree v-if="item?.children?.length" :data="item.children"></Tree>
    <!--
    可选链操作符的使用(?)的使用,如果后面的属性不到的话,就返回undefined,避免报错。undefined隐式转换以后就是false,停止了循环。
    一般配合双问(??)好表达式进行使用,如果左边返回一个undefined或者null值的时候(而且只有是undefined或者null才可以),则可以返回双问号后面(右边)的值,有点类似三目运算符。
    undefined??123  返回123
    0??123  返回0
    -->
    </div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
interface Tree{
  name:string,
  checked: boolean,
  children?: Tree[]
}
defineProps<{
    data?:Tree[]
}>()
</script>
<style lang="scss" scoped>
.tree{
    margin-left: 10px;
}
</style>
2、自定义一个名称用来递归

上面我们直接使用了文件名,如果感觉突兀,也可以自定义,在子组件中,新建一组script代码。

html 复制代码
<template>
    <div v-for="item in datas" class="tree">
    <input type="checkbox" v-model="item.checked"><span>{{ item.name }}</span>
    <!--
        递归体使用自定义的LvmanbaTree
    -->
    <LvmanbaTree v-if="item.children?.length" :datas="item.children"></LvmanbaTree>
    </div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
interface Tree{
  name:string,
  checked: boolean,
  children?: Tree[]
}
defineProps<{
    datas?:Tree[]
}>()
</script>
<script lang="ts">
export default{
	name="LvmanbaTree"
}
</script>
3、使用插件

第二种方法虽然可以自定了循环名称了,但是也要增加一组

第一步: 安装插件

pnpm add -D unplugin-vue-define-options @vue-macros/volar

第二步:在vite.config.ts中进行配置

ts 复制代码
//1、引入组件
import DefineOptions from 'unplugin-vue-define-options/vite'

export default defineConfig({
  //2、在plugins中注册下,使用DefineOpetions()
  plugins: [DefineOptions()],
})

第三步:

在tsconfig.json 进行如下配置:就出现了代码提示了。

ts 复制代码
{
  "compilerOptions": {
    // ...
    "types": ["unplugin-vue-define-options/macros-global" /* ... */]
  }
}

第四步: 使用

它提供一个编译宏,必须使用这个插件,才能使用defineOptions. 这个是插件提供的,这样就不用再写一个

ts 复制代码
defineOptions({
	name:"LvmanbaTree"
})
4、接收tree的点击事件
ts 复制代码
<template>
	<!--
		不加stop,出现冒泡事件
	-->
    <div @click.stop="clickTap(item)" v-for="item in datas" class="tree">
    <input type="checkbox" v-model="item.checked"><span>{{ item.name }}</span>
    <!--
        递归体使用自定义的LvmanbaTree
    -->
    <LvmanbaTree v-if="item.children?.length" :datas="item.children"></LvmanbaTree>
    </div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
interface Tree{
  name:string,
  checked: boolean,
  children?: Tree[]
}
defineProps<{
    datas?:Tree[]
}>()
const clickTap = (item: Tree) =>{
    console.log(item)
}
</script>
<script lang="ts">
    export default{
        name:"LvmanbaTree"
    }
</script>
<style lang="scss" scoped>
.tree{
    margin-left: 10px;
}
</style>

也可以传递事件

html 复制代码
<template>
    <div @click.stop="clickTap(item,$event)" v-for="item in datas" class="tree">
    <input type="checkbox" v-model="item.checked"><span>{{ item.name }}</span>
    <Tree v-if="item.children?.length" :datas="item.children"></Tree>
    </div>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
interface Tree{
  name:string,
  checked: boolean,
  children?: Tree[]
}
defineProps<{
    datas?:Tree[]
}>()
const clickTap = (item: Tree,e) =>{
    //console.log(item)
    console.log(e.target)
}
</script>

<style lang="scss" scoped>
.tree{
    margin-left: 10px;
}
</style>

四、 动态组件

动态组件,就是多个组件使用同一个挂载点,也就是一个位置,可以动态切换的显示多个组件。 在挂载点使用 component标签,然后在使用v-bind:is = "组件"。 应用场景,典型的tab切换。当然也可以使用动态路由来实现。

实例: 效果如下。

第一步: 先创建三个组件

第二步:编写代码

一个标签内可以有两个想通的属性,但是必须一个静态,另外一个是动态。另外一个难理解的地方就是就把一个组件赋值给一个变量。

html 复制代码
<template>
    <div style="display: flex;">
      
      <div 
      @click="switchCom(item,index)"
      v-for="(item,index) in data" 
      class="tabs" 
      :class="[active == index ? 'active':'']"
      >
        <div>{{ item.name }}</div>
      </div>
    </div>
    <component :is="comId"></component>
</template>
<script setup lang='ts'>
import { ref,reactive } from 'vue'
import AVue from './components/A.vue';
import BVue from './components/B.vue';
import CVue from './components/C.vue';
const comId = ref(AVue)
const active = ref(0)
const switchCom =(item,index:number) =>{
  active.value = index,
  comId.value = item.com
}
const data = reactive([
  {
    name:"A组件",
    com: AVue  //此时的变量是一个组件
  },
  {
    name:"B组件",
    com: BVue
  },
  {
    name:"C组件",
    com: CVue
  }

])
</script>
<style scoped>
.active{
  background: skyblue;
}
.tabs{
  border: solid 1px #ccc;
  padding: 5px 10px;
  margin: 5px;
  cursor: pointer; /* 鼠标放上去变成小手 */
}
</style>

上面代码可以正常使用,但是在控制台有错误报错,原因就是组件变量导致的。

错误的引发:他包括了一些组件的信息,这里的属性没有必要去劫持,因此我们需要跳过它,他提供了2个API,第一个是shallowRef(它是代理最外面的一层), 另外一个是markRaw(它是在对象里面,它有一个skip属性,reactive如果碰到这个属性,也会跳过proxy代理)

错误修复如下:

html 复制代码
<script setup lang='ts'>
import { ref,reactive,markRaw,shallowRef } from 'vue'
import AVue from './components/A.vue';
import BVue from './components/B.vue';
import CVue from './components/C.vue';
// 修改错误第一处,使用shallowRef
const comId = shallowRef(AVue)
const active = ref(0)
const switchCom =(item,index:number) =>{
  active.value = index,
  comId.value = item.com
  console.log(comId.value)
}
// 修改错误第二处,使用markRaw
const data = reactive([
  {
    name:"A组件",
    com: markRaw(AVue)
  },
  {
    name:"B组件",
    com: markRaw(BVue)
  },
  {
    name:"C组件",
    com: markRaw(CVue)
  }

])
</script>

第二种方法:

html 复制代码
<template>
    <div style="display: flex;">
      <div 
      @click="switchCom(item,index)"
      v-for="(item,index) in data" 
      class="tabs" 
      :class="[active == index ? 'active':'']">
        <div>{{ item.name }}</div>
      </div>
    </div>
    <component :is="comId"></component>
</template>
<script setup lang='ts'>
import { ref,reactive,shallowRef } from 'vue'

// 修改错误第一处,使用shallowRef
const comId = shallowRef('AVue')
const active = ref(0)
const switchCom =(item,index:number) =>{
  active.value = index,
  comId.value = item.com
  console.log(comId.value)
}
const data = reactive([
  {
    name:"A组件",
    com: "AVue"
  },
  {
    name:"B组件",
    com: "BVue"
  },
  {
    name:"C组件",
    com: "CVue"
  }

])
</script>
<script lang="ts">
import AVue from './components/A.vue';
import BVue from './components/B.vue';
import CVue from './components/C.vue';
export default{
  components:{
    AVue,
    BVue,
    CVue
  }
}
</script>
<style scoped>
.active{
  background: skyblue;
}
.tabs{
  border: solid 1px #ccc;
  padding: 5px 10px;
  margin: 5px;
  cursor: pointer; /* 鼠标放上去变成小手 */
}
</style>
相关推荐
四喜花露水1 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy11 分钟前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
怀旧66617 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
web Rookie41 分钟前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
工业甲酰苯胺1 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
infiniteWei1 小时前
【Lucene】原理学习路线
学习·搜索引擎·全文检索·lucene
follycat1 小时前
[极客大挑战 2019]PHP 1
开发语言·学习·网络安全·php
程序员爱技术5 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会5 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
悦涵仙子6 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass