(四)从零学 React Props:数据传递 + 实战案例 + 避坑指南

一、为什么需要Props?------ 解决组件"定制化"问题

上节课我们学会了创建和使用组件,但有个明显的问题:组件内容是固定的。比如创建的UserCard组件,无论用多少次,都只能显示"李四"的信息;Button组件也只能显示"点击我"。

但实际开发中,组件需要根据不同场景展示不同内容:

  • 按钮可能需要显示"登录"、"注册"、"提交"等不同文字;
  • 用户卡片需要展示"张三"、"李四"等不同用户的信息;
  • 商品卡片需要展示不同商品的名称、价格、图片。

这就需要一种方式让父组件给子组件传递数据 ,而Props(全称Properties,属性)就是React专门用来解决这个问题的机制。

形象理解 :Props就像组件的"参数"。比如你去咖啡店点咖啡(调用咖啡组件),需要告诉店员"要拿铁"(传递type="latte")、"少糖"(传递sugar="less"),店员根据这些参数制作出你要的咖啡(组件根据Props展示内容)。

二、Props的基本使用:传递与接收

Props的使用分为两步:父组件传递数据子组件接收并使用数据

1. 父组件传递Props:像写HTML属性一样简单

父组件在使用子组件时,通过"属性=值"的形式传递数据,和HTML标签的属性写法类似。

例如,父组件App要给子组件Button传递"按钮文字"和"颜色":

jsx 复制代码
// 父组件 App.js
import Button from './Button';

function App() {
  return (
    <div>
      {/* 传递Props:text是属性名,"登录"是属性值 */}
      <Button text="登录" color="blue" />
      
      {/* 再传一组不同的Props */}
      <Button text="注册" color="green" />
    </div>
  );
}

export default App;
传递数据的类型:

Props可以传递任意类型的数据,包括:

  • 字符串(直接写,如text="登录");
  • 数字(需要用{}包裹,如count={5});
  • 布尔值(用{},如disabled={true});
  • 数组(用{},如list={[1,2,3]});
  • 对象(用{},如user={``{name: "张三", age: 20}})。
jsx 复制代码
// 传递多种类型的Props
<Button 
  text="购买" 
  price={99}  // 数字
  isNew={true}  // 布尔值
  tags={["热销", "新品"]}  // 数组
/>

2. 子组件接收Props:函数参数就是Props对象

函数组件通过函数的第一个参数接收父组件传递的所有Props,这个参数是一个对象,属性名就是父组件传递的属性名。

例如,Button组件接收并使用Props:

jsx 复制代码
// 子组件 Button.js
// props是一个对象,包含父组件传递的所有属性(text、color等)
function Button(props) {
  console.log(props); // 打印看看Props内容:{ text: "登录", color: "blue" }(第一次渲染时)
  
  return (
    <button 
      style={{ 
        backgroundColor: props.color,  // 使用color属性
        color: "white", 
        padding: "8px 16px",
        border: "none",
        borderRadius: "4px",
        margin: "0 8px"
      }}
    >
      {props.text}  {/* 使用text属性 */}
    </button>
  );
}

export default Button;

运行后,页面会显示两个按钮:

  • 蓝色背景的"登录"按钮;
  • 绿色背景的"注册"按钮。
    这就是Props的作用:同一个组件,通过不同的Props展示不同内容。

3. 简化Props接收:解构赋值

如果Props属性很多,每次写props.textprops.color会很繁琐。可以用对象解构直接提取需要的属性,让代码更简洁。

修改Button组件:

jsx 复制代码
// 用解构赋值直接提取text和color
function Button({ text, color }) {
  return (
    <button 
      style={{ 
        backgroundColor: color,  // 直接用color
        // 其他样式...
      }}
    >
      {text}  {/* 直接用text */}
    </button>
  );
}

效果和之前完全一样,但代码更简洁。这是React开发中最常用的写法,一定要掌握。

三、Props的核心特性:只读性(不可修改!)

这是React的重要原则:子组件不能修改接收到的Props,Props是"只读的"(read-only)。

为什么Props不能修改?

  • Props的数据来源是父组件,修改Props会导致数据流向混乱(父组件和子组件都可能改数据,难以追踪);
  • React通过"单向数据流"(父→子)保证数据可预测性,方便调试和维护。

错误示例:尝试修改Props

jsx 复制代码
function Button({ text }) {
  // 尝试修改Props(会报错!)
  text = "新文字";  // 错误:Props是只读的,不能重新赋值
  
  return <button>{text}</button>;
}

运行后控制台会报错,提示"Assignment to constant variable"(给常量赋值),因为Props被视为不可变数据。

正确做法:如果需要修改,用子组件自己的状态

如果子组件需要修改数据,应该把数据存在子组件的"状态"(State)中,这部分我们下节课会详细讲。Props只负责"接收外部数据",不负责"存储内部可变数据"。

四、Props默认值:给组件设置"默认参数"

如果父组件使用子组件时没有传递某个Props,子组件可以设置默认值,避免出现undefined

设置默认值有两种方式:

1. 函数参数默认值(推荐,简单直观)

直接在解构赋值时给属性设置默认值:

jsx 复制代码
// 给color设置默认值"gray",text设置默认值"按钮"
function Button({ text = "按钮", color = "gray" }) {
  return (
    <button style={{ backgroundColor: color }}>
      {text}
    </button>
  );
}

当父组件不传递Props时,会使用默认值:

jsx 复制代码
// 父组件中不传递text和color
<Button />  // 会显示灰色背景的"按钮"

2. 组件的defaultProps属性(了解即可)

这是另一种设置默认值的方式,通过组件的defaultProps属性:

jsx 复制代码
function Button({ text, color }) {
  return (
    <button style={{ backgroundColor: color }}>
      {text}
    </button>
  );
}

// 设置默认Props
Button.defaultProps = {
  text: "按钮",
  color: "gray"
};

效果和函数参数默认值一样,但现在更推荐用第一种方式(更简洁,符合ES6语法)。

五、Props类型检查:让组件更健壮(基础版)

大型项目中,为了避免传递错误类型的Props(比如应该传数字却传了字符串),可以给Props做类型检查。React推荐用prop-types库来实现。

步骤1:安装prop-types

在终端中进入项目目录,执行安装命令:

bash 复制代码
npm install prop-types --save

步骤2:在组件中使用类型检查

Button组件为例,限制text是字符串,color是字符串,price是数字:

jsx 复制代码
import PropTypes from 'prop-types';  // 导入PropTypes

function Button({ text, color, price }) {
  return (
    <button style={{ backgroundColor: color }}>
      {text} {price && `¥${price}`}
    </button>
  );
}

// 定义Props类型检查规则
Button.propTypes = {
  text: PropTypes.string,  // text必须是字符串
  color: PropTypes.string, // color必须是字符串
  price: PropTypes.number  // price必须是数字
};

export default Button;

如果父组件传递错误类型(比如price="99",字符串),控制台会警告:
Warning: Failed prop type: Invalid prop 'price' of type 'string' supplied to 'Button', expected 'number'.

常用类型检查规则:

  • PropTypes.string:字符串
  • PropTypes.number:数字
  • PropTypes.bool:布尔值
  • PropTypes.array:数组
  • PropTypes.object:对象
  • PropTypes.func:函数
  • PropTypes.node:可以渲染的内容(数字、字符串、元素等)
  • PropTypes.isRequired:必须传递的属性(在类型后加,如PropTypes.string.isRequired
jsx 复制代码
Button.propTypes = {
  text: PropTypes.string.isRequired,  // text必须传递,且是字符串
  price: PropTypes.number  // price可选,但如果传必须是数字
};

如果父组件没传递text(加了isRequired),会警告:Warning: Failed prop type: The prop 'text' is marked as required in 'Button', but its value is 'undefined'.

六、综合案例:用户列表组件(复用+Props传递)

我们来创建一个UserList父组件,传递多个用户数据给UserCard子组件,展示用户列表。

步骤1:改造UserCard组件(接收Props)

jsx 复制代码
// src/UserCard.js
import PropTypes from 'prop-types';

// 接收用户数据作为Props
function UserCard({ user }) {
  return (
    <div style={{ 
      border: '1px solid #ddd', 
      padding: '16px', 
      borderRadius: '8px', 
      margin: '8px',
      maxWidth: '200px'
    }}>
      <img 
        src={user.avatar} 
        alt={user.name} 
        style={{ width: '80px', borderRadius: '50%' }}
      />
      <h3>{user.name}</h3>
      <p>{user.age}岁 | {user.gender}</p>
      <p>{user.bio}</p>
    </div>
  );
}

// 类型检查
UserCard.propTypes = {
  user: PropTypes.shape({  // 检查对象内部属性
    name: PropTypes.string.isRequired,
    avatar: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired,
    gender: PropTypes.string,
    bio: PropTypes.string
  }).isRequired  // user必须传递
};

export default UserCard;

步骤2:创建UserList组件(传递Props)

jsx 复制代码
// src/UserList.js
import UserCard from './UserCard';

function UserList() {
  // 模拟用户数据(实际项目可能来自接口)
  const users = [
    {
      name: "张三",
      avatar: "https://picsum.photos/id/1/200",
      age: 25,
      gender: "男",
      bio: "前端工程师"
    },
    {
      name: "李四",
      avatar: "https://picsum.photos/id/2/200",
      age: 23,
      gender: "女",
      bio: "UI设计师"
    },
    {
      name: "王五",
      avatar: "https://picsum.photos/id/3/200",
      age: 28,
      gender: "男",
      bio: "产品经理"
    }
  ];

  return (
    <div style={{ display: 'flex', flexWrap: 'wrap' }}>
      {/* 遍历用户数据,给每个UserCard传递不同的user */}
      {users.map((user, index) => (
        <UserCard key={index} user={user} />  {/* 传递user对象作为Props */}
      ))}
    </div>
  );
}

export default UserList;

步骤3:在App中使用UserList

jsx 复制代码
// src/App.js
import UserList from './UserList';

function App() {
  return (
    <div style={{ padding: '20px' }}>
      <h1>用户列表</h1>
      <UserList />
    </div>
  );
}

export default App;

运行效果:

页面会显示3个用户卡片,每个卡片展示不同用户的信息(姓名、头像、年龄等)。通过Props,我们实现了UserCard组件的复用,只写一次组件,就能展示不同数据。

七、常见问题与解决办法

  1. Props传递后显示undefined

    可能原因:

    • 父组件传递的属性名和子组件接收的不一致(比如父传username,子取name);
    • 子组件没正确解构Props(比如写成{ userName }但实际属性是userName)。
      解决:检查属性名拼写,用console.log(props)在子组件中打印Props,确认数据是否传递成功。
  2. 传递数字/布尔值时显示错误

    错误写法:<Button count="5" />(会被当作字符串"5")

    解决:用{}包裹非字符串类型:<Button count={5} isActive={true} />

  3. 忘记安装prop-types导致报错

    报错:Cannot find module 'prop-types'

    解决:在终端执行npm install prop-types --save安装依赖。

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax