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

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 h1Hello, {props.name}!/h1; } // 使用组件 const element Welcome nameReact Learner /;1.2 组件类型对比/** * 函数组件 vs 类组件 */ // 1. 函数组件推荐 - React 16.8 function FunctionalComponent({ name, age }) { const [count, setCount] useState(0); return ( div classNamefunctional 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 classNameclass-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 h1Hello, {props.name}!/h1; } // 2. 箭头函数定义 const GreetingArrow (props) { return h1Hello, {props.name}!/h1; }; // 3. 隐式返回适用于简单组件 const GreetingImplicit (props) h1Hello, {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 classNamecard div classNamecard-header{title}/div div classNamecard-body{children}/div {footer div classNamecard-footer{footer}/div} /div ); } // 使用示例 function App() { return ( div Greeting nameReact学习者 / 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 classNameuser-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 typesubmit classNamebtn-primary disabled{false} onClick{() console.log(clicked)} 提交 /ButtonSpread // 4. 传递组件作为Props function Layout({ header, sidebar, content, footer }) { return ( div classNamelayout header{header}/header div classNamelayout-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 classNameuser-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.FCUserProfileProps ({ 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 classNamecontainer{children}/div; } // 2. 多个 children function SplitPane({ left, right }) { return ( div classNamesplit-pane div classNameleft-pane{left}/div div classNameright-pane{right}/div /div ); } // 3. 函数作为 childrenRender 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 classNamecard {title div classNamecard-header{title}/div} div classNamecard-body{children}/div {actions div classNamecard-actions{actions}/div} /div ); } // 使用 Card title{h2用户资料/h2} actions{ button编辑/button button删除/button / } p姓名: 张三/p p邮箱: zhangsanexample.com/p /Card // 2. 模态框组件 function Modal({ isOpen, onClose, title, children, footer }) { if (!isOpen) return null; return ( div classNamemodal-overlay onClick{onClose} div classNamemodal-content onClick{e e.stopPropagation()} div classNamemodal-header h3{title}/h3 button classNamemodal-close onClick{onClose}×/button /div div classNamemodal-body{children}/div {footer div classNamemodal-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 classNametabs div classNametab-titles{titles}/div div classNametab-content{activeContent}/div /div ); } function TabPane({ title, children }) { return div classNametab-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 /Tabs4.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 variantprimary {...props} /; } function DangerButton(props) { return Button variantdanger {...props} /; } function LargeButton(props) { return Button sizelarge {...props} /; } function SmallButton(props) { return Button sizesmall {...props} /; } // 3. 图标按钮 function IconButton({ icon, children, ...props }) { return ( Button {...props} span classNameicon{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 ); }五、高阶组件HOC5.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基础用法状态更新机制对象和数组状态状态提升状态管理最佳实践