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

发布时间:2026/7/6 3:36:17
06-高级模式与实战项目——01. Render Props - 共享渲染逻辑 01. Render Props - 共享渲染逻辑概述Render Props 是一种 React 组件模式通过将一个函数作为 prop传递给组件让组件决定如何渲染。这个函数接收组件的内部状态作为参数返回要渲染的 UI。维度内容What使用函数作为 prop 来共享渲染逻辑的模式Why实现代码复用将渲染逻辑与业务逻辑分离When需要共享组件行为但渲染方式不同时Where组件内部通过 children 或 render prop 接收函数Who需要高度复用逻辑的开发者HowDataProvider render{(data) UI data{data} /} /1. 什么是 Render Props1.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 urlhttps://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 urlhttps://api.example.com/users {({ data, loading, error }) { if (loading) return div加载中.../div; if (error) return div错误: {error.message}/div; return UserList users{data} /; }} /DataFetcher3. 实际应用场景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 }} / )} /MouseTracker3.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 )} /WindowSize3.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 )} /ScrollPosition3.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 nameemail value{values.email || } onChange{handleChange} placeholder邮箱 / input namepassword typepassword value{values.password || } onChange{handleChange} placeholder密码 / button typesubmit登录/button /form )} /Form4. 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 trthID/thth姓名/thth邮箱/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 模式