← 返回主页

第6课: 组件基础

什么是组件?

组件是Vue应用的基本构建块,允许我们将UI拆分成独立、可复用的部分。每个组件都有自己的模板、逻辑和样式。

定义组件

单文件组件(SFC)

<!-- MyButton.vue -->
<template>
  <button @click="count++">
    点击了 {{ count }} 次
  </button>
</template>

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

const count = ref(0)
</script>

<style scoped>
button {
  background: #42b883;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

Options API写法

<!-- MyButton.vue -->
<template>
  <button @click="count++">
    点击了 {{ count }} 次
  </button>
</template>

<script>
export default {
  name: 'MyButton',
  data() {
    return {
      count: 0
    }
  }
}
</script>

使用组件

<!-- App.vue -->
<template>
  <div>
    <h1>我的应用</h1>
    <MyButton />
    <MyButton />
    <MyButton />
  </div>
</template>

<script setup>
import MyButton from './components/MyButton.vue'
</script>
注意:每个组件实例都维护自己的状态,互不影响。

Props传递数据

Props用于父组件向子组件传递数据。

Composition API写法

<!-- BlogPost.vue -->
<template>
  <div class="blog-post">
    <h3>{{ title }}</h3>
    <p>{{ content }}</p>
    <span>作者: {{ author }}</span>
  </div>
</template>

<script setup>
// 定义props
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  content: {
    type: String,
    default: ''
  },
  author: {
    type: String,
    default: '匿名'
  }
})
</script>

使用Props

<!-- App.vue -->
<template>
  <div>
    <BlogPost
      title="Vue3学习笔记"
      content="Vue3是一个很棒的框架"
      author="张三"
    />

    <BlogPost
      v-for="post in posts"
      :key="post.id"
      :title="post.title"
      :content="post.content"
      :author="post.author"
    />
  </div>
</template>

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

const posts = ref([
  { id: 1, title: '文章1', content: '内容1', author: '作者1' },
  { id: 2, title: '文章2', content: '内容2', author: '作者2' }
])
</script>

Props验证

<script setup>
defineProps({
  // 基础类型检查
  title: String,
  likes: Number,
  isPublished: Boolean,
  tags: Array,
  author: Object,
  callback: Function,

  // 多种可能的类型
  propA: [String, Number],

  // 必填的字符串
  propB: {
    type: String,
    required: true
  },

  // 带有默认值的数字
  propC: {
    type: Number,
    default: 100
  },

  // 带有默认值的对象
  propD: {
    type: Object,
    default: () => ({ message: 'hello' })
  },

  // 自定义验证函数
  propE: {
    validator: (value) => {
      return ['success', 'warning', 'danger'].includes(value)
    }
  }
})
</script>

触发事件(Emits)

子组件通过emit向父组件发送事件。

子组件

<!-- Counter.vue -->
<template>
  <div>
    <button @click="increment">增加</button>
    <p>{{ count }}</p>
  </div>
</template>

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

const count = ref(0)

// 定义emits
const emit = defineEmits(['increment', 'change'])

function increment() {
  count.value++
  emit('increment', count.value)
  emit('change', { count: count.value, timestamp: Date.now() })
}
</script>

父组件

<!-- App.vue -->
<template>
  <div>
    <Counter
      @increment="handleIncrement"
      @change="handleChange"
    />
    <p>父组件接收到的值: {{ parentCount }}</p>
  </div>
</template>

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

const parentCount = ref(0)

function handleIncrement(value) {
  parentCount.value = value
  console.log('计数增加到:', value)
}

function handleChange(data) {
  console.log('变化数据:', data)
}
</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>
<!-- 使用 -->
<template>
  <CustomInput v-model="message" />
  <p>{{ message }}</p>
</template>

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

const message = ref('')
</script>

组件注册

全局注册

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import MyButton from './components/MyButton.vue'

const app = createApp(App)

// 全局注册
app.component('MyButton', MyButton)

app.mount('#app')

局部注册

<script setup>
import MyButton from './components/MyButton.vue'
// 导入后自动注册,可直接使用
</script>

实战示例:用户卡片组件

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <img :src="avatar" :alt="name">
    <h3>{{ name }}</h3>
    <p>{{ bio }}</p>
    <button @click="handleFollow">
      {{ isFollowing ? '已关注' : '关注' }}
    </button>
  </div>
</template>

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

const props = defineProps({
  name: {
    type: String,
    required: true
  },
  avatar: {
    type: String,
    required: true
  },
  bio: {
    type: String,
    default: ''
  }
})

const emit = defineEmits(['follow', 'unfollow'])

const isFollowing = ref(false)

function handleFollow() {
  isFollowing.value = !isFollowing.value
  emit(isFollowing.value ? 'follow' : 'unfollow', props.name)
}
</script>

<style scoped>
.user-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  text-align: center;
}

img {
  width: 100px;
  height: 100px;
  border-radius: 50%;
}
</style>

练习

  1. 创建一个按钮组件,支持不同的颜色和大小props
  2. 创建一个输入框组件,实现v-model双向绑定
  3. 创建一个商品卡片组件,包含图片、标题、价格和购买按钮
  4. 创建一个评分组件,可以显示和修改星级评分