首页 > 解决方案 > React - 从高阶函数调用包装的组件回调

问题描述

我有一个包装服务调用的高阶函数。数据通过回调流式传输,我必须将其传递给包装的组件。我目前已经编写了下面的代码,其中子分配handleChange给 HOC 传递的空对象。包装的组件是一个常规的 JS 网格,因此我必须调用 api 来添加数据,而不是将其作为道具传递。有更清洁的方法吗?

    function withSubscription(WrappedComponent) {     
    return class extends React.Component {
      constructor(props) {
        super(props);       
        this.handler = {};
      }

      componentDidMount() { 
        DataSource.addChangeListener(this.handleChange);
      }

      componentWillUnmount() {
        DataSource.removeChangeListener(this.handleChange);
      }

      handleChange(row) {
        if (typeof this.handler.handleChange === "function") {
            this.handler.handleChange(row);
        }
      }

      render() { 
        return <WrappedComponent serviceHandler={this.handler} {...this.props} />;
      }
    };
  }

  class MyGrid extends React.Component {
     constructor(props) {
         super(props);
         if (props.serviceHandler !== undefined) {
             props.serviceHandler.handleChange = this.handleChange.bind(this);
         }

         this.onReady = this.onReady.bind(this);
     }

     onReady(evt) {
         this.gridApi = evt.api;
     }

     handleChange(row) {
        this.gridApi.addRow(row);
     }

     render() { 
        return <NonReactGrid onReady={this.onReady} />;
      }

  }

  const GridWithSubscription = withSubscription(MyGrid);

标签: reactjshigher-order-functions

解决方案


那个被包裹的组件应该知道handler.handleChange看起来很尴尬。

如果withSubscription可以仅限于使用有状态组件,则组件可能会暴露changeHandler钩子:

function withSubscription(WrappedComponent) {
  return class extends React.Component {
    ...
    wrappedRef = React.createRef();

    handleChange = (row) => {
      if (typeof this.wrappedRef.current.changeHandler === "function") {
        this.wrappedRef.current.changeHandler(row);
      }
    }

    render() { 
      return <WrappedComponent ref={this.wrappedRef}{...this.props} />;
    }
  };
}

class MyGrid extends React.Component {
  changeHandler = (row) => {
    ...
  }
}

const GridWithSubscription = withSubscription(MyGrid);

要使用有状态和无状态组件withSubscription,应该更通用化,以便通过 props 与包装组件交互,即注册一个回调:

function withSubscription(WrappedComponent) {
  return class extends React.Component {
    handleChange = (row) => {
      if (typeof this.changeHandler === "function") {
        this.changeHandler(row);
      }
    }

    registerChangeHandler = (cb) => {
      this.changeHandler = cb;
    }

    render() { 
      return <WrappedComponent
        registerChangeHandler={this.registerChangeHandler}
        {...this.props}
      />;
    }
  };
}

class MyGrid extends React.Component {
  constructor(props) {
    super(props);
    props.registerChangeHandler(this.changeHandler);
  }
  changeHandler = (row) => {
    ...
  }
}

如果应用程序已经使用了某种形式的事件发射器,例如 RxJS 主题,则可以使用它们来代替handler.handleChange父子之间的交互:

function withSubscription(WrappedComponent) {
  return class extends React.Component {
    changeEmitter = new Rx.Subject();

    handleChange = (row) => {
      this.changeEmitter.next(row);
    }

    render() { 
      return <WrappedComponent
        changeEmitter={this.changeEmitter}
        {...this.props}
      />;
    }
  };
}

class MyGrid extends React.Component {
  constructor(props) {
    super(props);
    this.props.changeEmitter.subscribe(this.changeHandler);
  }

  componentWillUnmount() {
    this.props.changeEmitter.unsubscribe();
  }

  changeHandler = (row) => {
    ...
  }
}

为此目的传递主题/事件发射器在 Angular 中很常见,因为框架已经强加了对 RxJS 的依赖。


推荐阅读