动手写个React高阶组件

原罪

是什么

简称HOC,全称 High Order Component。作用是给react组件增减props属性。

怎么用

为什么不先说怎么写?恩,因为你其实已经用过了,举个例子:

// App.js

import {connect} from 'react-redux';

class App extends React.Component {
  render() {}
}

export default connect()(App);

熟悉不?redux的连接器。不过笔者有洁癖,喜欢用装饰器:

// App.js

import {connect} from 'react-redux';

@connect()
export class App extends React.Component {
  render() {}
}

开始写

connect()()可以看出,connect是一个函数,返回值是个react组件。这么聪明,好佩服自己啊。

雏形

// myHoc.js
import React from 'react';

export const myHoc = () => {
  return (Wrapped) => {
    class Hoc extends React.Component {
      render() {
        return <Wrapped {...this.props}>;
      }
    }
    
    return Hoc;
  };
};

是的,高阶组件的雏形,就是函数里隐藏了一个react组件,而参数Wrapped是什么?就是下面被装饰的组件:

// App.js

@myHoc()
export class App extends React.Component {
  render() {}
}

恩恩,表现形式和redux的connect一模一样。
所以用了高阶组件后,export出去的不再是你自己写的App(Class),而是最后一个高阶。

增加props属性

好的啦,现在用myHoc给App组件加点料:

// myHoc.js
export const myHoc = () => {
  return (Wrapped) => {
    class Hoc extends React.Component {
      render() {
        return <Wrapped {...this.props} whoAmI="原罪">;
      }
    }
    
    return Hoc;
  };
};
// App.js

@myHoc()
export class App extends React.Component {
  render() {
    return <div>{this.props.whoAmI}</div>;
  }
}

放心,此刻浏览器里已经把我的名字 原罪 打印出来了。

多个高阶组件

是的,写完一个hoc之后,你就会有写第二个的需求,那就一起用呢:

// App.js

@myHoc()
@yourHoc()
@hisHoc()
@herHoc()
export class App extends React.Component {
  render() {
    return <div>{this.props.whoAmI}</div>;
  }
}

这就是笔者为啥要用装饰器的原因,简洁,看起来舒服,写起来快,我们看一下另一种写法:

class App extends React.Component {
  render() {}
}

export default myHoc()(yourHoc()(hisHoc()(herHoc()(App))));

自己体会,格式化一下吧:

class App extends React.Component {
  render() {}
}

let hoc;
hoc = herHoc()(App);
hoc = hisHoc()(hoc);
hoc = yourHoc()(hoc);
hoc = myHoc()(hoc);

export default hoc;

写得累不?来,给你条毛巾擦擦汗

带参数

对了,hoc可以接收参数,比如这样:

// App.js

@myHoc('原罪2号')
export class App extends React.Component {
  render() {
    return <div>{this.props.whoAmI}</div>;
  }
}

那高阶组件怎么接呢?

// myHoc.js

export const myHoc = (name) => {
  return (Wrapped) => {
    class Hoc extends React.Component {
      render() {
        return <Wrapped {...this.props} whoAmI={name}>;
      }
    }
    
    return Hoc;
  };
};

我把hoc接收到的参数又返还给了App组件,那现在浏览器输出的就是:原罪2号

不带参数

现在,你可能有一个大胆的插法..哦不,想法,就是@myHoc后面可以不加括号吗?是哦,看前面几个案例,都是@myHoc()。好的,看我的:

// myHoc.js

export const myHoc = (Wrapped) => {
  class Hoc extends React.Component {
    render() {
      return <Wrapped {...this.props} whoAmI="原罪">;
    }
  }
    
  return Hoc;
};
// App.js

@myHoc
export class App extends React.Component {
  render() {
    return <div>{this.props.whoAmI}</div>;
  }
}

细心的看官看一下myHoc.js和带参数的时候有什么区别。是的,少了一层回调。如果你的高阶组件不需要带参数,这样写也是很ok的。

操控原组件

你可能需要拿被装饰的组件的state数据或者执行它的方法。那么需要建立一个引用:

// myHoc.js

import React from 'react';

export const myHoc = () => {
  return (Wrapped) => {
    class Hoc extends React.Component {
      appRef = null;
      
      componentDidMount() {
        // 可以对被myHoc装饰的组件做羞羞的事情了,:)
        console.log(this.appRef);
      }
    
      render() {
        return <Wrapped {...this.props} ref={(app) => {this.appRef = app}} >;
      }
    }
    
    return Hoc;
  };
};

注意: 在多个高阶组件装饰同一个组件的情况下,此法并不奏效。你拿到的ref是上一个高阶组件的函数中临时生成的组件。而且在大多数情况下,你并不知道某个组件会被多少个高阶装饰!

总结

当项目中多处用到某个逻辑方法,但是这个逻辑不能放到util里的时候,HOC适合你。一个HOC最好只做一件事,这样维护方便

阅读 3.5k

全栈那些事
授人以鱼不如授人以渔

极客

3.2k 声望
1.2k 粉丝
0 条评论

极客

3.2k 声望
1.2k 粉丝
文章目录
宣传栏