可组合组件
我们先回顾一下可组合组件。在 props
对象中有一个常用的 children
属性。通过它你可以将元素从上层传递到你的组件中,这些元素对你的组件来说是未知的,但是却为组件相互组合提供了可能性。children
非常灵活,可以是字符串,组件,甚至他们的集合,就像写html
一样:
const A= ({ children }) => (
<div>
<p> A logic</p>
{ children }
</div>
)
const App = () => (
<>
<p> children 示例:</p>
<A>
组件B:<B />
组件C:<C />
</A>
</>
);
上面的组件A
就是一个可组合组件。
可共享的可组合组件
上面示例中 children
是最基本的使用方式,只是在组合层面,还没到达共享层面。如果 B
C
组件要使用 A
组件的属性,即 A
组件要共享自己的属性给B C
使用。通过 通过callBack
实现 A > APP > B&C
这种传递显然太low。这里就有一个技巧:把 children
函数化 这样便可以通过传递实现数据共享。
我们来看一个实例:一个检测浏览器在线/离线状态的共享组件,很多组件都需要通过这个组件获取浏览器是否在线:
import React from "react";
// 共享组件
class BrowserIfOnline extends React.Component {
state = {
isOnline: window.navigator.onLine
};
onOffline = () => {
this.setState({ isOnline: false });
};
onOnline = () => {
this.setState({ isOnline: true });
};
componentDidMount() {
// 事件监听
window.addEventListener("offline", this.onOffline);
window.addEventListener("online", this.onOnline);
}
componentWillUnmount() {
window.removeEventListener("offline", this.onOffline);
window.removeEventListener("online", this.onOnline);
}
render() {
return (
<>
{this.state.isOnline ? (
<div>网断已连接 ...</div>
) : (
<div>网络已断开 ...</div>
)}
{this.props.children(this.state.isOnline)}
</>
);
}
}
// 子组件
const B = ({ isOnline }) => <p>组件B:{isOnline ? "在线" : "离线"}</p>;
const C = ({ isOnline }) => <p>组件C:{isOnline ? "在线" : "离线"}</p>;
// 通过 函数化的 children 使用共享组件
class A extends React.Component {
render() {
return (
<BrowserIfOnline>
{isOnline => (
<>
<B isOnline={isOnline} />
<C isOnline={isOnline} />
</>
)}
</BrowserIfOnline>
);
}
}
export default A
Render props 技巧
render props
是 React
的 一个高级特性。指一种在 React
组件之间使用一个值为函数的 prop
共享代码/逻辑的简单技术。是一个用于告知组件需要渲染什么内容的函数。
上述可共享的可组合组件的实现,使用的便是 children
+ render props
技巧。在官方文档中介绍的 render props
使用的是另一种方式:把一个函数作为render
属性传递,而非 children
:
// 共享组件
// 通过props.render 渲染其他组件,并共享数据
class BrowserIfOnline extends React.Component {
// ......
// 这里省略其他逻辑 同上
// ......
render() {
return (
<>
{this.state.isOnline ? (
<div>网断已连接 ...</div>
) : (
<div>网络已断开 ...</div>
)}
{this.props.render(this.state.isOnline)}
</>
);
}
}
//通过`render props` 的 属性(render)方式使用共享组件
class A extends React.Component {
render() {
return (
<BrowserIfOnline
render={isOnline => (
<>
<B isOnline={isOnline} />
<C isOnline={isOnline} />
</>
)}
/>
);
}
}
render
属性是国际惯例,你也可以自定义,但最好不要。复杂场景下,你可能需要多个 render
函数。这个时候必须要重命名了,但依然建议加render
前缀,比如renderOne renderTwo
。
render props
优势
很多时候,React Hoc
可替代 render props
来实现逻辑共享。比如上面的功能用React Hoc
实现如下:
import React from "react";
// 把BrowserIfOnline组件封装成一个高阶组件 withBrowserIfOnline
const withBrowserIfOnline = comps =>
class BrowserIfOnline extends React.Component {
state = {
isOnline: window.navigator.onLine
};
onOffline = () => {
this.setState({ isOnline: false });
};
onOnline = () => {
this.setState({ isOnline: true });
};
componentDidMount() {
// 事件监听
window.addEventListener("offline", this.onOffline);
window.addEventListener("online", this.onOnline);
}
componentWillUnmount() {
window.removeEventListener("offline", this.onOffline);
window.removeEventListener("online", this.onOnline);
}
render() {
return (
<>
{this.state.isOnline ? (
<div>网断已连接 ...</div>
) : (
<div>网络已断开 ...</div>
)}
{comps.map((Comp) => <Comp isOnline={this.state.isOnline}/>)}
</>
);
}
}
const B = ({ isOnline }) => <p>组件B:{isOnline ? "在线" : "离线"}</p>;
const C = ({ isOnline }) => <p>组件C:{isOnline ? "在线" : "离线"}</p>;
const D = withBrowserIfOnline([B, C]);
export default class A extends React.Component {
render() {
return (
<D/>
);
}
}
我们使用 render props
的优点主要体现在它的灵活性。比如上述示例,我们是可以在组件 B
C
前后增加一些零碎的代码逻辑,而React Hoc
就没那么便利。
class A extends React.Component {
render() {
return (
<BrowserIfOnline
render={isOnline => (
<>
<p>组件B:</p>
<B isOnline={isOnline} />
<p>组件C:</p>
<C isOnline={isOnline} />
<p>其他信息</p>
</>
)}
/>
);
}
}