Files
home/docs/vue/component.md
2026-02-21 15:39:27 +08:00

3.5 KiB

组件开发

组件基础

定义组件

<!-- MyButton.vue -->
<template>
  <button :class="['btn', `btn-${type}`]" @click="handleClick">
    <slot></slot>
  </button>
</template>

<script setup>
const props = defineProps({
  type: {
    type: String,
    default: 'default',
    validator: (value) => ['default', 'primary', 'danger'].includes(value)
  }
})

const emit = defineEmits(['click'])

function handleClick(event) {
  emit('click', event)
}
</script>

<style scoped>
.btn {
  padding: 8px 16px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
}

.btn-default {
  background: #f0f0f0;
  color: #333;
}

.btn-primary {
  background: #42b883;
  color: white;
}

.btn-danger {
  background: #ff4d4f;
  color: white;
}
</style>

使用组件

<template>
  <MyButton type="primary" @click="handleButtonClick">
    点击我
  </MyButton>
</template>

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

function handleButtonClick() {
  console.log('按钮被点击')
}
</script>

Props

Props声明

<script setup>
// 简单声明
const props = defineProps(['title', 'content'])

// 带类型的声明
const props = defineProps({
  title: String,
  content: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  },
  items: {
    type: Array,
    default: () => []
  }
})
</script>

Props验证

<script setup>
const props = defineProps({
  status: {
    type: String,
    required: true,
    validator: (value) => ['active', 'inactive', 'pending'].includes(value)
  },
  callback: {
    type: Function,
    default: () => {}
  }
})
</script>

事件

定义事件

<script setup>
const emit = defineEmits(['update', 'delete', 'change'])

function handleUpdate() {
  emit('update', { id: 1, name: '更新数据' })
}

function handleDelete(id) {
  emit('delete', id)
}
</script>

v-model

<!-- CustomInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  >
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<!-- 使用 -->
<CustomInput v-model="text" />

插槽

默认插槽

<!-- Card.vue -->
<template>
  <div class="card">
    <slot></slot>
  </div>
</template>

<!-- 使用 -->
<Card>
  <h2>标题</h2>
  <p>内容</p>
</Card>

具名插槽

<!-- Layout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- 使用 -->
<Layout>
  <template #header>
    <h1>页面标题</h1>
  </template>
  
  <p>主要内容</p>
  
  <template #footer>
    <p>页脚信息</p>
  </template>
</Layout>

作用域插槽

<!-- List.vue -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>

<script setup>
defineProps(['items'])
</script>

<!-- 使用 -->
<List :items="users">
  <template #default="{ item, index }">
    <span>{{ index }}. {{ item.name }}</span>
  </template>
</List>

生命周期

<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue'

onMounted(() => {
  console.log('组件已挂载')
})

onUpdated(() => {
  console.log('组件已更新')
})

onUnmounted(() => {
  console.log('组件已卸载')
})
</script>