1. Vue3 组件定义与 Props 父子传值

1.1 Props:父组件向子组件传递数据

Props 是父组件向子组件传递数据的单向数据流。

父组件传递数据
<!-- ParentComponent.vue -->
<template>
  <div>
    <h2>父组件</h2>
    <!-- 使用 v-bind 或 : 传递数据 -->
    <ChildComponent :message="parentMessage" />
  </div>
</template>

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

const parentMessage = ref('这是来自父组件的问候!')
</script>
Props 的类型与验证

可以为 Props 指定类型并进行验证,提高代码健壮性。

const props = defineProps({
  // 基础类型检查
  title: String,
  
  // 多个可能的类型
  value: [String, Number],
  
  // 必填项
  userId: {
    type: Number,
    required: true
  },
  
  // 带默认值
  size: {
    type: String,
    default: 'medium'
  },
  
  // 自定义验证函数
  score: {
    type: Number,
    validator: (value) => {
      return value >= 0 && value <= 100
    }
  }
})

1.2 使用 Props 的注意事项

  1. 单向数据流:Props 是只读的,子组件不应直接修改。
  2. 动态与静态传递:
    • 静态:<Child title="静态标题" />
    • 动态:<Child :title="dynamicTitle" />

2. 掌握 defineEmits 子向父传值

2.1 使用 defineEmits 定义自定义事件

子组件通过触发事件向父组件通信。

<!-- ChildComponent.vue -->
<template>
  <div>
    <h3>子组件</h3>
    <button @click="sendMessage">向父组件发送消息</button>
  </div>
</template>

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

// 定义可触发的事件
const emit = defineEmits(['message-sent', 'update:count'])

const sendMessage = () => {
  // 触发事件并传递数据
  emit('message-sent', '你好,父组件!')
  
  // 触发更新事件(常用于 v-model 双向绑定)
  emit('update:count', 10)
}
</script>

2.2 父组件监听子组件事件

<!-- ParentComponent.vue -->
<template>
  <div>
    <h2>父组件</h2>
    <p>收到子组件消息:{{ receivedMessage }}</p>
    <p>当前计数:{{ count }}</p>
    
    <!-- 监听子组件事件 -->
    <ChildComponent 
      @message-sent="handleMessage" 
      @update:count="count = $event"
    />
  </div>
</template>

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

const receivedMessage = ref('')
const count = ref(0)

const handleMessage = (msg) => {
  receivedMessage.value = msg
  console.log('收到子组件消息:', msg)
}
</script>

2.3 事件验证与类型提示

可以为事件添加验证和类型提示(TypeScript)。

// 类型提示
const emit = defineEmits<{
  (e: 'message-sent', message: string): void
  (e: 'update:count', value: number): void
}>()

// 带验证的事件定义
const emit = defineEmits({
  'message-sent': (payload: string) => {
    // 返回 true 表示验证通过
    return payload.length > 0
  }
})

3. 学习组件生命周期(onMounted 等)

3.1 Vue3 生命周期钩子

在组合式 API 中,生命周期钩子以 onXxx 函数形式导入使用。

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

// 组件挂载后执行
onMounted(() => {
  console.log('组件已挂载到 DOM')
  // 可以在这里进行 DOM 操作、发起网络请求等
})

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

// 组件卸载前执行
onUnmounted(() => {
  console.log('组件即将卸载')
  // 清理定时器、取消事件监听等
})

// 组件挂载前执行
onBeforeMount(() => {
  console.log('组件即将挂载')
})
</script>

3.2 完整生命周期流程图

是

否

是

否

开始

setup

onBeforeMount

onMounted

数据更新?

onBeforeUpdate

onUpdated

组件卸载?

onBeforeUnmount

onUnmounted

结束

3.3 常见使用场景

生命周期钩子使用场景
onMountedDOM 操作、数据初始化、事件监听、API 请求
onUpdatedDOM 更新后的操作、依赖更新的计算
onUnmounted清理定时器、取消事件监听、释放资源
onBeforeMount服务器端渲染、最后时刻的状态修改

4. 练习简单组件拆分与复用

4.1 组件拆分原则

  1. 单一职责:每个组件只负责一个特定功能
  2. 高内聚低耦合:组件内部紧密相关,组件之间依赖明确
  3. 可复用性:设计通用的、可配置的组件
  4. 清晰的接口:Props 和 Emits 定义明确

4.2 实战:按钮组件

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

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  type: {
    type: String,
    default: 'primary',
    validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
  },
  text: {
    type: String,
    default: '按钮'
  },
  disabled: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['click'])

const handleClick = () => {
  if (!props.disabled) {
    emit('click')
  }
}
</script>

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

.btn-primary {
  background-color: #007bff;
  color: white;
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

.btn-danger {
  background-color: #dc3545;
  color: white;
}

.btn-disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
</style>

4.3 使用示例

<!-- App.vue -->
<template>
  <div>
    <h1>按钮组件示例</h1>
    
    <ReusableButton 
      type="primary" 
      text="主要按钮" 
      @click="handlePrimaryClick"
    />
    
    <ReusableButton 
      type="secondary" 
      @click="handleSecondaryClick"
    >
      自定义内容
    </ReusableButton>
    
    <ReusableButton 
      type="danger" 
      text="危险按钮" 
      :disabled="true"
    />
  </div>
</template>

<script setup>
import ReusableButton from './components/ReusableButton.vue'

const handlePrimaryClick = () => {
  console.log('主要按钮被点击')
}

const handleSecondaryClick = () => {
  console.log('次要按钮被点击')
}
</script>

总结:Vue3 的组件化开发通过清晰的 Props 和 Emits 接口实现了父子组件通信,组合式 API 让逻辑组织更加灵活。掌握这些核心概念后,可以构建出可维护、可复用的前端应用。建议在实际项目中多加练习,不断优化组件设计。

更多推荐