HOC(全称Higher-order component)是一种React的进阶使用方法,主要仍是为了便于组件的复用。HOC就是一个方法,获取一个组件,返回一个更高级的组件。html
在React开发过程当中,发现有不少状况下,组件须要被"加强",好比说给组件添加或者修改一些特定的props,一些权限的管理,或者一些其余的优化之类的。而若是这个功能是针对多个组件的,同时每个组件都写一套相同的代码,明显显得不是很明智,因此就能够考虑使用HOC。react
栗子:react-redux的connect方法就是一个HOC,他获取wrappedComponent,在connect中给wrappedComponent添加须要的props。git
HOC不只仅是一个方法,确切说应该是一个组件工厂,获取低阶组件,生成高阶组件。github
一个最简单的HOC实现是这个样子的:redux
function HOCFactory(WrappedComponent) {
return class HOC extends React.Component {
render(){
return <WrappedComponent {...this.props} />
}
}
}
复制代码
其实,除了代码复用和模块化,HOC作的其实就是劫持,因为传入的wrappedComponent是做为一个child进行渲染的,上级传入的props都是直接传给HOC的,因此HOC组件拥有很大的权限去修改props和控制渲染。bash
能够经过对传入的props进行修改,或者添加新的props来达到增删改props的效果。app
好比你想要给wrappedComponent增长一个props,能够这么搞:模块化
function control(wrappedComponent) {
return class Control extends React.Component {
render(){
let props = {
...this.props,
message: "You are under control"
};
return <wrappedComponent {...props} />
}
}
}
复制代码
这样,你就能够在你的组件中使用message这个props:优化
class MyComponent extends React.Component {
render(){
return <div>{this.props.message}</div>
}
}
export default control(MyComponent);
复制代码
这里的渲染劫持并非你能控制它渲染的细节,而是控制是否去渲染。因为细节属于组件内部的render方法控制,因此你没法控制渲染细节。ui
好比,组件要在data没有加载完的时候,现实loading...,就能够这么写:
function loading(wrappedComponent) {
return class Loading extends React.Component {
render(){
if(!this.props.data) {
return <div>loading...</div>
}
return <wrappedComponent {...props} />
}
}
}
复制代码
这个样子,在父级没有传入data的时候,这一起就只会显示loading...,不会显示组件的具体内容
class MyComponent extends React.Component {
render(){
return <div>{this.props.data}</div>
}
}
export default control(MyComponent);
复制代码
最经典的就是React Redux的connect方法(具体在connectAdvanced中实现)。
经过这个HOC方法,监听redux store,而后把下级组件须要的state(经过mapStateToProps获取)和action creator(经过mapDispatchToProps获取)绑定到wrappedComponent的props上。
这个是官网上的一个示例,能够用来监控父级组件传入的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对组件进行包裹,当跳转到当前页面的时候,检查用户是否含有对应的权限。若是有的话,渲染页面。若是没有的话,跳转到其余页面(好比无权限页面,或者登录页面)。
也能够给当前组件提供权限的API,页面内部也能够进行权限的逻辑判断。
尽可能不要随意修改下级组件须要的props 之因此这么说,是由于修改父级传给下级的props是有必定风险的,可能会形成下级组件发生错误。好比,本来须要一个name的props,可是在HOC中给删掉了,那么下级组件或许就没法正常渲染,甚至报错。
之前你在父组件中使用<component ref="component"/>
的时候,你能够直接经过this.refs.component
进行获取。可是由于这里的component通过HOC的封装,已是HOC里面的那个component了,因此你没法获取你想要的那个ref(wrappedComponent的ref)
。
要解决这个问题,这里有两个方法:
a) 像React Redux的connect方法同样,在里面添加一个参数,好比withRef
,组件中检查到这个flag了,就给下级组件添加一个ref,并经过getWrappedInstance方法获取。
栗子:
function HOCFactory(wrappedComponent) {
return class HOC extends React.Component {
getWrappedInstance = ()=>{
if(this.props.widthRef) {
return this.wrappedInstance;
}
}
setWrappedInstance = (ref)=>{
this.wrappedInstance = ref;
}
render(){
let props = {
...this.props
};
if(this.props.withRef) {
props.ref = this.setWrappedInstance;
}
return <wrappedComponent {...props} />
}
}
}
export default HOCFactory(MyComponent);
复制代码
这样子你就能够在父组件中这样获取MyComponent的ref值了。
class ParentCompoent extends React.Component {
doSomethingWithMyComponent(){
let instance = this.refs.child.getWrappedInstance();
// ....
}
render(){
return <MyComponent ref="child" withRef />
}
}
复制代码
b) 还有一种方法,在官网中有提到过: 父级经过传递一个方法,来获取ref,具体看栗子:
先看父级组件:
class ParentCompoent extends React.Component {
getInstance = (ref)=>{
this.wrappedInstance = ref;
}
render(){
return <MyComponent getInstance={this.getInstance} />
}
}
复制代码
HOC里面把getInstance方法看成ref的方法传入就好
function HOCFactory(wrappedComponent) {
return class HOC extends React.Component {
render(){
let props = {
...this.props
};
if(typeof this.props.getInstance === "function") {
props.ref = this.props.getInstance;
}
return <wrappedComponent {...props} />
}
}
}
export default HOCFactory(MyComponent);
复制代码
好比,你原来在Component上面绑定了一些static方法MyComponent.staticMethod = o=>o
。可是因为通过HOC的包裹,父级组件拿到的已经不是原来的组件了,因此固然没法获取到staticMethod方法了。
官网上的示例:
// 定义一个static方法
WrappedComponent.staticMethod = function() {/*...*/}
// 利用HOC包裹
const EnhancedComponent = enhance(WrappedComponent);
// 返回的方法没法获取到staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
复制代码
这里有一个解决方法,就是hoist-non-react-statics组件,这个组件会自动把全部绑定在对象上的非React方法都绑定到新的对象上:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
复制代码
当你须要作React插件的时候,HOC模型是一个很实用的模型。
但愿这篇文章能帮你对HOC有一个大概的了解和启发。
另外,这篇medium上的文章会给你更多的启发,在这篇文章中,我这里讲的被分为Props Proxy HOC,还有另一种Inheritance Inversion HOC,强烈推荐看一看。