React将所有事件传递给子组件

React pass all events to child component

本文关键字:组件 事件 React      更新时间:2023-09-26

在React中是否有可能将所有事件传递给子元素?

作为一个例子,我有一个自定义的按钮类,它(简化)看起来像这样:

class Button extends Component {
  
  constructor (props) {
    super(props);
    this.onClick      = this.onClick.bind(this);
  }
  
  /* .... */
  
  onClick (ev) {
    const { disabled, onClick } = this.props;
    if (!disabled) {
      onClick(ev);
    }
  }
  
  render () {
     const {
      children,
      disabled,
      type
     } = this.props;
    
    return (
      <button
        disabled={disabled}
        onClick={this.onClick}
        ref="button"
        type={type}
      >{children}</button>
  }
  
  
}

我不知道什么事件我可能想在未来使用(onMouseDown, onMouseUp, onBlur, onKeyDown, onTouchStart,等等…)

是否有可能将所有可能的事件传递给button元素而不为每个可能的事件编写prop ?

添加{…这个。props}传递给button元素并不是我想要的,因为它传递了所有的道具,而有些道具(比如本例中省略的className)不应该直接传递。

我想过克隆props对象并删除不应该直接传递的props,但这感觉像是一种hack。有人知道更干净的方法吗?

我已经编写了一个函数来遍历道具并过滤掉所有以'on'开头的属性,这是我目前为止最接近的。如果对其他人有帮助的话:

/* helpers.js */
export function filterEvents (props, ignore = []) {
  let events = {};
  for (let property in props) {
    if (props.hasOwnProperty(property)) {
      if (property.startsWith('on') && ignore.indexOf(property) === -1) {
        events[property] = props[property];
      }
    }
  }
  return events;
}
/* Tests for the filterEvents */
  
import { expect } from 'chai';
import { filterEvents } from './helpers';
describe('filterEvents', () => {
  const props = {
    className: 'someClass',
    disabled: true,
    onBlur: 'onBlur',
    onClick: 'onClick',
    onMouseDown: 'onMouseDown',
    onMouseUp: 'onMouseUp'
  };
  it('only returns keys starting with on', () => {
    const expected = {
      onBlur: 'onBlur',
      onClick: 'onClick',
      onMouseDown: 'onMouseDown',
      onMouseUp: 'onMouseUp'
    };
    expect(filterEvents(props)).to.deep.equal(expected);
  });
  it('only returns keys starting with on minus the ones in the ignore array', () => {
    const expected = {
      onBlur: 'onBlur',
      onMouseUp: 'onMouseUp'
    };
    const ignore = ['onClick', 'onMouseDown'];
    expect(filterEvents(props, ignore)).to.deep.equal(expected);
  });
});
/* Using the function inside a component */
import { filterEvents } from './helpers'; //at the top of the components file
//Inside the render method:
const events = filterEvents(this.props, ['onClick']); //don't include onClick it's handled like the questions example
return (
  <button
    disabled={this.props.disabled}
    onClick={this.onClick}
    {...events}
  >
    {this.props.children}
  </button>
);

我已经采取了Barry127的答案,并从React中添加了所有事件处理程序,并将它们放在一个对象中。

const acceptedEventHandlersForComponentValidationFunction = {
    clipBoard: [
        "onCopy",
        "onCut",
        "onPaste",
        "onCopyCapture",
        "onCutCapture",
        "onPasteCapture"
    ],
    composition: [
        "onCompositionEnd",
        "onCompositionStart",
        "onCompositionUpdate",
        "onCompositionEndCapture",
        "onCompositionStartCapture",
        "onCompositionUpdateCapture"
    ],
    keyboard: [
        "onKeyDown",
        "onKeyPress",
        "onKeyUp",
        "onKeyDownCapture",
        "onKeyPressCapture",
        "onKeyUpCapture"
    ],
    focus: ["onFocus", "onBlur", "onFocusCapture", "onBlurCapture"],
    form: [
        "onChange",
        "onInput",
        "onInvalid",
        "onReset",
        "onSubmit",
        "onChangeCapture",
        "onInputCapture",
        "onInvalidCapture",
        "onResetCapture",
        "onSubmitCapture"
    ],
    generic: ["onError", "onLoad", "onErrorCapture", "onLoadCapture"],
    mouse: [
        "onClick",
        "onContextMenu",
        "onDoubleClick",
        "onDrag",
        "onDragEnd",
        "onDragEnter",
        "onDragExit",
        "onDragLeave",
        "onDragOver",
        "onDragStart",
        "onDrop",
        "onMouseDown",
        "onMouseEnter",
        "onMouseLeave",
        "onMouseMove",
        "onMouseOut",
        "onMouseOver",
        "onMouseUp",
        "onClickCapture",
        "onContextMenuCapture",
        "onDoubleClickCapture",
        "onDragCapture",
        "onDragEndCapture",
        "onDragEnterCapture",
        "onDragExitCapture",
        "onDragLeaveCapture",
        "onDragOverCapture",
        "onDragStartCapture",
        "onDropCapture",
        "onMouseDownCapture",
        "onMouseMoveCapture",
        "onMouseOutCapture",
        "onMouseOverCapture",
        "onMouseUpCapture"
    ],
    pointer: [
        "onPointerDown",
        "onPointerMove",
        "onPointerUp",
        "onPointerCancel",
        "onGotPointerCapture",
        "onLostPointerCapture",
        "onPointerEnter",
        "onPointerLeave",
        "onPointerOver",
        "onPointerOut",
        "onPointerDownCapture",
        "onPointerMoveCapture",
        "onPointerUpCapture",
        "onPointerCancelCapture",
        "onGotPointerCaptureCapture",
        "onLostPointerCaptureCapture",
        "onPointerOverCapture",
        "onPointerOutCapture"
    ],
    selection: ["onSelect", "onSelectCapture"],
    touch: [
        "onTouchCancel",
        "onTouchEnd",
        "onTouchMove",
        "onTouchStart",
        "onTouchCancelCapture",
        "onTouchEndCapture",
        "onTouchMoveCapture",
        "onTouchStartCapture"
    ],
    ui: ["onScroll", "onScrollCapture"],
    wheel: ["onWheel", "onWheelCapture"],
    media: [
        "onAbort",
        "onCanPlay",
        "onCanPlayThrough",
        "onDurationChange",
        "onEmptied",
        "onEncrypted",
        "onEnded",
        "onError",
        "onLoadedData",
        "onLoadedMetadata",
        "onLoadStart",
        "onPause",
        "onPlay",
        "onPlaying",
        "onProgress",
        "onRateChange",
        "onSeeked",
        "onSeeking",
        "onStalled",
        "onSuspend",
        "onTimeUpdate",
        "onVolumeChange",
        "onWaiting",
        "onAbortCapture",
        "onCanPlayCapture",
        "onCanPlayThroughCapture",
        "onDurationChangeCapture",
        "onEmptiedCapture",
        "onEncryptedCapture",
        "onEndedCapture",
        "onErrorCapture",
        "onLoadedDataCapture",
        "onLoadedMetadataCapture",
        "onLoadStartCapture",
        "onPauseCapture",
        "onPlayCapture",
        "onPlayingCapture",
        "onProgressCapture",
        "onRateChangeCapture",
        "onSeekedCapture",
        "onSeekingCapture",
        "onStalledCapture",
        "onSuspendCapture",
        "onTimeUpdateCapture",
        "onVolumeChangeCapture",
        "onWaitingCapture"
    ],
    image: ["onLoad", "onError", "onLoadCapture", "onErrorCapture"],
    animation: [
        "onAnimationStart",
        "onAnimationEnd",
        "onAnimationIteration",
        "onAnimationStartCapture",
        "onAnimationEndCapture",
        "onAnimationIterationCapture"
    ],
    transition: ["onTransitionEnd", "onTransitionEndCapture"],
    other: ["onToggle", "onToggleCapture"]
}
/*
- Component props event handler vilidation
Return all valid events to be used on a component
{
    acceptedEventHandlerTypes: [
        "${event handler type}"
    ],
    eventHandlers: {
        ${event}: "${callback function}" // ${event} can contain "Capture" at the end to register the event handler for the capture phase
    }
}
*/
const validateComponentPropsEventHandlers = (
  acceptedEventHandlerTypes,
  eventHandlers = {}
) => {
  if (Object.keys(eventHandlers).length == 0) {
    return {}
  }
  // Fill eventsForSpecifiedType with only the required events
  let eventsForSpecifiedType = {}
  let eventsCount = 0
  for (const eventHandlerType in acceptedEventHandlerTypes) {
    if (
      acceptedEventHandlerTypes[eventHandlerType] in
      acceptedEventHandlersForComponentValidationFunction
    ) {
      const newEvents =
        acceptedEventHandlersForComponentValidationFunction[
          acceptedEventHandlerTypes[eventHandlerType]
        ]
      eventsForSpecifiedType[
        acceptedEventHandlerTypes[eventHandlerType]
      ] = newEvents
      eventsCount += newEvents.length
    }
  }
  // Fill events
  let events = {}
  let eventsCountCheck = 0
  const checkIfEventsCountHasBeenReached = () =>
    eventsCountCheck == eventsCount
  for (const eventHandler in eventHandlers) {
    if (checkIfEventsCountHasBeenReached()) {
      return events
    }
    // Append event handler to events object if it is recognised
    Object.values(eventsForSpecifiedType).forEach(
      (EVENT_HANDLERS) => {
        if (
          EVENT_HANDLERS.includes(eventHandler) &&
          !(eventHandler in events)
        ) {
          events[eventHandler] = eventHandlers[eventHandler]
          eventsCountCheck += 1
        }
        if (checkIfEventsCountHasBeenReached()) {
          return events
        }
      }
    )
  }
  return events
}
// Usage
const test = () => {console.log("test")}
const events = validateComponentPropsEventHandlers(["mouse"], { onClick: test })
console.log(events)
// <button {...events}>Button</button>