01-React基础入门——03-组件与Props
·

组件与Props
一、组件是什么?
1.1 5W1H分析
/**
* 5W1H 分析:组件与Props
*
* What: 组件是 React 的最小复用单元,Props 是传递给组件的数据
* Why: 组件化开发提高代码复用性和可维护性
* Who: 所有 React 开发者
* When: 构建 UI 时,将界面拆分为独立、可复用的部分
* Where: 函数组件、类组件(推荐函数组件)
* How: 定义组件函数,通过参数接收 props,返回 JSX
*/
console.log("=" .repeat(60));
console.log("React 19 学习路线 - 第3篇:组件与Props");
console.log("=" .repeat(60));
// 组件示例
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用组件
const element = <Welcome name="React Learner" />;
1.2 组件类型对比
/**
* 函数组件 vs 类组件
*/
// 1. 函数组件(推荐 - React 16.8+)
function FunctionalComponent({ name, age }) {
const [count, setCount] = useState(0);
return (
<div className="functional">
<h2>函数组件</h2>
<p>姓名: {name}, 年龄: {age}</p>
<button onClick={() => setCount(c => c + 1)}>
点击次数: {count}
</button>
</div>
);
}
// 2. 类组件(传统方式)
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div className="class-component">
<h2>类组件</h2>
<p>姓名: {this.props.name}, 年龄: {this.props.age}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
点击次数: {this.state.count}
</button>
</div>
);
}
}
// 3. 函数组件 vs 类组件对比
const comparison = {
函数组件: {
语法: "更简洁",
this绑定: "不需要",
生命周期: "useEffect",
状态管理: "useState",
性能: "更优",
学习曲线: "平缓"
},
类组件: {
语法: "较复杂",
this绑定: "需要",
生命周期: "componentDidMount等",
状态管理: "this.state",
性能: "稍差",
学习曲线: "陡峭"
}
};
二、函数组件详解
2.1 基础函数组件
// 1. 基本定义
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 2. 箭头函数定义
const GreetingArrow = (props) => {
return <h1>Hello, {props.name}!</h1>;
};
// 3. 隐式返回(适用于简单组件)
const GreetingImplicit = (props) => <h1>Hello, {props.name}!</h1>;
// 4. 解构props
function GreetingDestructured({ name, age, city }) {
return (
<div>
<h2>你好,{name}!</h2>
<p>年龄: {age}岁</p>
<p>城市: {city}</p>
</div>
);
}
// 5. 带默认值的解构
function GreetingWithDefaults({ name = "游客", age = 18, city = "未知" }) {
return (
<div>
<h2>你好,{name}!</h2>
<p>年龄: {age}岁</p>
<p>城市: {city}</p>
</div>
);
}
// 6. 带children的组件
function Card({ title, children, footer }) {
return (
<div className="card">
<div className="card-header">{title}</div>
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
// 使用示例
function App() {
return (
<div>
<Greeting name="React学习者" />
<GreetingDestructured name="张三" age={25} city="北京" />
<GreetingWithDefaults />
<Card title="卡片标题">
<p>这是卡片的内容区域</p>
<button>操作按钮</button>
</Card>
</div>
);
}
2.2 组件命名规范
/**
* 组件命名规范
* 1. 使用 PascalCase(大驼峰)命名
* 2. 组件名应具有描述性
* 3. 文件名与组件名保持一致
*/
// ✅ 正确:PascalCase
function UserProfile() { return <div>用户资料</div>; }
function ShoppingCart() { return <div>购物车</div>; }
function ButtonGroup() { return <div>按钮组</div>; }
// ❌ 错误:小驼峰或小写
// function userProfile() { ... }
// function shoppingcart() { ... }
// 目录结构建议
// src/
// components/
// common/
// Button.jsx
// Input.jsx
// Card.jsx
// user/
// UserProfile.jsx
// UserAvatar.jsx
// product/
// ProductList.jsx
// ProductCard.jsx
// 默认导出 vs 命名导出
// 默认导出(推荐用于主要组件)
export default function UserProfile() { ... }
// 命名导出(推荐用于工具组件或需要多个导出的文件)
export const Button = () => { ... };
export const Input = () => { ... };
三、Props详解
3.1 Props传递
/**
* Props 传递方式
*/
// 1. 基础传递
function UserCard({ user }) {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
// 2. 多层传递(Props Drilling)
function GrandParent() {
const user = { name: '张三', age: 25 };
return <Parent user={user} />;
}
function Parent({ user }) {
return <Child user={user} />;
}
function Child({ user }) {
return <div>{user.name}</div>;
}
// 3. 展开运算符传递
function Button({ type, size, disabled, children, onClick }) {
// 方式1:逐个传递
return (
<button
type={type}
className={`btn btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// 方式2:使用展开运算符
function ButtonSpread(props) {
const { children, ...restProps } = props;
return <button {...restProps}>{children}</button>;
}
// 使用示例
<ButtonSpread
type="submit"
className="btn-primary"
disabled={false}
onClick={() => console.log('clicked')}
>
提交
</ButtonSpread>
// 4. 传递组件作为Props
function Layout({ header, sidebar, content, footer }) {
return (
<div className="layout">
<header>{header}</header>
<div className="layout-main">
<aside>{sidebar}</aside>
<main>{content}</main>
</div>
<footer>{footer}</footer>
</div>
);
}
// 使用
<Layout
header={<Header />}
sidebar={<Sidebar />}
content={<MainContent />}
footer={<Footer />}
/>
3.2 Props类型检查
import PropTypes from 'prop-types';
/**
* PropTypes 类型检查
*/
function UserProfile({ name, age, email, isActive, role, friends, settings }) {
return (
<div className="user-profile">
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
<p>状态: {isActive ? '活跃' : '离线'}</p>
<p>角色: {role}</p>
<p>好友数: {friends.length}</p>
</div>
);
}
// 定义 Props 类型
UserProfile.propTypes = {
// 基本类型
name: PropTypes.string.isRequired, // 必填字符串
age: PropTypes.number, // 可选数字
email: PropTypes.string, // 可选字符串
isActive: PropTypes.bool, // 可选布尔值
// 联合类型
role: PropTypes.oneOf(['admin', 'user', 'guest']), // 枚举值
// 数组和对象
friends: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired
})
),
// 对象
settings: PropTypes.shape({
theme: PropTypes.string,
notifications: PropTypes.bool
}),
// 自定义验证
customProp: function(props, propName, componentName) {
if (!/^[0-9]+$/.test(props[propName])) {
return new Error(`Invalid prop ${propName} supplied to ${componentName}`);
}
return null;
}
};
// 默认 Props 值
UserProfile.defaultProps = {
age: 18,
isActive: false,
role: 'guest',
friends: [],
settings: { theme: 'light', notifications: true }
};
// TypeScript 版本(推荐)
interface UserProfileProps {
name: string;
age?: number;
email: string;
isActive?: boolean;
role: 'admin' | 'user' | 'guest';
friends: Array<{ id: number; name: string }>;
settings?: {
theme: string;
notifications: boolean;
};
}
const UserProfileTS: React.FC<UserProfileProps> = ({
name,
age = 18,
email,
isActive = false,
role,
friends,
settings = { theme: 'light', notifications: true }
}) => {
return <div>...</div>;
};
3.3 Children属性
/**
* children 属性详解
*/
// 1. 基础 children
function Container({ children }) {
return <div className="container">{children}</div>;
}
// 2. 多个 children
function SplitPane({ left, right }) {
return (
<div className="split-pane">
<div className="left-pane">{left}</div>
<div className="right-pane">{right}</div>
</div>
);
}
// 3. 函数作为 children(Render Props)
function DataFetcher({ url, children }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return children({ data, loading, error });
}
// 使用 Render Props
<DataFetcher url="/api/users">
{({ data, loading, error }) => {
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <UserList users={data} />;
}}
</DataFetcher>
// 4. 验证 children 类型
function List({ children }) {
// 确保只有一个子元素
const child = React.Children.only(children);
// 遍历 children
const items = React.Children.map(children, (child, index) => {
return React.cloneElement(child, { key: index });
});
// 计数 children
const count = React.Children.count(children);
// 转换为数组
const array = React.Children.toArray(children);
return <ul>{items}</ul>;
}
// 5. children 类型检查
List.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired
};
四、组件组合模式
4.1 包含关系
/**
* 组件组合模式
*/
// 1. 卡片组件(包含关系)
function Card({ title, children, actions }) {
return (
<div className="card">
{title && <div className="card-header">{title}</div>}
<div className="card-body">{children}</div>
{actions && <div className="card-actions">{actions}</div>}
</div>
);
}
// 使用
<Card
title={<h2>用户资料</h2>}
actions={
<>
<button>编辑</button>
<button>删除</button>
</>
}
>
<p>姓名: 张三</p>
<p>邮箱: zhangsan@example.com</p>
</Card>
// 2. 模态框组件
function Modal({ isOpen, onClose, title, children, footer }) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<h3>{title}</h3>
<button className="modal-close" onClick={onClose}>×</button>
</div>
<div className="modal-body">{children}</div>
{footer && <div className="modal-footer">{footer}</div>}
</div>
</div>
);
}
// 3. 选项卡组件
function Tabs({ children }) {
const [activeIndex, setActiveIndex] = useState(0);
const titles = React.Children.map(children, (child, index) => (
<button
key={index}
className={`tab-title ${activeIndex === index ? 'active' : ''}`}
onClick={() => setActiveIndex(index)}
>
{child.props.title}
</button>
));
const activeContent = React.Children.toArray(children)[activeIndex];
return (
<div className="tabs">
<div className="tab-titles">{titles}</div>
<div className="tab-content">{activeContent}</div>
</div>
);
}
function TabPane({ title, children }) {
return <div className="tab-pane">{children}</div>;
}
// 使用
<Tabs>
<TabPane title="标签1">
<p>标签1的内容</p>
</TabPane>
<TabPane title="标签2">
<p>标签2的内容</p>
</TabPane>
<TabPane title="标签3">
<p>标签3的内容</p>
</TabPane>
</Tabs>
4.2 特化关系
/**
* 特化关系(特定配置的组件)
*/
// 1. 基础按钮组件
function Button({ variant, size, children, ...props }) {
const className = `btn btn-${variant} btn-${size}`;
return (
<button className={className} {...props}>
{children}
</button>
);
}
// 2. 特化组件
function PrimaryButton(props) {
return <Button variant="primary" {...props} />;
}
function DangerButton(props) {
return <Button variant="danger" {...props} />;
}
function LargeButton(props) {
return <Button size="large" {...props} />;
}
function SmallButton(props) {
return <Button size="small" {...props} />;
}
// 3. 图标按钮
function IconButton({ icon, children, ...props }) {
return (
<Button {...props}>
<span className="icon">{icon}</span>
{children}
</Button>
);
}
// 4. 确认按钮(带确认对话框)
function ConfirmButton({ onConfirm, message, children, ...props }) {
const handleClick = () => {
if (window.confirm(message || '确定要执行此操作吗?')) {
onConfirm();
}
};
return (
<Button onClick={handleClick} {...props}>
{children}
</Button>
);
}
五、高阶组件(HOC)
5.1 HOC基础
/**
* 高阶组件(Higher-Order Component)
* 是一个函数,接收组件作为参数,返回新组件
*/
// 1. 基础 HOC - 添加日志
function withLogging(WrappedComponent) {
return function WithLogging(props) {
useEffect(() => {
console.log(`组件 ${WrappedComponent.name} 已挂载`);
return () => console.log(`组件 ${WrappedComponent.name} 将卸载`);
}, []);
useEffect(() => {
console.log(`组件 ${WrappedComponent.name} 已更新`, props);
});
return <WrappedComponent {...props} />;
};
}
// 2. 条件渲染 HOC
function withAuthentication(WrappedComponent) {
return function WithAuthentication(props) {
const { isLoggedIn } = useAuth();
if (!isLoggedIn) {
return <LoginPrompt />;
}
return <WrappedComponent {...props} />;
};
}
// 3. 数据获取 HOC
function withDataFetching(WrappedComponent, fetchUrl) {
return function WithDataFetching(props) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(fetchUrl)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
return (
<WrappedComponent
{...props}
data={data}
loading={loading}
error={error}
/>
);
};
}
// 4. 样式注入 HOC
function withStyles(WrappedComponent, styles) {
return function WithStyles(props) {
return (
<div style={styles}>
<WrappedComponent {...props} />
</div>
);
};
}
// 5. 组合多个 HOC
function withFeatures(WrappedComponent) {
return compose(
withLogging,
withAuthentication,
withDataFetching('/api/data')
)(WrappedComponent);
}
// 使用 HOC
const EnhancedComponent = withLogging(MyComponent);
const ProtectedComponent = withAuthentication(Dashboard);
const DataComponent = withDataFetching(UserList, '/api/users');
const StyledComponent = withStyles(Button, { color: 'red' });
5.2 HOC注意事项
/**
* HOC 注意事项和最佳实践
*/
// ✅ 正确:传递不相关的 props
function withSubscription(WrappedComponent, selectData) {
return function WithSubscription(props) {
const { forwardedRef, ...rest } = props;
const data = selectData(DataSource, props);
return (
<WrappedComponent
{...rest}
data={data}
ref={forwardedRef}
/>
);
};
}
// ✅ 正确:转发 ref
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const { forwardedRef, ...rest } = this.props;
return <WrappedComponent ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
// ✅ 正确:保留显示名称
function withSubscription(WrappedComponent) {
function WithSubscription(props) {
// ...
}
WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
return WithSubscription;
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
// ❌ 错误:在 render 方法中创建 HOC
function App() {
// 每次渲染都会创建新组件,导致性能问题
const EnhancedComponent = withLogging(MyComponent);
return <EnhancedComponent />;
}
// ✅ 正确:在组件外部创建
const EnhancedComponent = withLogging(MyComponent);
function App() {
return <EnhancedComponent />;
}
六、Render Props模式
6.1 Render Props基础
/**
* Render Props - 使用函数作为 children 或 render 属性
*/
// 1. 使用 children 作为函数
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
// 使用
<MouseTracker>
{({ x, y }) => (
<p>鼠标位置: ({x}, {y})</p>
)}
</MouseTracker>
// 2. 使用 render 属性
class DataProvider extends React.Component {
state = { data: null, loading: true, error: null };
componentDidMount() {
this.fetchData();
}
fetchData = async () => {
try {
const response = await fetch(this.props.url);
const data = await response.json();
this.setState({ data, loading: false });
} catch (error) {
this.setState({ error, loading: false });
}
};
render() {
return this.props.render(this.state);
}
}
// 使用
<DataProvider
url="/api/users"
render={({ data, loading, error }) => {
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <UserList users={data} />;
}}
/>
// 3. 组合多个 Render Props
function withMouse(Component) {
return function(props) {
return (
<MouseTracker>
{mouse => <Component {...props} mouse={mouse} />}
</MouseTracker>
);
};
}
// 4. Render Props vs HOC
// 两者可以互相转换,选择取决于使用场景
七、组件通信
7.1 父子组件通信
/**
* 父子组件通信模式
*/
// 1. 父 → 子:通过 props
function Parent() {
const [message, setMessage] = useState('来自父组件的消息');
return <Child message={message} />;
}
function Child({ message }) {
return <div>{message}</div>;
}
// 2. 子 → 父:通过回调函数
function ParentWithCallback() {
const [childData, setChildData] = useState(null);
const handleChildData = (data) => {
setChildData(data);
};
return (
<div>
<ChildWithCallback onSendData={handleChildData} />
<p>子组件发送的数据: {childData}</p>
</div>
);
}
function ChildWithCallback({ onSendData }) {
const sendData = () => {
onSendData('这是子组件的数据');
};
return <button onClick={sendData}>发送数据给父组件</button>;
}
// 3. 父 → 子:通过 ref 调用子组件方法
const ChildWithRef = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod: () => {
console.log('子组件方法被调用');
return '子组件返回值';
}
}));
return <div>子组件内容</div>;
});
function ParentWithRef() {
const childRef = useRef();
const callChildMethod = () => {
const result = childRef.current.childMethod();
console.log(result);
};
return (
<div>
<ChildWithRef ref={childRef} />
<button onClick={callChildMethod}>调用子组件方法</button>
</div>
);
}
7.2 兄弟组件通信
/**
* 兄弟组件通信(通过父组件)
*/
function SiblingsCommunication() {
const [sharedData, setSharedData] = useState('');
const handleDataChange = (data) => {
setSharedData(data);
};
return (
<div>
<SiblingA onDataChange={handleDataChange} />
<SiblingB data={sharedData} />
</div>
);
}
function SiblingA({ onDataChange }) {
const sendData = () => {
onDataChange('来自兄弟A的数据');
};
return <button onClick={sendData}>发送数据给兄弟B</button>;
}
function SiblingB({ data }) {
return <div>收到数据: {data}</div>;
}
八、总结
8.1 知识点回顾
| 知识点 | 说明 | 重要程度 |
|---|---|---|
| 函数组件 | 现代React推荐方式 | ⭐⭐⭐⭐⭐ |
| Props传递 | 父子组件通信基础 | ⭐⭐⭐⭐⭐ |
| PropTypes | 运行时类型检查 | ⭐⭐⭐⭐ |
| Children | 组件组合模式 | ⭐⭐⭐⭐ |
| HOC | 横切关注点复用 | ⭐⭐⭐ |
| Render Props | 共享逻辑模式 | ⭐⭐⭐ |
8.2 练习任务
// 练习1:创建一个可复用的表单字段组件
// 要求:支持标签、错误提示、验证
function FormField({ label, name, type, validation, ...props }) {
// 实现代码
return <div>...</div>;
}
// 练习2:创建一个数据表格组件
// 要求:支持列配置、排序、分页
function DataTable({ columns, data, pageSize }) {
// 实现代码
return 表...;
}
// 练习3:创建一个可拖拽的模态框组件
// 要求:支持拖拽移动、自定义大小
function DraggableModal({ title, children, onClose }) {
// 实现代码
return <div>...</div>;
}
8.3 下一节预告
下一篇将学习 State基础与useState,内容包括:
- useState基础用法
- 状态更新机制
- 对象和数组状态
- 状态提升
- 状态管理最佳实践
更多推荐


所有评论(0)