前端八股Vue---自定义组件(控件)

目录

一、什么是自定义组件?

二、常见自定义组件示例

三、封装一个分页组件(完整示例)

四、封装一个搜索栏组件(带防抖)

五、封装一个弹窗组件

六、封装自定义组件的核心要点

七、问答


一、什么是自定义组件?

把项目中重复使用的、通用的模块封装成可复用的组件,方便多个页面直接调用,避免重复写同样的代码。

复制代码
没有自定义组件:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 页面A   │ │ 页面B   │ │ 页面C   │
│ 重复写  │ │ 重复写  │ │ 重复写  │
│ 分页逻辑│ │ 分页逻辑│ │ 分页逻辑│
└─────────┘ └─────────┘ └─────────┘

有自定义组件:
┌─────────────────────────────────┐
│        分页组件(写一次)         │
└─────────────────────────────────┘
         ↑        ↑        ↑
    ┌────┴────┐   │   ┌────┴────┐
    │ 页面A   │   │   │ 页面C   │
    │ 直接调用 │   │   │ 直接调用 │
    └─────────┘   │   └─────────┘
              ┌───┴───┐
              │ 页面B  │
              │ 直接调用│
              └───────┘

二、常见自定义组件示例

组件类型 说明 复用场景
公共头部/底部 每个页面都一样 所有页面
分页组件 页码切换、每页条数 列表页
搜索栏组件 输入框、搜索按钮、防抖 搜索场景
弹窗/确认框 确定、取消、回调 确认操作
表单组件 输入框、下拉框、校验 表单场景
表格组件 请求、加载、选中、操作列 数据列表

三、封装一个分页组件(完整示例)

javascript 复制代码
<!-- components/Pagination.vue -->
<template>
  <div class="pagination">
    <button 
      :disabled="currentPage === 1" 
      @click="handlePageChange(currentPage - 1)"
    >
      上一页
    </button>
    
    <span>第 {{ currentPage }} / {{ totalPages }} 页</span>
    
    <button 
      :disabled="currentPage === totalPages" 
      @click="handlePageChange(currentPage + 1)"
    >
      下一页
    </button>
    
    <select :value="pageSize" @change="handleSizeChange">
      <option :value="10">10条/页</option>
      <option :value="20">20条/页</option>
      <option :value="50">50条/页</option>
    </select>
  </div>
</template>

<script setup>
import { computed } from 'vue'

// 1. 接收父组件传过来的数据(props)
const props = defineProps({
  currentPage: {
    type: Number,
    default: 1
  },
  pageSize: {
    type: Number,
    default: 10
  },
  total: {
    type: Number,
    default: 0
  }
})

// 2. 声明要触发的事件(emit)
const emit = defineEmits(['update:currentPage', 'update:pageSize', 'change'])

// 计算总页数
const totalPages = computed(() => Math.ceil(props.total / props.pageSize))

// 3. 触发事件,通知父组件
function handlePageChange(page) {
  emit('update:currentPage', page)
  emit('change', { page, pageSize: props.pageSize })
}

function handleSizeChange(e) {
  const newSize = Number(e.target.value)
  emit('update:pageSize', newSize)
  emit('change', { page: 1, pageSize: newSize })
}
</script>
javascript 复制代码
<!-- 父组件中使用 -->
<template>
  <div>
    <!-- 使用分页组件,双向绑定数据 -->
    <Pagination
      v-model:currentPage="page"
      v-model:pageSize="size"
      :total="total"
      @change="loadData"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Pagination from '@/components/Pagination.vue'

const page = ref(1)
const size = ref(10)
const total = ref(100)

function loadData({ page, pageSize }) {
  // 调用接口获取数据
  fetchData({ page, pageSize })
}
</script>

四、封装一个搜索栏组件(带防抖)

javascript 复制代码
<!-- components/SearchBar.vue -->
<template>
  <div class="search-bar">
    <input 
      type="text"
      :value="modelValue"
      placeholder="请输入搜索关键词"
      @input="handleInput"
    />
    <button @click="handleSearch">搜索</button>
    <button v-if="modelValue" @click="handleClear">清空</button>
  </div>
</template>

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

const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  delay: {
    type: Number,
    default: 300
  }
})

const emit = defineEmits(['update:modelValue', 'search'])

let timer = null

// 防抖处理
function handleInput(e) {
  const value = e.target.value
  emit('update:modelValue', value)
  
  if (timer) clearTimeout(timer)
  timer = setTimeout(() => {
    emit('search', value)
  }, props.delay)
}

function handleSearch() {
  if (timer) clearTimeout(timer)
  emit('search', props.modelValue)
}

function handleClear() {
  emit('update:modelValue', '')
  emit('search', '')
}
</script>
javascript 复制代码
<!-- 父组件中使用 -->
<template>
  <SearchBar v-model="keyword" @search="handleSearch" />
</template>

<script setup>
import { ref } from 'vue'
import SearchBar from '@/components/SearchBar.vue'

const keyword = ref('')

function handleSearch(val) {
  console.log('搜索:', val)
  // 调用接口搜索
}
</script>

五、封装一个弹窗组件

javascript 复制代码
<!-- components/Modal.vue -->
<template>
  <Teleport to="body">
    <div v-if="visible" class="modal-overlay" @click="handleClose">
      <div class="modal-container" @click.stop>
        <div class="modal-header">
          <h3>{{ title }}</h3>
          <button class="close" @click="handleClose">×</button>
        </div>
        <div class="modal-body">
          <slot></slot>
        </div>
        <div class="modal-footer">
          <button @click="handleCancel">取消</button>
          <button @click="handleConfirm">确定</button>
        </div>
      </div>
    </div>
  </Teleport>
</template>

<script setup>
const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  },
  title: {
    type: String,
    default: '提示'
  }
})

const emit = defineEmits(['update:visible', 'confirm', 'cancel'])

function handleClose() {
  emit('update:visible', false)
}

function handleConfirm() {
  emit('confirm')
  handleClose()
}

function handleCancel() {
  emit('cancel')
  handleClose()
}
</script>

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-container {
  background: white;
  border-radius: 8px;
  min-width: 400px;
}
</style>
javascript 复制代码
<!-- 父组件中使用 -->
<template>
  <button @click="showModal = true">打开弹窗</button>
  
  <Modal v-model:visible="showModal" title="确认删除" @confirm="handleDelete">
    <p>确定要删除这条数据吗?</p>
  </Modal>
</template>

<script setup>
import { ref } from 'vue'
import Modal from '@/components/Modal.vue'

const showModal = ref(false)

function handleDelete() {
  console.log('执行删除操作')
}
</script>

六、封装自定义组件的核心要点

要点 说明
props 接收父组件传过来的数据
emit 向父组件发送事件
slot 允许父组件传入自定义内容
v-model 实现双向绑定
defineExpose 暴露方法给父组件调用

七、问答

问:有没有自己写过自定义组件?

答:写过。我会把项目中通用的模块封装成可复用组件,比如:

  • 分页组件

  • 搜索栏组件

  • 弹窗组件

  • 公共头部/底部

封装时主要使用:

  • props 接收父组件传参

  • emit 向父组件发送事件

  • slot 支持内容定制

  • v-model 实现双向绑定

好处: 减少重复代码、统一风格、提高开发效率、便于维护。

问:自定义组件和自定义 Hooks 有什么区别?

答:两者的核心区别在于封装的内容不同:

  • 自定义组件 :封装 UI + 逻辑,有实际的界面展示,如分页组件、弹窗组件

  • 自定义 Hooks :只封装 逻辑,不包含 UI,返回数据和方法,如 useCounter、useRequest

使用场景:

  • 多个页面需要同样的界面 → 自定义组件

  • 多个组件需要同样的逻辑,但界面不同 → 自定义 Hooks

两者也可以配合使用:Hooks 为组件提供逻辑,组件负责 UI 展示。

相关推荐
用户52709648744902 小时前
微前端(qiankun)单侧启动调试技巧
前端
于慨2 小时前
flutter基础组件用法
开发语言·javascript·flutter
斌味代码2 小时前
jQuery 内存泄漏排查:常见场景、工具使用与修复实战
前端·javascript·jquery
weixin199701080162 小时前
《爱回收商品详情页前端性能优化实战》
前端·性能优化
chenbin___2 小时前
鸿蒙(HarmonyOS)支持 useNativeDriver的详细说明(转自千问)
前端·javascript·react native·react.js·harmonyos
We་ct2 小时前
Git 核心知识点全解析
开发语言·前端·git·gitee·github
iDao技术魔方2 小时前
Bun v1.3.12 深度解析:新特性、性能优化与实战指南
开发语言·javascript·visual studio code
小兵阿飞2 小时前
Vite 技术介绍:实现原理、应用与优化
前端·vite
码喽7号2 小时前
vue学习六:状态管理VueX
javascript·vue.js·学习