从 StencilJS 组件内部渲染 React 组件并将插槽映射到 props.children

Rendering React component from inside StencilJS component and mapping slot to props.children

我想将现有的 React 组件包装在 StencilJS 组件中。

我通过在 StencilJS componentDidRender 钩子中调用 ReactDom.render 并在渲染后将其移动到反应子元素中,我有一个 mvp 的东西,但是我想知道是否有更好的方法实现这一目标。

我不喜欢这需要宿主内部的两个包装器元素,手动将插槽移动到 React 组件中感觉很恶心。

示例代码 - 我试图在此处呈现的现有 React 组件是来自 react-bootstrap 项目的 Bootstrap 警报,仅作为示例。

import {
    Component,
    ComponentInterface,
    Host,
    h,
    Element,
    Prop,
    Event,
} from '@stencil/core';
import { Alert, AlertProps } from 'react-bootstrap';
import ReactDOM from 'react-dom';
import React from 'react';

@Component({
    tag: 'my-alert',
    styleUrl: 'my-alert.css',
    shadow: false,
})
export class MyAlert implements ComponentInterface, AlertProps {
    @Element() el: HTMLElement;

    @Prop() bsPrefix?: string;
    @Prop() variant?:
        | 'primary'
        | 'secondary'
        | 'success'
        | 'danger'
        | 'warning'
        | 'info'
        | 'dark'
        | 'light';
    @Prop() dismissible?: boolean;
    @Prop() show?: boolean;
    @Event() onClose?: () => void;
    @Prop() closeLabel?: string;
    @Prop() transition?: React.ElementType;

    componentDidRender() {
        const wrapperEl = this.el.getElementsByClassName('alert-wrapper')[0];
        const slotEl = this.el.getElementsByClassName('slot-wrapper')[0];

        const alertProps: AlertProps = {
            variant: this.variant,
            dismissible: this.dismissible,
            show: this.show,
            onClose: this.onClose,
            closeLabel: this.closeLabel,
            transition: this.transition,
        };

        ReactDOM.render(
            React.createElement(
                Alert,
                alertProps,
                React.createElement('div', { className: 'tmp-react-child-el-class-probs-should-be-a-guid-or-something' })
            ),
            wrapperEl
        );

        const reactChildEl = this.el.getElementsByClassName(
            'tmp-react-child-el-class-probs-should-be-a-guid-or-something'
        )[0];
        reactChildEl.appendChild(slotEl);
    }

    render() {
        return (
            <Host>
                <div class="alert-wrapper"></div>
                <div class="slot-wrapper">
                    <slot />
                </div>
            </Host>
        );
    }
}

一些提示:

  • 您不需要 wrapperEl,您只需将 React 组件渲染到宿主元素中即可 this.el
  • 由于您没有使用 shadow,您可以克隆组件的插槽内容并将其用作 React 组件的子组件。
  • 您也可以使用 this.el.querySelector('.slot-wrapper') 代替 this.el.getElementsByClassName('slot-wrapper')[0],或使用 element reference.
  • onClose 不应用作道具名称,因为 on... 也是事件处理程序的绑定方式,例如。 G。如果您的组件发出 'click' 事件,那么您可以通过在组件上设置 onClick 处理程序来为其绑定处理程序。虽然你已经将它装饰成一个事件,但这在 afaik 上不起作用。

codesandbox.io 上的工作示例:
https://codesandbox.io/s/stencil-react-mv5p4?file=/src/components/my-alert/my-alert.tsx
(这也适用于重新渲染)

import {
  Component,
  ComponentInterface,
  Host,
  h,
  Element,
  Prop,
  Event,
} from '@stencil/core';
import { Alert, AlertProps } from 'react-bootstrap';
import ReactDOM from 'react-dom';
import React from 'react';

@Component({
  tag: 'my-alert',
  shadow: false,
})
export class MyAlert implements ComponentInterface {
  @Element() host: HTMLMyAlertElement;

  @Prop() bsPrefix?: string;
  @Prop() variant?:
    | 'primary'
    | 'secondary'
    | 'success'
    | 'danger'
    | 'warning'
    | 'info'
    | 'dark'
    | 'light';
  @Prop() dismissible?: boolean;
  @Prop() show?: boolean;
  @Prop() closeHandler?: () => void;
  @Prop() closeLabel?: string;
  @Prop() transition?: React.ElementType;

  originalContent: any[];

  componentDidLoad() {
    // clone the original (slotted) content
    this.originalContent = Array.from(this.host.childNodes).map(node =>
      node.cloneNode(true),
    );

    this.componentDidUpdate();
  }

  componentDidUpdate() {
    const alertProps: AlertProps = {
      variant: this.variant,
      dismissible: this.dismissible,
      show: this.show,
      onClose: this.closeHandler,
      closeLabel: this.closeLabel,
      transition: this.transition,
      ref: el => el?.append(...this.originalContent), // content injected here
    };

    ReactDOM.render(React.createElement(Alert, alertProps), this.host);
  }

  render() {
    return (
      <Host>
        <slot />
      </Host>
    );
  }
}