vue3中的各种组件


一、父子组件

1、父组件

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const msg = ref('Hello World!')

import ChildComponent from './ChildComponent.vue'

// 父组件接收子组件传递的值,需要实现子组件派发的方法,参数就是子组件传递的值
const cdata = ref<string[]>([])
const getData = (list: string[]) => {
  cdata.value = []
  list.forEach((item) => cdata.value.push(item))
  // console.log('子组件传值:', list)
}

// 父组件中接收子组件暴漏出来的变量或者方法
const childData = ref<InstanceType<typeof ChildComponent>>()
onMounted(() => {
  console.log('这是从子组件接收过来的数据:', childData.value?.['num'], childData.value?.clist)
  console.log('childData.value?.childMethod()', childData.value?.childMethod())
})
</script>

<template>
  <!-- 父组件传值到子组件中,使用在子组件上增加使用:绑定属性名称即可,子组件中使用defineProps接受接收即可获取-->
  <ChildComponent ref="childData" :passValue="msg" @pass="getData"></ChildComponent>

  <p>这是子组件传递到父组件的值:{{ cdata }}</p>
  <ul v-for="(item, index) in cdata" :key="index">
    <li>{{ item }} -- {{ index }}</li>
  </ul>
</template>

<style scoped lang="css"></style>

2、子组件

<script setup lang="ts">
// 子组件接收父组件传值的属性

// JS写法
// const pro = defineProps({
//   passValue: {
//     type: String,
//     required: true,
//     default: 'Default message'
//   },
//   user: {
//     required: false,
//     default: { name: 'John', age: 32 }
//   }
// })

/**
 * TS写法,如果需要设置默认值,需要在外层包装withDefaults函数,接受两个参数,全是对象,设置默认值的时候,如果是一个复杂的对象,
 * 则需要列举出它的所有键值对,并将结果返回才行
 */
const pro = withDefaults(
  defineProps<{
    passValue: string
    user: any
  }>(),
  {
    passValue: 'Default message',
    user: () => {
      return { name: 'John', age: 32 }
    }
  }
)

console.log('代码中输出:', pro.passValue)

// 子组件传值到父组件中,使用defineEmits定义传递的方法名,并派发该方法,父组件中需要定义该方法名获取子组件传递的属性
const list = ['foo', 'bar', 'baz', 'ee']
// const emit = defineEmits(['pass'])
const emit = defineEmits<{
  // (e: 'pass', list: string[]): void
  pass: [list: string[]]
}>()
emit('pass', list)

// 子组件也可以暴漏自己定义的变量已达到传值的目的,或者直接暴漏自己定义好的方法供父组件直接调用
defineExpose({
  clist: list,
  num: 123,
  childMethod: () => {
    console.log('我是子组件定义的方法,我被暴漏出来供父组件调用', '')
    return 123
  }
})
</script>

<template>
    <p>这是父组件传递到子组件的值---------------</p>
    <div>{{ passValue }}</div>
    <div>{{ user }}</div>
</template>

<style scoped lang="css"></style>


3、父子组件传值

  • 1、父组件向子组件中传值:父组件引入子组件是之后,直接在子组件上通过:属性名绑定参数传值即可。子组件接收的时候,有两种处理方式:
    • JS中使用defineProps接收对应的参数,并且也可以定义参数类型以及默认值。
const pro = defineProps({
    passValue: {
        type: String, // 定义参数类型
        required: true, // 是否必传
        default: "Default message", // 默认值
    },
    user: {
        required: false,
        default: { name: "John", age: 32 },
    },
});
  • TS中使用defineProps接收对应的参数,并且也可以定义参数类型,但是要定义默认值的话,就必须使用TS中特有的函数withDefaults,它接受两个参数,并且都是对象,要注意的是,如果参数是一个复杂的对象,在设置默认值的时候要使用函数的方式进行返回才可以设置默认值。
const pro = withDefaults(
    defineProps<{
        passValue: string; // 定义参数类型
        user: any;
    }>(),
    {
        passValue: "Default message", // 设置参数默认值
        user: () => {
            return { name: "John", age: 32 };  // 复杂的参数,需要使用函数通过return返回默认值
        },
    }
);

注意: defineProps接收参数,如果需要在JS或者TS代码中使用,那必须定义一个变量去接收defineProps的返回值,后边使用返回值.属性名去使用接收到的参数。例如:使用pro.passValue去获取passValue的值。

  • 2、子组件向父组件中传值
    • 2.1、VUE3中setup语法糖模式下,子组件可以使用defineExpose将自己定义的变量或者方法暴漏给父组件使用。
// 子组件也可以暴漏自己定义的变量已达到传值的目的,或者直接暴漏自己定义好的方法供父组件直接调用
defineExpose({
    clist: list,
    num: 123,
    childMethod: () =>
        console.log("我是子组件定义的方法,我被暴漏出来供父组件调用", ""),
});

// 父组件中接收子组件暴漏出来的变量或者方法
<ChildComponent ref="childData"></ChildComponent>
// 这里的childData名称必须和定义在子组件标签上的ref的名称一致,类型推断可以通过如下方式推断为子组件的类型
const childData = ref<InstanceType<typeof ChildComponent>>();
onMounted(() => {
    console.log(
        "这是从子组件接收过来的数据:",
        childData.value?.["num"], // .["num"]和.num的写法效果都是一样的,都可以获取到num的值
        childData.value?.clist
    );
    childData.value?.childMethod(); // 方法要也是可以有返回值的,父组件中可以接收这个返回值
});

  • 2.2、子组件可以使用defineEmits将自己定义的变量传递给父组件使用。
// 子组件传值到父组件中,使用defineEmits定义传递的方法名,并派发该方法,父组件中需要定义该方法名获取子组件传递的属性
const list = ["foo", "bar", "baz", "ee"];
// const emit = defineEmits(['pass']) // 这种定义emit是常规的定义,下边这种是ts定义emit的方式
const emit = defineEmits<{
    // (e: 'pass', list: string[]): void   // 这两种传递pass事件的方式是等效的,下边这种更加简洁
    pass: [list: string[]];
}>();
emit("pass", list);

// 父组件接收子组件传递的值,需要实现子组件派发的方法,参数就是子组件传递的值
<ChildComponent @pass="getData"></ChildComponent>
const cdata = ref<string[]>([]);
const getData = (list: string[]) => {
    cdata.value = [];
    list.forEach((item) => cdata.value.push(item));
    console.log('子组件传值:', list)
};

4、兄弟组件传参

兄弟传参有以下两种方式处理:

  • 4.1、借助父组件进行传参,子组件将值传递给父组件,父组件将值接收后再传递给其他子组件,参考3、父子组件传参
  • 4.2、借助总线Bus处理,原理是利用JS的发布订阅模式进行监听处理。
// Bus.ts 总线的自定义实现,实际上就是重写`emit`和`on`方法。
// 限制总线Bus的类型 
type BusType = {
    emit: (name: string) => void;
    on: (name: string, callback: Function) => void;
};

// 限制存储的key的类型
type PKey = string | symbol | number;

// 用来存放总线事件的缓存,key就是派发的事件名称,如“on-send”,value是接收时事件时的处理函数,是一个Array<Function>结构
type CallBackStore = {
    [key: PKey]: Array<Function>;
};

class Bus implements BusType {
    private callBackStore: CallBackStore;

    constructor() {
        this.callBackStore = {};
    }

	// 接收事件,name就是事件名称,callback就是回调函数,即自定义的事件处理逻辑
    on(name: string, callback: Function) {
        const call = this.callBackStore[name] || []; // // 根据事件名称获取当前总线中缓存的所有的与该事件有关的所有的回调函数
        call.push(callback); // 将新的与该事件有关的回调函数添加到这事件名称下
        this.callBackStore[name] = call; // 关联
    }
	
	// 派发事件的具体实现,name就是事件名称,args时事件参数
    emit(name: string, ...args: Array<any>) {
        const callback = this.callBackStore[name] || []; // 根据事件名称获取当前总线中缓存的所有的与该事件有关的所有的回调函数
        callback.forEach((call) => call.apply(this, args)); // 循环调用这些回调函数
    }
}

export default new Bus();

// Child1Component.vue
<script setup lang="ts">
import Bus from './Bus'
import { ref } from 'vue'

const msg = ref('')

// 接收向自身派发过来的on-send事件,接收参数并处理
Bus.on('on-send', (param: string) => {
  console.log('param', param)
  msg.value = param
})

// 当点击按钮时,向Child2Component子组件派发一个on-send1事件,并将参数带过去
const Child1 = () => {
  Bus.emit('on-send1', 'Child1给Child2传递的值')
}
</script>

<template>
    <p>Child1---------------{{ msg }}</p>
    <button @click="Child1">Child1</button>
</template>

<style scoped lang="css"></style>

// Child2Component.vue
<script setup lang="ts">
import Bus from './Bus'
import { ref } from 'vue'

const msg = ref('')

// 接收向自身派发过来的on-send1事件,接收参数并处理
Bus.on('on-send1', (param: string) => {
  console.log('param', param)
  msg.value = param
})

// 当点击按钮时,向Child1Component子组件派发一个on-send事件,并将参数带过去
const Child2 = () => {
  Bus.emit('on-send', 'Child2给Child1传递的值')
}
</script>

<template>
    <p>Child2---------------{{ msg }}</p>
    <button @click="Child2">Child2</button>
</template>

<style scoped lang="css"></style>

// ParentComponent.vue
<script setup lang="ts">
import Child1Component from './Child1Component.vue'
import Child2Component from './Child2Component.vue'
</script>

<template>
    <p>==========================================</p>
    <Child1Component></Child1Component>
    <Child2Component></Child2Component>
</template>

<style scoped lang="css"></style>
  • 4.3、利用借助父组件传参的思想,我们也可以借助store进行兄弟间参数传递,将数据存储在store中,其他子组件直接从store中获取,比较复杂的数据建议使用这种方式处理,其他的一些简单的数据使用Bus总线比较好。

二、全局组件

将定义好的组件在main.ts中注册为全局组件,这样的组件将在整个项目中任意组件中可以引入使用。

import ChildComponentVue from './passvalue/ChildComponent.vue'
// 将导入的组件注册为全局组件,通过app.component注册,它有两个参数:key,为组件的名称,可以随便起名;component,为想要注册的组件。
app.component('ChildComponent111', ChildComponentVue)

// 批量注册组件,可参考elementui中icon的示例
import * as ElementPlusIconsVue from "@element-plus/icons-vue";

const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component);
}

// 全局组件注册成功后,直接使用注册的key在其他组件中引入即可
<ChildComponent111>我是全局组件</ChildComponent111>

三、局部组件

将定义好的组件在其他组件中其他组件中可以引入使用,这样的组件称为局部组件,局部组件只能在引入它的组件中使用,其他未引入的组件中不能使用。

import ChildComponent from './ChildComponent.vue'

<ChildComponent>我是局部组件</ChildComponent>

四、递归组件

递归组件一般用在树形结构中,在vue3中,递归组件通过自己使用自己来完成树形结构的创建。

// TreeComponent.vue
<script setup lang="ts">
/**
 * TS写法,如果需要设置默认值,需要在外层包装withDefaults函数,接受两个参数,全是对象,设置默认值的时候,如果是一个复杂的对象,
 * 则需要列举出它的所有键值对,并将结果返回才行
 */
interface Tree {
  name: string
  checked: boolean
  children?: Tree[]
}

withDefaults(
  defineProps<{
    region: Tree[] // 接收父组件传递过来的递归数据
  }>(),
  {
    region: () => {
      return []
    }
  }
)

const selected = (item: Tree, index: number, event: Event) => console.log(item, index, event.target)
</script>

<template>
	 // 循环数据
    <div v-for="(item, index) in region" :key="index" class="div" /**@click="selected(item, index, $event)"*/> // 这里的@click事件会触发冒泡效果,会将自己的以及自己的父级事件全部触发,解决办法使用.stop阻止
        <input type="checkbox" v-model="item.checked" @click="selected(item, index, $event)"/><span>
            {{ item.name }}</span>
        
        // 递归的开始,直接将自己作为组件引入,递归体:item.children还有值,递归条件:item.children?.length的长度不为0
        <TreeComponent
            v-if="item.children?.length != 0"
            :region="item.children"
        ></TreeComponent>
    </div>
</template>

<style scoped lang="css">
.div {
  margin-left: 10px;
  padding: 5px 10px;
}
</style>

// ParentComponent.vue
<script setup lang="ts">
interface Tree {
  name: string
  checked: boolean
  children?: Tree[]
}

// 定义递归数据,这个数据将会传递给TreeComponent递归使用。
const region = reactive<Tree[]>([
  {
    name: '广东',
    checked: false,
    children: [
      {
        name: '广州',
        checked: false,
        children: [
          {
            name: '天河',
            checked: false,
            children: []
          },
          {
            name: '番禺',
            checked: false,
            children: []
          },
          {
            name: '白云',
            checked: false,
            children: []
          }
        ]
      },
      {
        name: '东莞',
        checked: false,
        children: [
          {
            name: '大朗',
            checked: false,
            children: []
          }
        ]
      },
      {
        name: '珠海',
        checked: false,
        children: [
          {
            name: '香洲',
            checked: true,
            children: []
          },
          {
            name: '斗门',
            checked: false,
            children: []
          }
        ]
      }
    ]
  }
])
</script>

<template>
    <p>==========================================</p>
    <TreeComponent :region="region">我是递归组件</TreeComponent>
</template>

<style scoped lang="css"></style>

五、动态组件

动态组件一般用于tab切换的场景中,指的是动态的显示组件内容。

1、TabComponent.vue:点击不同的按钮,显示不同组件信息
<script setup lang="ts">
import { ref, reactive } from 'vue'

import Tab1Component from './Tab1Component.vue'
import Tab2Component from './Tab2Component.vue'
import Tab3Component from './Tab3Component.vue'

const tab = ref(Tab1Component)
</script>

<template>
    <div id="myDiv" class="myDiv">
        <button @click="tab = Tab1Component">Tab1</button>
        <button @click="tab = Tab2Component">Tab2</button>
        <button @click="tab = Tab3Component">Tab3</button>
    </div>
    <component :is="tab"></component>
</template>

<style scoped lang="css">
button {
  width: 100px;
  height: 60px;
}

.myDiv {
  width: 100%;
  height: 100px;
  border: 1px #ccc;
}
</style>
2、Tab1Component.vue/Tab2Component.vue/Tab3Component.vue:三个组件的内容不同,用于区分即可
<script setup lang="ts">
import { ref } from 'vue'

const msg = ref('人生得意须尽欢,莫使金樽空对月。')
</script>

<template>
    <div id="myDiv" class="myDiv">{{ msg }}</div>
</template>

<style scoped lang="css">
.myDiv {
  color: red;
  width: 100px;
  height: 100px;
  border: solid 1px #ccc;
}
</style>

Logo

前往低代码交流专区

更多推荐