用于点击跟踪的高阶反应组件

jam*_*s0n 6 javascript javascript-events reactjs high-order-component

我想实现一个更高阶的反应组件,可用于轻松跟踪任何React组件上的事件(如点击).这样做的目的是轻松将点击(和其他事件)挂钩到我们的第一方分析跟踪器中.

我遇到的挑战是React合成事件系统需要事件(如onClick)绑定对DOM元素的反应,如a div.如果我正在包装的组件是自定义组件,就像通过更高阶函数实现的每个HOC一样,我的单击事件无法正确绑定.

例如,使用此HOC,onClick处理程序将触发button1,但不触发button2.

// Higher Order Component 
class Track extends React.Component {
  onClick = (e) => {
    myTracker.track(props.eventName);
  }

  render() {
    return React.Children.map(
      this.props.children,
      c => React.cloneElement(c, {
        onClick: this.onClick,
      }),
    );
  }
}

function Wrapper(props) {
  return props.children;
}

<Track eventName={'button 1 click'}>
  <button>Button 1</button>
</Track>

<Track eventName={'button 2 click'}>
  <Wrapper>
    <button>Button 2</button>
  </Wrapper>
</Track>
Run Code Online (Sandbox Code Playgroud)

CodeSandbox带有工作示例: https ://codesandbox.io/embed/pp8r8oj717

我的目标是能够像这样使用HOF(可选择作为装饰器)来跟踪任何React组件定义的点击.

export const withTracking = eventName => Component => props => {
  return (
    <Track eventName={eventName}>
      {/* Component cannot have an onClick event attached to it */}
      <Component {...props} />
    </Track>
  );
};
Run Code Online (Sandbox Code Playgroud)

我能想到的唯一解决方案是Ref在每个子节点上使用a 并在Ref填充后手动绑定我的click事件.

任何想法或其他解决方案表示赞赏!

更新: 使用remapChildren@estus'答案中的技术和更加手动的渲染包装组件的方法,我能够将其作为更高阶函数 - https://codesandbox.io/s/3rl9rn1om1

export const withTracking = eventName => Component => {
  if (typeof Component.prototype.render !== "function") {
    return props => <Track eventName={eventName}>{Component(props)}</Track>;
  }
  return class extends Component {
    render = () => <Track eventName={eventName}>{super.render()}</Track>;
  };
};
Run Code Online (Sandbox Code Playgroud)

Est*_*ask 3

通过常规方式(例如Wrapperref 或ReactDOM.findDOMNode. 为此,应递归遍历子元素。

一个例子

remapChildren(children) {
  const { onClick } = this;

  return React.Children.map(
    children,
    child => {   
      if (typeof child.type === 'string') {
        return React.cloneElement(child, { onClick });
      } else if (React.Children.count(child.props.children)) {
        return React.cloneElement(child, { children: this.remapChildren(
          child.props.children
        ) });
      }
    }
  );
}

render() {
  return this.remapChildren(this.props.children);
}
Run Code Online (Sandbox Code Playgroud)

remapChildren仅放置onClick在 DOM 元素上。请注意,此实现会跳过嵌套的 DOM 元素。