当前位置: 首页 > news >正文

06-高级模式与实战项目——01. Render Props - 共享渲染逻辑

01. Render Props - 共享渲染逻辑

概述

Render Props 是一种 React 组件模式,通过将一个函数作为 prop传递给组件,让组件决定如何渲染。这个函数接收组件的内部状态作为参数,返回要渲染的 UI。

维度内容
What使用函数作为 prop 来共享渲染逻辑的模式
Why实现代码复用,将渲染逻辑与业务逻辑分离
When需要共享组件行为但渲染方式不同时
Where组件内部通过 children 或 render prop 接收函数
Who需要高度复用逻辑的开发者
How<DataProvider render={(data) => <UI data={data} />} />

1. 什么是 Render Props

1.1 基本概念

Render Props 是指一个组件接收一个函数作为 prop,这个函数返回 React 元素。

// Render Props 模式 <MouseTracker render={(mouse) => ( <div> 鼠标位置: {mouse.x}, {mouse.y} </div> )} />

1.2 为什么需要 Render Props

// ❌ 问题:重复的鼠标跟踪逻辑 function ComponentA() { const [mouse, setMouse] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMove = (e) => setMouse({ x: e.clientX, y: e.clientY }); window.addEventListener('mousemove', handleMove); return () => window.removeEventListener('mousemove', handleMove); }, []); return <div>鼠标位置: {mouse.x}, {mouse.y}</div>; } function ComponentB() { // 同样的逻辑重复写一遍 const [mouse, setMouse] = useState({ x: 0, y: 0 }); // ... 重复代码 }
// ✅ 使用 Render Props 复用逻辑 function MouseTracker({ render }) { const [mouse, setMouse] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMove = (e) => setMouse({ x: e.clientX, y: e.clientY }); window.addEventListener('mousemove', handleMove); return () => window.removeEventListener('mousemove', handleMove); }, []); return render(mouse); } // 使用 <MouseTracker render={(mouse) => <div>位置: {mouse.x}, {mouse.y}</div>} /> <MouseTracker render={(mouse) => <img style={{ left: mouse.x, top: mouse.y }} />} />

2. 基础示例

2.1 使用 render prop

// 数据获取组件 function DataFetcher({ url, render }) { 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 render({ data, loading, error }); } // 使用 <DataFetcher url="https://api.example.com/users" render={({ data, loading, error }) => { if (loading) return <div>加载中...</div>; if (error) return <div>错误: {error.message}</div>; return <UserList users={data} />; }} />

2.2 使用 children 作为函数

// 使用 children 作为 render prop 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 }); } // 使用 <DataFetcher url="https://api.example.com/users"> {({ data, loading, error }) => { if (loading) return <div>加载中...</div>; if (error) return <div>错误: {error.message}</div>; return <UserList users={data} />; }} </DataFetcher>

3. 实际应用场景

3.1 鼠标位置跟踪

function MouseTracker({ children }) { const [position, setPosition] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (e) => { setPosition({ x: e.clientX, y: e.clientY }); }; window.addEventListener('mousemove', handleMouseMove); return () => window.removeEventListener('mousemove', handleMouseMove); }, []); return children(position); } // 显示坐标 <MouseTracker> {({ x, y }) => <div>鼠标位置: ({x}, {y})</div>} </MouseTracker> // 跟随鼠标的图片 <MouseTracker> {({ x, y }) => ( <img src="/cursor.png" style={{ position: 'absolute', left: x, top: y }} /> )} </MouseTracker>

3.2 窗口尺寸监听

function WindowSize({ children }) { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight, }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return children(size); } // 使用 <WindowSize> {({ width, height }) => ( <div> 窗口大小: {width} x {height} </div> )} </WindowSize>

3.3 滚动位置监听

function ScrollPosition({ children }) { const [scroll, setScroll] = useState({ x: 0, y: 0 }); useEffect(() => { const handleScroll = () => { setScroll({ x: window.scrollX, y: window.scrollY, }); }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []); return children(scroll); } // 使用 <ScrollPosition> {({ x, y }) => ( <div> 滚动位置: ({x}, {y}) <ProgressBar progress={y / (document.body.scrollHeight - window.innerHeight)} /> </div> )} </ScrollPosition>

3.4 表单状态管理

function Form({ children, onSubmit }) { const [values, setValues] = useState({}); const [errors, setErrors] = useState({}); const [touched, setTouched] = useState({}); const handleChange = (e) => { const { name, value } = e.target; setValues(prev => ({ ...prev, [name]: value })); }; const handleBlur = (e) => { const { name } = e.target; setTouched(prev => ({ ...prev, [name]: true })); }; const handleSubmit = (e) => { e.preventDefault(); onSubmit(values); }; return children({ values, errors, touched, handleChange, handleBlur, handleSubmit, }); } // 使用 <Form onSubmit={(values) => console.log(values)}> {({ values, handleChange, handleSubmit }) => ( <form onSubmit={handleSubmit}> <input name="email" value={values.email || ''} onChange={handleChange} placeholder="邮箱" /> <input name="password" type="password" value={values.password || ''} onChange={handleChange} placeholder="密码" /> <button type="submit">登录</button> </form> )} </Form>

4. Render Props vs HOC

特性Render PropsHOC
代码复用通过函数传递通过组件包装
命名冲突可能冲突
组合性嵌套较深可链式调用
性能可能内联函数可能需要 memo
调试组件树清晰多层包装
// Render Props <MouseTracker> {(mouse) => ( <WindowSize> {(size) => ( <div>鼠标: {mouse.x}, 窗口: {size.width}</div> )} </WindowSize> )} </MouseTracker> // HOC const EnhancedComponent = withMouse(withWindowSize(MyComponent));

5. 完整示例:数据表格组件

// 可配置的数据表格组件 function DataTable({ fetchData, renderTable, pageSize = 10 }) { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); useEffect(() => { setLoading(true); fetchData({ page, pageSize }) .then(({ data, total }) => { setData(data); setTotal(total); setLoading(false); }); }, [page, pageSize, fetchData]); const nextPage = () => setPage(p => p + 1); const prevPage = () => setPage(p => Math.max(1, p - 1)); return renderTable({ data, loading, page, total, totalPages: Math.ceil(total / pageSize), nextPage, prevPage, }); } // 使用 <DataTable fetchData={({ page, pageSize }) => fetch(`/api/users?page=${page}&limit=${pageSize}`).then(res => res.json()) } renderTable={({ data, loading, page, totalPages, nextPage, prevPage }) => ( <div> {loading ? ( <div>加载中...</div> ) : ( <> <table> <thead> <tr><th>ID</th><th>姓名</th><th>邮箱</th></tr> </thead> <tbody> {data.map(user => ( <tr key={user.id}> <td>{user.id}</td> <td>{user.name}</td> <td>{user.email}</td> </tr> ))} </tbody> </table> <div> <button onClick={prevPage} disabled={page === 1}>上一页</button> <span>第 {page} / {totalPages} 页</span> <button onClick={nextPage} disabled={page === totalPages}>下一页</button> </div> </> )} </div> )} />

6. 总结

核心要点

要点说明
核心价值高度复用组件逻辑
实现方式render prop 或 children 函数
适用场景数据获取、鼠标跟踪、表单状态
替代方案HOC、自定义 Hooks

记忆口诀

Render Props 函数传,逻辑复用不困难
children 也能当函数,灵活渲染随便换


7. 相关资源

  • React Render Props 文档
  • Render Props 模式

http://www.gsyq.cn/news/1644345.html

相关文章:

  • TFT-LCD 驱动架构对比:4 种 Cs 存储电容布局的优缺点与选型指南
  • 私密科普:女性经后淋漓不尽,别当成普通经期残留
  • 机房故障换机后应急验证:24 小时 SpeedCE 点检 SOP
  • AI编程助手实战指南:从原理到应用,GitHub Copilot与Cursor深度测评
  • 白话MVP
  • Cline 配置 Claude Sonnet 5 实战指南:思考深度调优与切换 Fable 5 的时机
  • Redis--Redis分布式系统的原理与实操
  • 图最短路评测:Dijkstra 对了,也可能用错场景
  • 74LS73 异步计数器设计实战:2片芯片实现4位二进制与8421BCD电路对比
  • [特殊字符] Git 协作指南
  • Claude Code的完美国产替代小米 MiMo Code安装指南
  • CameraGraph™全域相机拓扑推理引擎 视频孪生跨镜目标连续追踪核心支撑 空间相机关系张量建模:根治跨镜头目标ID跳变、身份混淆底层算法拆解
  • 2025反爬系统深度解析:从Canvas指纹到AI行为画像的攻防实战
  • ML预测半导体良品率——样本缺失值模式分析(Python+Pandas+Matplotlib)
  • 想了解实力强的陕西GEO优化流程收费情况?这里有答案!
  • WebPShop技术方案:Photoshop插件如何填补WebP动画与专业编码的市场空白
  • 企业级低代码平台技术架构解析:从零代码搭建到异构系统深度集成
  • 【242期】QtScrcpy手机投屏控制的天花板,支持多设备群控!
  • LINQ to SQL、NHibernate比较(一)-- LINQ和NHibernate初体验
  • YOLOv10模型改进-Neck改进-第68篇:YOLOv10改进策略【Neck】| CSPPAN改进
  • Video2X:用AI魔法让模糊视频重获新生
  • 什么是相机标定
  • AI Agent框架:从模型驱动到任务执行的关键工程化实践
  • 059、RealBasicVSR 实战:真实场景视频超分的退化建模与优化技巧
  • 信息论与编码课程调研报告:连续AWGN信道中香农容量极限的数学推导与MATLAB仿真实现(P124302067 吴晨晨,P124302076 吕欣欣)
  • AI编程接单实战复盘:Claude Code 4天完成电商开票系统迭代,5000元私活全过程
  • Dell PERC H330/H730 RAID 卡实战:R730 创建 RAID-5 与删除配置 12 步详解
  • 电话机器人厂家哪个好
  • 德明利:从布头生意到整布豪赌,存储赛道的独特玩家能否再赢一局?
  • 第2章 异常