vue3笔记:自定义组件

自定义组件,举个:

1、封装自定义组件 CustomList.vue


<script lang="ts">
import type { IDataItem } from "../type/customlist";
// IProps 不能直接从外部导入,当前会报错,以后可能会支持(有人提了issue)
export interface IProps {
  title?: string;
  data: Array<IDataItem>;
}
script>

<script setup lang="ts">
import { onMounted } from "vue";
// 声明一些属性,给父组件传值进来,并且给部分设置了默认值
const props = withDefaults(defineProps<IProps>(), {
  title: "CustomList",
});
// 抛出事件,供父组件使用
// const emit = defineEmits(["click-item"]);
const emit = defineEmits<{
  (e: "click-item", item: IDataItem, $event: Event): void;
}>();
const clickItem = function (item: IDataItem, e: Event) {
  // 可以在函数里执行完一些操作
  console.log("自定义组件点击了组件里面的item...", item, e);
  // 再抛出宏定义的事件
  emit("click-item", item, e);
};
onMounted(() => {
  console.log("CustomList onMounted...", props);
});
script>

<template>
  <h1>{{ title }}h1>
  <ul @click="$emit('click-ul', $event, 1)">
    <li @click="clickItem(item, $event)" v-for="item in data" :key="item.id">
      {{ item.name }}
    li>
  ul>
template>

<style lang="less" scoped>style>

src 底下 type 文件夹中声明的 interface 接口文件

// src/type/customlist.d.ts
export interface IDataItem {
  name: string;
  id: number;
}

2、在 App.vue 中使用自定义组件 CustomList.vue


<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeMount } from "vue";
import CustomList from "./components/CustomList.vue";
import type { IDataItem } from "./type/customlist";
// 声明一个data,传到自定义组件CustomList
const data = reactive([] as Array<IDataItem>);
const customListRef = ref<HTMLElement | null>(null);
// 点击了自定义组件的item,执行了自定义组件抛出宏定义事件并接收其携带过来的参数
const clickItem = function (item: string, e: Event) {
  console.log("clickItem...", item, e);
};
// 点击了自定义组件的ul在templete里面直接抛出的事件并接收其携带过来的参数
const clickUl = function (e: Event, value: number) {
  console.log("clickUl...", e, value);
};

onBeforeMount(() => {
  const list: Array<IDataItem> = [
    {
      name: "aaa",
      id: 1,
    },
    {
      name: "bbb",
      id: 2,
    },
  ];
  data.push(...list);
});
onMounted(() => {
  console.log("onMounted...", customListRef.value);
});
script>

<template>
  <div class="content">
    
    <CustomList
      @click-ul="clickUl"
      @click-item="clickItem"
      :data="data"
      ref="customListRef"
    />
  div>
template>

<style scoped lang="less">
.content {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  color: var(--colorTextNormal);
  padding: 50px;
}
style>

一、组件注册

1、全局注册

main.ts 中使用 app.component('MyComponent', MyComponent) 全局注册一个组件,可以在app内的任何地方使用。

缺点:

  • 无法在生产打包时被自动移除 (也叫 tree-shaking ),即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。

  • 依赖关系不明显,出问题不易定位,用多了难维护

2、局部注册

在用到组件的地方 import 导入

<script setup>
import ComponentA from './ComponentA.vue'
script>

<template>
  <ComponentA />
template>

3、总结:非必要不实用全局注册

二、属性

父组件通过给子组件传递不同的属性数据控制子组件最终展示状态。

1、定义一个 props

通过 defineProps 宏定义一个属性

const props = defineProps(['title'])
console.log(props.title)

2、给 props添加类型并给定默认值

针对类型的 props/emit 声明

defineProps() 可以给 props 设置类型,IPropsprops 类型 为组件的 props 标注类型

withDefaults 的第二个参数支持给 props 设置默认值 使用类型声明时的默认 props 值

import type { IDataItem } from "../type/customlist";
// IProps 不能直接从外部导入,当前会报错,以后可能会支持(有人提了issue)
export interface IProps {
  title?: string;
  data: Array<IDataItem>;
}
const props = withDefaults(defineProps<IProps>(), {
  title: "CustomList",
});

3、 props只读,不可修改

组件内的 props 都是只读的,不能对其进行修改 单向数据流

<script setup lang="ts">
import { onMounted } from "vue";
// 声明一些属性,给父组件传值进来,并且给部分设置了默认值
const props = withDefaults(defineProps<IProps>(), {
  title: "CustomList",
});
onMounted(() => {
    // ❎ 不能这么干,单向数据流 props 不可修改
    props.title = "改变了CustomList的title"
});
script>

三、事件

子组件抛出内部事件并传递参数供父组件使用。

1、template 内使用 $emit 直接抛出一个事件

在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件并抛出相关参数,$emit('抛出的事件名', 需要传到父组件的参数一, 参数二...)。抛出事件名要用可以使用 camelCasekebab-case 形式(建议 kebab-case )。


<button @click="$emit('someEvent', $event, 1)">click mebutton>

2、defineEmits() 宏来声明要触发的事件