Vue3 封装组件时,必须掌握的高级插槽技巧

在这里插入图片描述

1. 引言

Vue 3 组件封装的重要性

在 Vue 3 中,组件封装是构建可维护和可复用应用的关键。通过封装组件,开发者可以将复杂的 UI 和逻辑拆分为独立的模块,从而提高代码的可读性和可维护性。

插槽的作用与优势

插槽(Slot)是 Vue 组件封装中的重要特性,它允许开发者将内容分发到组件的特定位置。插槽的优势包括:

  • 灵活性:允许父组件自定义子组件的内容。
  • 复用性:通过插槽,组件可以适应不同的使用场景。
  • 可读性:插槽使得组件的结构更加清晰。

本文的目标与结构

本文旨在全面解析 Vue 3 中的高级插槽技巧,并通过详细的代码示例帮助读者掌握这些技巧。文章结构如下:

  1. 介绍基础插槽的概念和用法。
  2. 探讨作用域插槽的高级用法。
  3. 提供插槽的性能优化建议和实战案例。

2. 基础插槽

默认插槽

默认插槽是最简单的插槽类型,允许父组件将内容分发到子组件的默认位置。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot></slot>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyComponent>
    <p>这是默认插槽的内容</p>
  </MyComponent>
</template>
适用场景
  • 需要将内容分发到子组件的默认位置。
  • 子组件的内容结构简单。

具名插槽

具名插槽允许父组件将内容分发到子组件的特定位置。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyComponent>
    <template v-slot:header>
      <h1>这是头部内容</h1>
    </template>
    <p>这是默认插槽的内容</p>
    <template v-slot:footer>
      <p>这是底部内容</p>
    </template>
  </MyComponent>
</template>
适用场景
  • 需要将内容分发到子组件的多个位置。
  • 子组件的内容结构复杂。

插槽的作用域

插槽的作用域决定了插槽内容可以访问的数据。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot :user="user"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: 'John',
        age: 30,
      },
    };
  },
};
</script>

<!-- 父组件 -->
<template>
  <MyComponent v-slot="{ user }">
    <p>用户名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
  </MyComponent>
</template>
适用场景
  • 需要将子组件的数据传递给父组件。
  • 插槽内容需要根据子组件的数据动态渲染。

3. 作用域插槽

作用域插槽的概念

作用域插槽允许子组件将数据传递给父组件,父组件可以根据这些数据动态渲染插槽内容。

作用域插槽的使用场景

  • 动态表格组件。
  • 可定制的列表组件。
  • 复杂的表单组件。

示例:动态表格组件

通过作用域插槽实现一个动态表格组件。

示例代码
<!-- 子组件:MyTable.vue -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="column in columns" :key="column">{{ column }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, index) in data" :key="index">
        <td v-for="column in columns" :key="column">
          <slot :name="column" :row="row">{{ row[column] }}</slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: {
    columns: {
      type: Array,
      required: true,
    },
    data: {
      type: Array,
      required: true,
    },
  },
};
</script>

<!-- 父组件 -->
<template>
  <MyTable :columns="['name', 'age']" :data="users">
    <template v-slot:name="{ row }">
      <strong>{{ row.name }}</strong>
    </template>
    <template v-slot:age="{ row }">
      <span style="color: red;">{{ row.age }}</span>
    </template>
  </MyTable>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { name: 'John', age: 30 },
        { name: 'Jane', age: 25 },
      ],
    };
  },
};
</script>

4. 插槽的高级用法

插槽的嵌套

插槽可以嵌套使用,实现更复杂的组件结构。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot name="header"></slot>
    <div class="content">
      <slot></slot>
    </div>
    <slot name="footer"></slot>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyComponent>
    <template v-slot:header>
      <h1>这是头部内容</h1>
    </template>
    <p>这是默认插槽的内容</p>
    <template v-slot:footer>
      <p>这是底部内容</p>
    </template>
  </MyComponent>
</template>

插槽的默认内容

插槽可以设置默认内容,当父组件没有提供插槽内容时显示。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot>这是默认内容</slot>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyComponent></MyComponent>
</template>

插槽的动态命名

插槽的名称可以是动态的,通过 v-slot:[name] 实现。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot name="header"></slot>
    <slot name="content"></slot>
    <slot name="footer"></slot>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyComponent>
    <template v-slot:[slotName]>
      <p>这是动态插槽的内容</p>
    </template>
  </MyComponent>
</template>

<script>
export default {
  data() {
    return {
      slotName: 'header',
    };
  },
};
</script>

5. 插槽与 Composition API

在 Composition API 中使用插槽

Composition API 提供了更灵活的方式来组织逻辑代码,插槽可以与 Composition API 结合使用。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot :user="user"></slot>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const user = ref({
      name: 'John',
      age: 30,
    });

    return {
      user,
    };
  },
};
</script>

<!-- 父组件 -->
<template>
  <MyComponent v-slot="{ user }">
    <p>用户名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
  </MyComponent>
</template>

插槽与 provide/inject 的结合

通过 provide/inject,可以在插槽中共享数据。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot></slot>
  </div>
</template>

<script>
import { provide, ref } from 'vue';

export default {
  setup() {
    const user = ref({
      name: 'John',
      age: 30,
    });

    provide('user', user);

    return {};
  },
};
</script>

<!-- 父组件 -->
<template>
  <MyComponent>
    <ChildComponent />
  </MyComponent>
</template>

<script>
import { inject } from 'vue';

export default {
  setup() {
    const user = inject('user');

    return {
      user,
    };
  },
};
</script>

示例:可复用的表单组件

通过插槽和 Composition API 实现一个可复用的表单组件。

示例代码
<!-- 子组件:MyForm.vue -->
<template>
  <form @submit.prevent="submit">
    <slot></slot>
    <button type="submit">提交</button>
  </form>
</template>

<script>
import { ref } from 'vue';

export default {
  setup(_, { emit }) {
    const submit = () => {
      emit('submit');
    };

    return {
      submit,
    };
  },
};
</script>

<!-- 父组件 -->
<template>
  <MyForm @submit="handleSubmit">
    <input v-model="formData.name" placeholder="姓名" />
    <input v-model="formData.age" placeholder="年龄" />
  </MyForm>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const formData = ref({
      name: '',
      age: '',
    });

    const handleSubmit = () => {
      console.log('表单数据:', formData.value);
    };

    return {
      formData,
      handleSubmit,
    };
  },
};
</script>

6. 插槽的性能优化

避免不必要的插槽渲染

通过 v-ifv-for 优化插槽的渲染。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot v-if="showSlot"></slot>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const showSlot = ref(true);

    return {
      showSlot,
    };
  },
};
</script>

使用 v-ifv-for 优化插槽

通过 v-ifv-for 动态控制插槽的渲染。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot v-for="item in items" :key="item.id" :item="item"></slot>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    const items = ref([
      { id: 1, name: 'Item 1' },
      { id: 2, name: 'Item 2' },
    ]);

    return {
      items,
    };
  },
};
</script>

示例:懒加载插槽内容

通过 IntersectionObserver 实现插槽内容的懒加载。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot v-if="isVisible"></slot>
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const isVisible = ref(false);

    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          isVisible.value = true;
          observer.disconnect();
        }
      });
    });

    onMounted(() => {
      observer.observe(document.querySelector('.my-component'));
    });

    onUnmounted(() => {
      observer.disconnect();
    });

    return {
      isVisible,
    };
  },
};
</script>

7. 插槽的测试与调试

插槽的单元测试

通过 Vue Test Utils 测试插槽的功能。

示例代码
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';

test('测试默认插槽', () => {
  const wrapper = mount(MyComponent, {
    slots: {
      default: '这是默认插槽的内容',
    },
  });

  expect(wrapper.text()).toContain('这是默认插槽的内容');
});

插槽的调试技巧

通过 Vue Devtools 调试插槽的内容。

示例代码
<!-- 子组件:MyComponent.vue -->
<template>
  <div class="my-component">
    <slot></slot>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyComponent>
    <p>这是默认插槽的内容</p>
  </MyComponent>
</template>

示例:使用 Vue Test Utils 测试插槽

通过 Vue Test Utils 测试具名插槽和作用域插槽。

示例代码
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';

test('测试具名插槽', () => {
  const wrapper = mount(MyComponent, {
    slots: {
      header: '<h1>这是头部内容</h1>',
      footer: '<p>这是底部内容</p>',
    },
  });

  expect(wrapper.find('h1').text()).toBe('这是头部内容');
  expect(wrapper.find('p').text()).toBe('这是底部内容');
});

test('测试作用域插槽', () => {
  const wrapper = mount(MyComponent, {
    scopedSlots: {
      default: '<p>{{ props.user.name }}</p>',
    },
    data() {
      return {
        user: {
          name: 'John',
          age: 30,
        },
      };
    },
  });

  expect(wrapper.find('p').text()).toBe('John');
});

8. 实战案例

案例一:实现一个可定制的模态框组件

通过插槽实现一个可定制的模态框组件。

示例代码
<!-- 子组件:MyModal.vue -->
<template>
  <div class="modal">
    <div class="modal-header">
      <slot name="header"></slot>
    </div>
    <div class="modal-body">
      <slot></slot>
    </div>
    <div class="modal-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyModal>
    <template v-slot:header>
      <h1>这是模态框的头部</h1>
    </template>
    <p>这是模态框的内容</p>
    <template v-slot:footer>
      <button @click="closeModal">关闭</button>
    </template>
  </MyModal>
</template>

案例二:实现一个可扩展的卡片组件

通过插槽实现一个可扩展的卡片组件。

示例代码
<!-- 子组件:MyCard.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header"></slot>
    </div>
    <div class="card-body">
      <slot></slot>
    </div>
    <div class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

<!-- 父组件 -->
<template>
  <MyCard>
    <template v-slot:header>
      <h1>这是卡片的头部</h1>
    </template>
    <p>这是卡片的内容</p>
    <template v-slot:footer>
      <button @click="viewDetails">查看详情</button>
    </template>
  </MyCard>
</template>
Vue3 中,如果你需要对已经存在的组件进行二次封装并扩展新的插槽,你可以按照以下步骤操作: 1. **理解基础组件结构**:首先,你需要了解原始组件的模板和选项(props、data、methods等)。通常,插槽是在`<template>`标签中通过`<slot>`元素声明的。 2. **复制组件源码**:创建一个新的文件夹,比如 `customized-component`,将原始组件的 `.vue` 文件复制到这个文件夹下,并对其进行修改。 3. **扩展插槽**:在复制后的组件模板里,可以在需要添加新插槽的地方插入 `<slot>` 元素,可以给它指定一个名字,例如 `<template><div>...</div><slot name="new-slot"></slot></template>`。然后,在组件的 `export default` 对象中,通过 `slots` 属性定义插槽的含义,如 `export default { ... , slots: { newSlot: &#39;这是新插槽的描述&#39; } }`。 4. **暴露插槽属性**:如果需要传递数据到新的插槽,可以通过 props 将其暴露出来,接收者通过 slot 的 `v-bind` 或者 `$attrs` 来获取。例如,`<template><slot v-bind:newProp="propValue" /></template>`。 5. **使用组件**:当你在父组件中使用这个定制化的组件,可以直接像使用原组件一样使用,通过 `<customized-component><template #new-slot>这里是插槽内容</template></customized-component>` 来引用新增的插槽。 6. **示例代码**: ```html <script setup> import CustomizedComponent from &#39;./CustomizedComponent.vue&#39;; const propValue = &#39;这是外部传入的新值&#39;; <CustomizedComponent> <template #new-slot> <p>{{ propValue }}</p> <!-- 这里的内容会渲染出 propValue --> </template> </CustomizedComponent> </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北辰alk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值