学习vue第十四天 小白学父组件传递子组件(Props)

用最直白、最详细的方式给你讲解父组件传递子组件(Props)!

🎁 Props:父组件给子组件送礼物

🎭 最简单的比喻

想象你是爸爸 (父组件),你的儿子(子组件)需要零花钱和玩具:

复制代码
👨 爸爸(父组件)
  ↓ 给钱、给玩具
👦 儿子(子组件)

📦 第一步:最简单的传递

父组件:给东西

vue 复制代码
<!-- App.vue(爸爸) -->
<template>
  <div>
    <!-- 🎁 给儿子传递两样东西 -->
    <Son name="小明" age="10" />
  </div>
</template>

<script>
import Son from './Son.vue'

export default {
  components: { Son }
}
</script>

子组件:收东西

vue 复制代码
<!-- Son.vue(儿子) -->
<template>
  <div>
    <h2>我叫:{{ name }}</h2>
    <p>我今年:{{ age }}岁</p>
  </div>
</template>

<script>
export default {
  // 📥 声明:我要接收name和age
  props: ['name', 'age']
}
</script>

结果:页面显示"我叫:小明,我今年:10岁"


🎯 第二步:传递动态数据

静态 vs 动态

vue 复制代码
<!-- 父组件 -->
<template>
  <div>
    <!-- ❌ 静态传递:传的是字符串"10" -->
    <Son name="小明" age="10" />
    
    <!-- ✅ 动态传递:传的是变量的值 -->
    <Son :name="userName" :age="userAge" />
    <!-- 或者写成 v-bind:name -->
    <Son v-bind:name="userName" v-bind:age="userAge" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      userName: '小明',
      userAge: 10
    }
  }
}
</script>

关键区别

  • name="小明" - 传的是字符串"小明"
  • :name="userName" - 传的是变量userName的值

🎪 第三步:传递各种类型的数据

vue 复制代码
<!-- 父组件 -->
<template>
  <div>
    <Child
      :string-data="message"
      :number-data="count"
      :boolean-data="isVIP"
      :array-data="hobbies"
      :object-data="user"
      :function-data="handleClick"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 字符串
      message: 'Hello',
      
      // 数字
      count: 100,
      
      // 布尔值
      isVIP: true,
      
      // 数组
      hobbies: ['篮球', '足球', '游泳'],
      
      // 对象
      user: {
        name: '张三',
        age: 25
      }
    }
  },
  
  methods: {
    // 函数
    handleClick() {
      console.log('点击了!')
    }
  }
}
</script>
vue 复制代码
<!-- 子组件 -->
<template>
  <div>
    <p>字符串:{{ stringData }}</p>
    <p>数字:{{ numberData }}</p>
    <p>布尔值:{{ booleanData }}</p>
    <p>数组:{{ arrayData }}</p>
    <p>对象:{{ objectData.name }}</p>
    <button @click="functionData">点击我</button>
  </div>
</template>

<script>
export default {
  props: [
    'stringData',
    'numberData', 
    'booleanData',
    'arrayData',
    'objectData',
    'functionData'
  ]
}
</script>

🔧 第四步:Props验证(推荐写法)

为什么要验证?

想象你开了个商店,顾客要买东西:

  • 不验证:顾客给你什么你都收(可能给你假钱)
  • 验证:检查钱是不是真的,数量对不对
vue 复制代码
<!-- 子组件 -->
<script>
export default {
  props: {
    // 🎯 方式1:只指定类型
    name: String,
    age: Number,
    isVIP: Boolean,
    
    // 🎯 方式2:详细配置
    title: {
      type: String,           // 类型
      required: true,         // 必须传
      default: '默认标题'      // 默认值
    },
    
    // 🎯 方式3:多种类型
    price: {
      type: [Number, String], // 可以是数字或字符串
      required: true
    },
    
    // 🎯 方式4:对象/数组默认值
    user: {
      type: Object,
      default() {             // 必须用函数返回
        return {
          name: '游客',
          age: 0
        }
      }
    },
    
    tags: {
      type: Array,
      default() {             // 必须用函数返回
        return []
      }
    },
    
    // 🎯 方式5:自定义验证
    score: {
      type: Number,
      validator(value) {
        // 分数必须在0-100之间
        return value >= 0 && value <= 100
      }
    }
  }
}
</script>

🎪 第五步:实际案例

案例1:用户卡片组件

vue 复制代码
<!-- 父组件:App.vue -->
<template>
  <div class="app">
    <!-- 显示多个用户卡片 -->
    <UserCard
      v-for="user in users"
      :key="user.id"
      :name="user.name"
      :age="user.age"
      :avatar="user.avatar"
      :is-vip="user.isVIP"
    />
  </div>
</template>

<script>
import UserCard from './UserCard.vue'

export default {
  components: { UserCard },
  data() {
    return {
      users: [
        { id: 1, name: '张三', age: 25, avatar: 'avatar1.jpg', isVIP: true },
        { id: 2, name: '李四', age: 30, avatar: 'avatar2.jpg', isVIP: false },
        { id: 3, name: '王五', age: 28, avatar: 'avatar3.jpg', isVIP: true }
      ]
    }
  }
}
</script>
vue 复制代码
<!-- 子组件:UserCard.vue -->
<template>
  <div class="user-card" :class="{ vip: isVip }">
    <img :src="avatar" :alt="name">
    <h3>{{ name }}</h3>
    <p>年龄:{{ age }}岁</p>
    <span v-if="isVip" class="vip-badge">VIP</span>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      required: true
    },
    age: {
      type: Number,
      required: true,
      validator(value) {
        return value > 0 && value < 150
      }
    },
    avatar: {
      type: String,
      default: 'default-avatar.jpg'
    },
    isVip: {
      type: Boolean,
      default: false
    }
  }
}
</script>

<style scoped>
.user-card {
  border: 1px solid #ddd;
  padding: 20px;
  margin: 10px;
}

.user-card.vip {
  border-color: gold;
  background: #fffef0;
}

.vip-badge {
  background: gold;
  padding: 2px 8px;
  border-radius: 3px;
}
</style>

🎯 第六步:Props的高级技巧

技巧1:批量传递对象属性

vue 复制代码
<!-- 父组件 -->
<template>
  <div>
    <!-- ❌ 麻烦的写法 -->
    <UserCard
      :name="user.name"
      :age="user.age"
      :email="user.email"
      :phone="user.phone"
    />
    
    <!-- ✅ 简洁的写法:v-bind批量传递 -->
    <UserCard v-bind="user" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: '张三',
        age: 25,
        email: 'zhangsan@example.com',
        phone: '13800138000'
      }
    }
  }
}
</script>

技巧2:Props命名转换

vue 复制代码
<!-- 父组件:使用kebab-case -->
<template>
  <Child
    user-name="张三"
    user-age="25"
    is-vip="true"
  />
</template>

<!-- 子组件:使用camelCase -->
<script>
export default {
  props: {
    userName: String,    // 自动转换
    userAge: Number,     // 自动转换
    isVip: Boolean       // 自动转换
  }
}
</script>

技巧3:Props默认值的妙用

vue 复制代码
<script>
export default {
  props: {
    // 带默认值的按钮
    buttonText: {
      type: String,
      default: '点击我'
    },
    
    buttonType: {
      type: String,
      default: 'primary',
      validator(value) {
        return ['primary', 'success', 'warning', 'danger'].includes(value)
      }
    },
    
    // 带默认配置的组件
    config: {
      type: Object,
      default() {
        return {
          showHeader: true,
          showFooter: true,
          pageSize: 10
        }
      }
    }
  }
}
</script>

⚠️ 重要注意事项

🚫 禁止1:不要修改Props

vue 复制代码
<script>
export default {
  props: ['count'],
  
  methods: {
    increment() {
      // ❌ 错误!不能直接修改props
      this.count++
      
      // ✅ 正确做法1:复制到本地data
      // this.localCount++
      
      // ✅ 正确做法2:通知父组件修改
      // this.$emit('update:count', this.count + 1)
    }
  }
}
</script>

🚫 禁止2:对象/数组默认值不用函数

vue 复制代码
<script>
export default {
  props: {
    // ❌ 错误!对象默认值必须用函数
    user: {
      type: Object,
      default: { name: '游客' }  // 错误!
    },
    
    // ✅ 正确!
    user: {
      type: Object,
      default() {
        return { name: '游客' }
      }
    }
  }
}
</script>

为什么? 因为对象是引用类型,不用函数的话所有组件实例会共享同一个对象!


🎪 完整实战案例:商品卡片

vue 复制代码
<!-- 父组件:ProductList.vue -->
<template>
  <div class="product-list">
    <h1>商品列表</h1>
    <ProductCard
      v-for="product in products"
      :key="product.id"
      :product="product"
      @add-to-cart="handleAddToCart"
    />
  </div>
</template>

<script>
import ProductCard from './ProductCard.vue'

export default {
  components: { ProductCard },
  
  data() {
    return {
      products: [
        {
          id: 1,
          name: 'iPhone 15',
          price: 5999,
          image: 'iphone.jpg',
          stock: 10,
          tags: ['热销', '新品']
        },
        {
          id: 2,
          name: 'MacBook Pro',
          price: 12999,
          image: 'macbook.jpg',
          stock: 5,
          tags: ['推荐']
        }
      ]
    }
  },
  
  methods: {
    handleAddToCart(product) {
      console.log('添加到购物车:', product.name)
    }
  }
}
</script>
vue 复制代码
<!-- 子组件:ProductCard.vue -->
<template>
  <div class="product-card">
    <img :src="product.image" :alt="product.name">
    
    <h3>{{ product.name }}</h3>
    
    <div class="price">
      ¥{{ product.price }}
    </div>
    
    <div class="tags">
      <span
        v-for="tag in product.tags"
        :key="tag"
        class="tag"
      >
        {{ tag }}
      </span>
    </div>
    
    <div class="stock">
      库存:{{ product.stock }}
    </div>
    
    <button
      @click="addToCart"
      :disabled="product.stock === 0"
    >
      {{ product.stock > 0 ? '加入购物车' : '已售罄' }}
    </button>
  </div>
</template>

<script>
export default {
  props: {
    product: {
      type: Object,
      required: true,
      validator(value) {
        // 验证product对象必须有这些属性
        return value.id && value.name && value.price !== undefined
      }
    }
  },
  
  methods: {
    addToCart() {
      // 通知父组件
      this.$emit('add-to-cart', this.product)
    }
  }
}
</script>

<style scoped>
.product-card {
  border: 1px solid #ddd;
  padding: 20px;
  margin: 10px;
  border-radius: 8px;
}

.price {
  color: #f40;
  font-size: 24px;
  font-weight: bold;
  margin: 10px 0;
}

.tag {
  background: #f0f0f0;
  padding: 2px 8px;
  margin-right: 5px;
  border-radius: 3px;
  font-size: 12px;
}

button {
  width: 100%;
  padding: 10px;
  background: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  background: #ccc;
  cursor: not-allowed;
}
</style>

💡 记忆口诀

父传子用Props,冒号绑定动态值
子组件声明接收,类型验证保安全
对象数组用函数,默认值要返回
Props只读不可改,要改通知父组件


🎯 总结

Props就像:

  • 🎁 父组件给子组件的礼物
  • 📦 单向数据流:只能父→子,不能子→父
  • 🔒 只读的:子组件不能修改
  • 要验证:确保数据类型正确

现在明白父组件传递子组件了吗?就是父组件通过Props给子组件传数据,子组件声明接收并使用!😊

相关推荐
qiuiuiu4132 小时前
正点原子RK3568学习日志21-实验1-字符设备点亮led
linux·学习
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习 Day 3:综合实践——多维数据流与实时交互实验室
学习·flutter·华为·交互·harmonyos·鸿蒙
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习 Day 3:工程实践——数据模型化:从黑盒 Map 走向强类型 Class
学习·flutter·ui·华为·harmonyos·鸿蒙·鸿蒙系统
Darkershadow2 小时前
蓝牙学习之Provision(7)bind (1)
学习·蓝牙·ble·mesh
web小白成长日记2 小时前
从零起步,用TypeScript写一个Todo App:踩坑与收获分享
前端·javascript·typescript
PNP Robotics2 小时前
PNP机器人分享具身操作策略和数据采集
大数据·人工智能·学习·机器人
xiaoxiaoxiaolll2 小时前
从结构到性能|《Adv. Funct. Mater.》MOF基电催化剂的设计策略与应用前沿
学习
代码游侠3 小时前
学习笔笔记——ARM 嵌入式系统与内核架构
arm开发·笔记·嵌入式硬件·学习·架构
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [driver][base]container
linux·笔记·学习
圣心3 小时前
Gemini 模型 介绍
前端