Render Props #
一、Render Props概述 #
1.1 什么是Render Props #
Render Props 是一种在 React 组件之间共享代码的技术,使用一个值为函数的 prop 来实现。
javascript
function Mouse({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
// 使用
<Mouse render={({ x, y }) => (
<h1>Mouse position: {x}, {y}</h1>
)} />
1.2 使用场景 #
| 场景 | 说明 |
|---|---|
| 状态共享 | 多个组件共享状态 |
| 行为复用 | 复用组件行为逻辑 |
| 动态渲染 | 根据状态动态渲染内容 |
| 灵活组合 | 组件组合更灵活 |
1.3 与HOC对比 #
| 特性 | Render Props | HOC |
|---|---|---|
| 嵌套问题 | 无 | 存在 |
| 命名冲突 | 无 | 可能存在 |
| 灵活性 | 高 | 中 |
| 可读性 | 好 | 一般 |
二、基本用法 #
2.1 render prop #
javascript
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(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return render({ data, loading, error });
}
// 使用
<DataFetcher
url="/api/users"
render={({ data, loading, error }) => {
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <UserList users={data} />;
}}
/>
2.2 children prop #
javascript
function Mouse({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
{children(position)}
</div>
);
}
// 使用
<Mouse>
{({ x, y }) => (
<h1>Mouse position: {x}, {y}</h1>
)}
</Mouse>
2.3 自定义prop名 #
javascript
function Toggle({ render, children, ...props }) {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);
const renderProp = children || render;
return renderProp({ on, toggle });
}
// 使用方式一
<Toggle render={({ on, toggle }) => (
<button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
)} />
// 使用方式二
<Toggle>
{({ on, toggle }) => (
<button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
)}
</Toggle>
三、常见模式 #
3.1 鼠标追踪 #
javascript
function MouseTracker({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div
onMouseMove={handleMouseMove}
style={{ height: '300px', border: '1px solid #ccc' }}
>
{children(position)}
</div>
);
}
// 使用
function App() {
return (
<MouseTracker>
{({ x, y }) => (
<div>
<h2>追踪鼠标</h2>
<p>位置: {x}, {y}</p>
<div
style={{
position: 'absolute',
left: x,
top: y,
width: '20px',
height: '20px',
background: 'red',
borderRadius: '50%'
}}
/>
</div>
)}
</MouseTracker>
);
}
3.2 切换状态 #
javascript
function Toggle({ initial = false, children }) {
const [on, setOn] = useState(initial);
const toggle = () => setOn(!on);
const setOnState = () => setOn(true);
const setOffState = () => setOn(false);
return children({
on,
toggle,
setOn: setOnState,
setOff: setOffState
});
}
// 使用
function Modal() {
return (
<Toggle>
{({ on, toggle }) => (
<>
<button onClick={toggle}>打开弹窗</button>
{on && (
<div className="modal">
<div className="modal-content">
<h2>弹窗标题</h2>
<button onClick={toggle}>关闭</button>
</div>
</div>
)}
</>
)}
</Toggle>
);
}
3.3 表单状态 #
javascript
function Form({ initialValues, children }) {
const [values, setValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
};
const reset = () => setValues(initialValues);
return children({ values, handleChange, reset });
}
// 使用
function LoginForm() {
return (
<Form initialValues={{ email: '', password: '' }}>
{({ values, handleChange, reset }) => (
<form onSubmit={(e) => {
e.preventDefault();
console.log(values);
}}>
<input
name="email"
value={values.email}
onChange={handleChange}
/>
<input
name="password"
type="password"
value={values.password}
onChange={handleChange}
/>
<button type="submit">登录</button>
<button type="button" onClick={reset}>重置</button>
</form>
)}
</Form>
);
}
3.4 列表操作 #
javascript
function ListManager({ initialItems, children }) {
const [items, setItems] = useState(initialItems);
const addItem = (item) => {
setItems(prev => [...prev, { id: Date.now(), ...item }]);
};
const removeItem = (id) => {
setItems(prev => prev.filter(item => item.id !== id));
};
const updateItem = (id, updates) => {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, ...updates } : item
));
};
const clearItems = () => setItems([]);
return children({
items,
addItem,
removeItem,
updateItem,
clearItems
});
}
// 使用
function TodoList() {
return (
<ListManager initialItems={[]}>
{({ items, addItem, removeItem }) => (
<div>
<button onClick={() => addItem({ text: 'New Task' })}>
添加任务
</button>
<ul>
{items.map(item => (
<li key={item.id}>
{item.text}
<button onClick={() => removeItem(item.id)}>删除</button>
</li>
))}
</ul>
</div>
)}
</ListManager>
);
}
四、与Hooks结合 #
4.1 Render Props转Hooks #
javascript
// Render Props方式
function Mouse({ 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);
}
// Hooks方式
function useMouse() {
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 position;
}
// 使用
function Component() {
const { x, y } = useMouse();
return <div>{x}, {y}</div>;
}
4.2 混合使用 #
javascript
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn(prev => !prev), []);
return { on, toggle };
}
function Toggle({ initial, children }) {
const toggleState = useToggle(initial);
return children(toggleState);
}
五、最佳实践 #
5.1 提供默认渲染 #
javascript
function List({ items, renderItem, renderEmpty }) {
if (items.length === 0) {
return renderEmpty ? renderEmpty() : <div>No items</div>;
}
return (
<ul>
{items.map((item, index) => (
<li key={item.id || index}>
{renderItem(item, index)}
</li>
))}
</ul>
);
}
// 使用
<List
items={users}
renderItem={user => <span>{user.name}</span>}
renderEmpty={() => <div>No users found</div>}
/>
5.2 避免内联函数(性能优化) #
javascript
// ❌ 每次渲染创建新函数
<Mouse>
{({ x, y }) => <div>{x}, {y}</div>}
</Mouse>
// ✅ 使用useCallback
function App() {
const renderMouse = useCallback(({ x, y }) => (
<div>{x}, {y}</div>
), []);
return <Mouse>{renderMouse}</Mouse>;
}
5.3 类型检查 #
javascript
import PropTypes from 'prop-types';
function Mouse({ children, render }) {
const renderFn = children || render;
if (typeof renderFn !== 'function') {
console.error('Mouse requires a render prop');
return null;
}
// ...
}
Mouse.propTypes = {
children: PropTypes.func,
render: PropTypes.func
};
5.4 组合多个Render Props #
javascript
function App() {
return (
<Mouse>
{mouse => (
<Toggle>
{toggle => (
<div>
<p>Mouse: {mouse.x}, {mouse.y}</p>
<button onClick={toggle.toggle}>
{toggle.on ? 'ON' : 'OFF'}
</button>
</div>
)}
</Toggle>
)}
</Mouse>
);
}
六、实际案例 #
6.1 分页组件 #
javascript
function Pagination({ total, pageSize, children }) {
const [current, setCurrent] = useState(1);
const totalPages = Math.ceil(total / pageSize);
const go = (page) => {
if (page >= 1 && page <= totalPages) {
setCurrent(page);
}
};
const next = () => go(current + 1);
const prev = () => go(current - 1);
return children({
current,
totalPages,
go,
next,
prev,
hasNext: current < totalPages,
hasPrev: current > 1
});
}
// 使用
<Pagination total={100} pageSize={10}>
{({ current, totalPages, next, prev, hasNext, hasPrev }) => (
<div>
<span>Page {current} of {totalPages}</span>
<button onClick={prev} disabled={!hasPrev}>Previous</button>
<button onClick={next} disabled={!hasNext}>Next</button>
</div>
)}
</Pagination>
6.2 搜索组件 #
javascript
function Search({ data, searchKey, children }) {
const [query, setQuery] = useState('');
const results = useMemo(() => {
if (!query) return data;
return data.filter(item =>
item[searchKey].toLowerCase().includes(query.toLowerCase())
);
}, [data, query, searchKey]);
return children({
query,
setQuery,
results
});
}
// 使用
<Search data={users} searchKey="name">
{({ query, setQuery, results }) => (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<ul>
{results.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
)}
</Search>
七、总结 #
| 要点 | 说明 |
|---|---|
| 定义 | 使用函数prop共享代码 |
| 常用prop | children或render |
| 灵活性 | 高,可动态渲染 |
| 推荐程度 | 新项目优先Hooks |
核心原则:
- Render Props 提供灵活的组件复用方式
- 使用 children prop 更直观
- 新项目优先使用 Hooks
- 注意性能优化
最后更新:2026-03-26