JS进阶(一)高阶函数HOF和高阶组件HOC(Higher Order Func/Comp)

一、什么是高阶函数(组件),作用是什么?

子类使用父类的方法可以通过继承的方式实现,那无关联组件通信(redux)、父类使用子类方法(反向继承)呢
为了解决类(函数)功能交叉/功能复用等问题,通过传入类/函数返回类/函数(继承)的方式使得类拥有自身未定义的方法。

例如react-redux的connect方法使用了高阶组件:

React Redux的connect:

const HOC = connnect(mapStateToProps)(Comp);
// connect为柯里化函数  实际为 =>
function connect(mapStateToProps) {
  // ...
  return function(Comp) {
    // ...
  }
}
// 使用箭头函数则为
const connect = mapStateToProps => Comp => {...};

二、通过高阶函数实现两个无关函数的通信

需求介绍

存在一个类SubClass(子类),该类范围内有数据state对象,且有setState和getState两个函数方法。现在希望通过SupClass1(超/父类1)去调用SubClass(子类)的setState方法,并在SupClass2(超/父类2)里通过getState方法输出结果。

注意,子为sub,父为sup

文件目录

├  ├── SubClass.js              # 子类
├  ├── SupClass1.js             # 父类1
├  ├── SupClass2.js             # 父类2
├── index.html

SubClass类增加数据state,并赋予查询修改的能力

// SubClass.js
class SubClass {
  constructor(args = {}) {
    this.state = {
      ...args,
    };
  }
  // 赋值时需要提供键和值
  setState = (key, val) => {
    this.state = {
      [key]: val,
    };
  };
  getState = (key) => {
    if (key in this.state) {
      return this.state[key];
    }
    // 当然我们希望严谨点
    const err = '无效key值';
    throw err;
  };
}

我们试试SubClass功能如何

// index.html
const subcls = new SubClass({name: 'xiaobe'});
const res = subCls.getState('name');
console.log('res', res);
// 输出xiaobe,妥妥的

接下来我们给SupClass1赋予setState的能力

class SuperClass1 {
  set(key, val) {
    // SuperClass1里没有setState方法!
    this.setState(key, val);
  }
}

如果直接执行类里的get方法肯定是会出错的。所以我们需要先对SupClass1做点事情。

需要给SuperClass1类里增加方法setState,可以使用继承

// SuperClass1.js
class SuperClass1 extends SubClass {
  constructor(props) {
    super(props);
  }
  set(key, val) {
    // 父类1上使用子类的setState方法
    this.setState(key, val);
  }
}

// index.html
const supCls1 = new SuperClass1({name: 'sup-xiaobe'});
const res = supCls1.getState('name');
console.log(res);
// 也能输出sup-xiaobe

但如果单纯使用继承的方式会造成很多的麻烦。例如子类和父类如果有同名方法,默认子类会覆盖基类(父类的其他叫法)的同名方法,如果基类方法使用了函数绑定或箭头函数,其this的指向就改变了,指向了基类,导致自身同名方法失效。

因此我们还是需要通过高阶组件实现;

首先我们先给子类SubClass增加一个HOC入口

class SubClass {
  // ...
  HOC(cls) {
    // 需要调用SubClass类的方法,所以需要存一份其this
    const sub_this = this;
    // 父类除了以下新增的两个方法,其他无任何变化地返回!
    return class extends cls {
      constructor(props) {
        super(props);
        // 此处this指向该子类,sub_this指向SubClass类
        this.getState = sub_this.getState;
        this.setState = sub_this.setState;
      }
    }
  }
  // ...
}

接着我们来父类1SupClass1实例化前升级(调用HOC)!

// index.html
const subCls = new SubClass();
// 在子类实例化后给父类加上HOC方法
const supClsHoc1 = subCls.HOC(SuperClass1);
// 实例化父类
const supCls1 = new supClsHoc1();
// 重新定义state.name
supCls1.set('name', 'sup-xiaobe');

console.log(supCls.getState('name'));
// 输出sup-xiaobe

同理地完成SupClass2

// SupClass2.js
class SuperClass2 {
  get(key) {
    return this.getState(key);
  }
}

// 最终的index.html
const subCls = new SubClass({name: 'xiaobe'});
const supClsHoc1 = subCls.HOC(SuperClass1);
const supClsHoc2 = subCls.HOC(SuperClass2);
const supCls1 = new supClsHoc1();
const supCls2 = new supClsHoc2(); 

supCls1.set('name', 'sup-xiaobe');
const res = supCls2.get('name');
console.log('res', res);

这么一个基础简单的组件通信就完成了。

根据这个思路可以封装一个类似全局变量的Store.js

思考个问题🤔

getState和setState方法里使用的this指向?

我先把SubClass的完整代码列下来

class SubClass {
  constructor(args = {}) {
    this.state = {
      ...args,
    };
  }
  // 使用了箭头函数!
  setState = (key, val) => {
    this.state = {
      [key]: val,
    };
  };
  // 使用了箭头函数!
  getState = (key) => {
    if (key in this.state) {
      return this.state[key];
    }
    return "";
  };
  HOC(cls) {
    const sub_this = this;
    return class extends cls {
      constructor(props) {
        super(props);
        // 此处this指向该子类,sub_this指向SubClass类
        this.getState = sub_this.getState;
        this.setState = sub_this.setState;
      }
    }
  }
}

可以发现凡是在方法内使用了类里的数据我都使用了箭头函数,我们想想,如果不使用箭头函数this指向会出现什么问题?

getState(key) {
  // ...
}

我们执行

const subCls = new SubClass({name: 'xiaobe'});
const supClsHoc2 = subCls.HOC(SuperClass2); 
const supCls2 = new supClsHoc2(); 
console.log('supCls2', supCls2.get('name'));

会发现浏览器直接给报错:
Cannot use 'in' operator to search for 'name' in undefined

因为在SuperClass2类里根本不存在state!换句话说即this.state = undefined,在undefined里使用对象方法,当然出错。

找到问题所在(我们需要的数据/方法在SubClass类里),我们有以下几种解决方法:

// 1.在HOC方法里传入state
this.state = sub_this.state;

// 2.getState使用箭头函数,使得this永远指向SubClass方法
getState = key => { ... };

// 3.将传入的getState方法this绑定到SubClass类上
this.getState = sub_this.getState.bind(sub_this);

这么一来就解决了this指向错误的问题,关于this的全面解析,大家可以看我的这篇文章:

--- 后面介绍

React HOC

posted @ 2020-01-05 13:57  xiaobe  阅读(2110)  评论(0编辑  收藏  举报