(四)从零学 React Props:数据传递 + 实战案例 + 避坑指南
一、为什么需要Props?—— 解决组件"定制化"问题
上节课我们学会了创建和使用组件,但有个明显的问题:组件内容是固定的。比如创建的UserCard
组件,无论用多少次,都只能显示"李四"的信息;Button
组件也只能显示"点击我"。
但实际开发中,组件需要根据不同场景展示不同内容:
- 按钮可能需要显示"登录"、“注册”、"提交"等不同文字;
- 用户卡片需要展示"张三"、"李四"等不同用户的信息;
- 商品卡片需要展示不同商品的名称、价格、图片。
这就需要一种方式让父组件给子组件传递数据,而Props
(全称Properties,属性)就是React专门用来解决这个问题的机制。
形象理解:Props就像组件的"参数"。比如你去咖啡店点咖啡(调用咖啡组件),需要告诉店员"要拿铁"(传递type="latte"
)、“少糖”(传递sugar="less"
),店员根据这些参数制作出你要的咖啡(组件根据Props展示内容)。
二、Props的基本使用:传递与接收
Props的使用分为两步:父组件传递数据和子组件接收并使用数据。
1. 父组件传递Props:像写HTML属性一样简单
父组件在使用子组件时,通过"属性=值"的形式传递数据,和HTML标签的属性写法类似。
例如,父组件App
要给子组件Button
传递"按钮文字"和"颜色":
// 父组件 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}}
)。
// 传递多种类型的Props
<Button text="购买" price={99} // 数字isNew={true} // 布尔值tags={["热销", "新品"]} // 数组
/>
2. 子组件接收Props:函数参数就是Props对象
函数组件通过函数的第一个参数接收父组件传递的所有Props,这个参数是一个对象,属性名就是父组件传递的属性名。
例如,Button
组件接收并使用Props:
// 子组件 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.text
、props.color
会很繁琐。可以用对象解构直接提取需要的属性,让代码更简洁。
修改Button
组件:
// 用解构赋值直接提取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
function Button({ text }) {// 尝试修改Props(会报错!)text = "新文字"; // 错误:Props是只读的,不能重新赋值return <button>{text}</button>;
}
运行后控制台会报错,提示"Assignment to constant variable"(给常量赋值),因为Props被视为不可变数据。
正确做法:如果需要修改,用子组件自己的状态
如果子组件需要修改数据,应该把数据存在子组件的"状态"(State)中,这部分我们下节课会详细讲。Props只负责"接收外部数据",不负责"存储内部可变数据"。
四、Props默认值:给组件设置"默认参数"
如果父组件使用子组件时没有传递某个Props,子组件可以设置默认值,避免出现undefined
。
设置默认值有两种方式:
1. 函数参数默认值(推荐,简单直观)
直接在解构赋值时给属性设置默认值:
// 给color设置默认值"gray",text设置默认值"按钮"
function Button({ text = "按钮", color = "gray" }) {return (<button style={{ backgroundColor: color }}>{text}</button>);
}
当父组件不传递Props时,会使用默认值:
// 父组件中不传递text和color
<Button /> // 会显示灰色背景的"按钮"
2. 组件的defaultProps属性(了解即可)
这是另一种设置默认值的方式,通过组件的defaultProps
属性:
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
在终端中进入项目目录,执行安装命令:
npm install prop-types --save
步骤2:在组件中使用类型检查
以Button
组件为例,限制text
是字符串,color
是字符串,price
是数字:
import PropTypes from 'prop-types'; // 导入PropTypesfunction 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
)
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)
// 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)
// 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
// src/App.js
import UserList from './UserList';function App() {return (<div style={{ padding: '20px' }}><h1>用户列表</h1><UserList /></div>);
}export default App;
运行效果:
页面会显示3个用户卡片,每个卡片展示不同用户的信息(姓名、头像、年龄等)。通过Props,我们实现了UserCard
组件的复用,只写一次组件,就能展示不同数据。
七、常见问题与解决办法
-
Props传递后显示undefined
可能原因:- 父组件传递的属性名和子组件接收的不一致(比如父传
username
,子取name
); - 子组件没正确解构Props(比如写成
{ userName }
但实际属性是userName
)。
解决:检查属性名拼写,用console.log(props)
在子组件中打印Props,确认数据是否传递成功。
- 父组件传递的属性名和子组件接收的不一致(比如父传
-
传递数字/布尔值时显示错误
错误写法:<Button count="5" />
(会被当作字符串"5")
解决:用{}
包裹非字符串类型:<Button count={5} isActive={true} />
-
忘记安装prop-types导致报错
报错:Cannot find module 'prop-types'
解决:在终端执行npm install prop-types --save
安装依赖。