Рендеринг компонента React изнутри компонента Stencil JS и слот сопоставления с props.children - PullRequest
2 голосов
/ 27 мая 2020
• 1000 рендеринг перемещения в дочерний элемент реакции, однако мне интересно, есть ли лучший способ добиться этого.

Мне не нравится, что для этого требуются два элемента-оболочки внутри хоста и ручное перемещение слота в компонент React кажется довольно неприятным.

Пример кода - существующий компонент React, который я пытаюсь отобразить, представляет собой Bootstrap Alert из проекта response- 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>
        );
    }
}

1 Ответ

0 голосов
/ 27 мая 2020

Всего пара советов:

  • Вам не нужен wrapperEl, вы можете просто визуализировать свой компонент реакции в элемент хоста this.el.
  • Поскольку вы не используете shadow, вы можете клонировать содержимое слота вашего компонента и использовать его в качестве дочерних компонентов компонента реакции.
  • Вместо this.el.getElementsByClassName('slot-wrapper')[0] вы также можете использовать this.el.querySelector('.slot-wrapper') или ссылка на элемент .
  • onClose не должна использоваться в качестве имени свойства, потому что on... также является способом привязки обработчиков событий, например, если ваш компонент генерирует событие 'click', тогда вы можете привяжите для него обработчик, установив обработчик onClick для компонента. Вы оформили его как событие, но это не работает.

Рабочий пример на codeandbox.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>
    );
  }
}
...