什么是高阶组件
高阶组件(简称HOC)的目的就是实现代码的复用,它不是React的API,而是根据React的特性形成的一种开发范式。它接受一个组件作为参数并返回一个新的组件
function HOCFactory(WrappedComponent, ...args) {
return class HOC extends React.Component {
render(){
return <WrappedComponent {...this.props} />
}
}
}
HOC中并没有修改输入的组件,也没有通过继承来扩展组件,而是通过组合的方式来达到扩展的目的
即:传入HOCFactory
中的WrappedComponent
是一个有个性的组件,而HOC中返回的class
是有公共特性的组件,通过传入args
一些配置参数,返回的就是这个特性组件和公共组件的组合组件
HOC可以做什么
- 代码复用,代码模块化
- 增删改
props
- 渲染劫持
(1)代码复用,代码模块化
看这样的一个例子:
加载数据、刷新数据的行为很常见,现在把这种逻辑抽离到高阶组件当中去。完成高阶组件loadAndRefresh
,它具有以下功能:
class Post extends Component {
render () {
return (
<div>
<p>{this.props.content}</p>
<button onClick={() => this.props.refresh()}>刷新</button>
</div>
)
}
}
Post = loadAndRefresh('/post')(Post)
高阶组件loadAndRefresh
接受一个url
作为参数,然后返回一个接受组件作为参数的函数,这个新函数返回一个新的组件。新的组件渲染的时候会给传入的组件设置content
和refresh
作为props
。
getData(url)
的返回Promise,它返回一个字符串的文本,你需要通过content
的props
把它传给被包裹的组件。组件在第一次加载还有refresh
的时候会去服务器取数据。
另外,组件在加载数据的时候,content
显示数据加载中...
。而且,所有传给loadAndRefresh
返回的组件的props
要原封不动传给被包裹的组件。
最后一句话,loadAndRefresh
返回的组件就是返回的新组件Post
,被包裹的就是传入的原来的Post
,原封不动就是指需要将this.props
完全传递,利用了对象解构的语法
const getData = url => new Promise((resolve) => {
setTimeout(resolve, 2000, Date.now())
});
class Post extends Component {
render () {
return (
<div>
<p>{this.props.content}</p>
<button onClick={() => this.props.refresh()}>刷新</button>
</div>
)
}
}
const loadAndRefresh = url => (Wrapper) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
content: ''
}
};
componentDidMount () {
this.refresh()
};
async refresh() {
this.setState({
content: '数据加载中...',
});
const content = await getData(url);
this.setState({
content,
});
};
render () {
return (
<Wrapper content={this.state.content} refresh={this.refresh.bind(this)} {...this.props}/>
)
}
}
};
Post = loadAndRefresh('/post')(Post);
export default class PCHeader extends Component {
render () {
return (
<Post />
)
}
}
上面的高阶组件中,接受了Post
作为个性组件,而HOC中的公共组件部分实现的就是抽离出来的获取、刷新数据的逻辑(它也是一个组件)。
props
的传递是通过在组件上,利用对象的扩展,将所有prop
传入
(2)增删改props
HOC组件可以对传入的props
进行修改或者添加
HOC组件会在原始组件的基础上增加一些扩展功能使用的props
,这些props
不应该传入到原始组件,一般会这样处理:
function control(wrappedComponent) {
return class Control extends React.Component {
render(){
let props = {
...this.props,
message: "You are under control"
};
return <wrappedComponent {...props} />
}
}
}
(3)渲染劫持
可以在HOC中控制是否渲染(这里控制的组件整体是否被渲染,而非组建内部的细节),无法在HOC中控制渲染细节
比如,组件在data
没有加载完的时候加载LOADING
,可以在HOC中这样实现:
function loading(wrappedComponent) {
return class Loading extends React.Component {
render(){
if(!this.props.data) {
return <div>LOADING...</div>
}
return <wrappedComponent {...props} />
}
}
}
注意事项
不要在render
方法里面调用HOC方法
在render
里面每次调用HOC都会返回一个新的class
,重新渲染会让性能损耗加大。
拷贝静态方法
有的时候在组件的class
包装的静态方法,通过HOC返回的包装后的组件就没有这些静态方法。
为了保持组件使用的一致性,一般会把这些静态方法拷贝到包装后的组件上
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// Must know exactly which method(s) to copy :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
例子
logger和debugger
官网的例子,可以用来监控父级组件传入的props
的改变:
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log(`WrappedComponent: ${WrappedComponent.displayName}, Current props: `, this.props);
console.log(`WrappedComponent: ${WrappedComponent.displayName}, Next props: `, nextProps);
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
}
}
页面权限管理
可以使用HOC对组件进行包裹,当组件加载的时候,首先检验用户是否有对应的权限,如果有的话就渲染页面,如果没有就跳走