React通用带上下文的组件ID实现方案

使用React开发我们会根据需求定义各种组件,然后将组件做一定的组合/嵌套最终实现需求,在这个过程中就会经常出现这么一种情况:

假设现在有CompA,CompB,CompC三个组件,其中CompA和CompB都使用了CompC组件,现需要对CompC中的某个方法做埋点,如何确定该方法是在CompA下调用的,还是在CompB下调用的呢?

可能有些朋友会说可以通过传参来实现,CompA和CompB分别向CompC传递一个参数/标志,这种方式确实可以做到,但是实际使用却并不合理,因为组件的嵌套层级可能很深,如果通过参数一级一级传递,容易出错还不太好维护,仅仅是为了一个简单的埋点参数,有点浪费。有没有什么更好的方法呢?

一、实现

如果CompA下的CompC可获得标识CompA.CompC,CompB下的CompC可获得标识CompB.CompC, 以此类推:CompA.CompC.CompDCompB.CompC.CompD,通过这个标识就可以识别出组件使用的上下文,进而更容易调查问题的根源,现在问题变成了如何让组件自身携带上下文的ID,因为组件的嵌套层级不确定,为了更好的传递这个ID,我的第一反应是使用嵌套的context,大家都知道context允许跨层级传递数据,在一番实验后,得到了以下高阶组件:

import React from 'react';
import PropTypes from 'prop-types';

function WithComponentId(WrappedComponent, definedComponentId) {
  class Component extends React.Component {
    static childContextTypes = {
      componentId: PropTypes.string,
    };

    static contextTypes = {
      componentId: PropTypes.string,
    };

    constructor(props) {
      super(props);

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

    getChildContext() {
      const componentId = this.getComponentId();
      return {
        componentId,
      };
    }

    getComponentId() {
      const componentId = definedComponentId || WrappedComponent.name;
      if (this.context) {
        const { componentId: parentComponentId } = this.context;
        return `${parentComponentId ? parentComponentId : ''}.${componentId ? componentId : ''}`.replace(/(^\.+|\.+$)/g, '');
      }
      return componentId;
    }

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

export default WithComponentId;

上面的高阶组件接受上层context传递的组件Id并拼装新的上下文组件ID利用context传递下去,该组件接收一个组件和一个标识ID(可选)作为参数,通过context获取到上层组件的componentId并和当前传入组件的componentId进行组合拼装,得到一个新的带上下文的组件ID并利用context传递给传入组件及其子孙组件,同时向传入组件传递一个getComponentId方法用于获取带上下文的组件ID。

使用的时候仅需用上面的高阶组件包装一下目标组件即可,类似下面:

export default WithComponentId(CompA, 'CompA');
export default WithComponentId(CompB, 'CompB');
export default WithComponentId(CompC, 'CompC');
export default WithComponentId(CompD, 'CompD');

如果不传入组件ID会以传入组件的名称作为组件ID。组件包装完成后,我们就可以在任何通过高阶组件包装了的组件中通过getComponentId方法获取带上下文的组件ID并使用。

二、结语

在上面的高阶组件中,我们选择了使用context来跨层级传递数据,主要是考虑一个大的组件树中有可能部分中间态组件并不需要组件ID,但是我们又需要保证上层组件ID的传递,而且这种方式对于已有项目进行改造是比较合理的,我们只需要通过高阶组件包装我们关心的组件即可,而不需要每个组件都改到,那意味着巨大的工作量。当然对于新项目来说,如果最初的想法就是每个组件都有自己的组件ID,我们也可以在高阶组件中通过属性传值的方式来实现。

  • 支付宝二维码 支付宝
  • 微信二维码 微信
相关文章