renderProps
renderProps
<DataProvider render={data => <h1>Hello {data.target}</h1>} />
我们常常在交叉关注点(Cross-Cutting Concerns)使用
class WindowWidth extends React.Component {
constructor() {
super();
this.state = { width: 0 };
}
componentDidMount() {
this.setState({ width: window.innerWidth }, () =>
window.addEventListener("resize", ({ target }) =>
this.setState({ width: target.innerWidth })
)
);
}
render() {
return this.props.children(this.state.width);
}
}
技巧
使用Props 而非render
记住仅仅是因为这一模式被称为 “render props” 而你不必为使用该模式而用一个名为
<Mouse
children={mouse => (
<p>
The mouse position is {mouse.x}, {mouse.y}
</p>
)}
/>
// 或者直接放置到元素的内部
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
无法使用React.PureComponent
如果你在
class Mouse extends React.PureComponent {
// Same implementation as above...
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
{/*
This is bad! The value of the `render` prop will
be different on each render.
*/}
<Mouse render={mouse => <Cat mouse={mouse} />} />
</div>
);
}
}
在这样例子中,每次
class MouseTracker extends React.Component {
constructor(props) {
super(props);
// This binding ensures that `this.renderTheCat` always refers
// to the *same* function when we use it in render.
this.renderTheCat = this.renderTheCat.bind(this);
}
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}
案例
响应鼠标移动
例如,下面的组件在
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}>
<h1>Move the mouse around!</h1>
<p>
The current mouse position is ({this.state.x}, {this.state.y})
</p>
</div>
);
}
}
随着鼠标在屏幕上移动,在一个 <p>
的组件上显示它的<Mouse>
组件中封装我们需要在其他地方的行为。
// The <Mouse> component encapsulates the behavior we need...
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}>
{/* ...but how do we render something other than a <p>? */}
<p>
The current mouse position is ({this.state.x}, {this.state.y})
</p>
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse />
</div>
);
}
}
现在 <Mouse>
组件封装了所有关于监听<Cat mouse={{ x, y }}
import React from "react";
class Cat extends React.Component<{ mouse: { x: number, y: number } }> {
render() {
const mouse = this.props.mouse;
return (
<img
src="https://s2.ax1x.com/2019/12/02/QucJwn.png"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
}
}
class Mouse extends React.Component<
{
render: (mouse: { x: number, y: number }) => JSX.Element
},
{ x: number, y: number }
> {
constructor(props: any) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what <Mouse> renders,
use the `render` prop to dynamically determine what to render.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse
render={(mouse: { x: number, y: number }) => <Cat mouse={mouse} />}
/>
</div>
);
}
}
通用数据加载
我们可以用
class Fetch extends React.Component {
state = {
data: void 0,
error: void 0,
loading: false
};
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (this.props.url && this.props.url !== prevProps.url) {
this.fetchData(this.props.url);
}
}
async fetchData() {
try {
this.setState({ loading: true });
const response = await fetch(this.props.url);
const json = await response.json();
this.setState({ data: json });
this.setState({ loading: false });
} catch (err) {
this.setState({ error: err });
}
}
render() {
const { error, data, loading } = this.state;
if (loading) return <div>Loading...</div>;
if (error) return this.props.error(error);
if (data) return this.props.render(data);
else return null;
}
}
该组件的用法如下:
<Fetch
url={`url-to-product`}
render={data => <ProductDetail product={data.product} />}
error={error => <div>{error.message}</div>}
/>