从 StencilJS 组件内部渲染 React 组件并将插槽映射到 props.children [英] Rendering React component from inside StencilJS component and mapping slot to props.children
问题描述
我想将现有的 React 组件包装在 StencilJS 组件中.
我通过在 StencilJS componentDidRender
钩子内调用 ReactDom.render 并在渲染后将其移动到 react 子元素中来工作,但我想知道是否有更好的方法实现这一目标.
我不喜欢这需要宿主内部的两个包装器元素,手动将插槽移动到 React 组件中感觉很恶心.
示例代码 - 我在这里尝试渲染的现有 React 组件是来自 react-bootstrap 项目的 Bootstrap Alert,仅作为示例.
import {成分,组件接口,主持人,H,元素,支柱,事件,来自'@stencil/core';从'react-bootstrap'导入{ Alert, AlertProps };从 'react-dom' 导入 ReactDOM;从反应"导入反应;@成分({标签:'我的警报',styleUrl: 'my-alert.css',阴影:假,})导出类 MyAlert 实现了 ComponentInterface,AlertProps {@Element() el: HTMLElement;@Prop() bsPrefix?: 字符串;@Prop() 变体?:|'基本的'|'次要'|'成功'|'危险'|'警告'|'信息'|'黑暗的'|'光';@Prop() 可以解雇吗?:布尔值;@Prop() 显示?:布尔值;@Event() onClose?: () =>空白;@Prop() closeLabel?: 字符串;@Prop() 转换?:React.ElementType;componentDidRender() {const wrapperEl = this.el.getElementsByClassName('alert-wrapper')[0];const slotEl = this.el.getElementsByClassName('slot-wrapper')[0];const alertProps: AlertProps = {变体:this.variant,可解雇:this.dismissible,显示:this.show,onClose: this.onClose,closeLabel: this.closeLabel,过渡:this.transition,};ReactDOM.render(React.createElement(警报,警报道具,React.createElement('div', { className: 'tmp-react-child-el-class-probs-should-be-a-guid-or-something' })),包装器El);const reactChildEl = this.el.getElementsByClassName('tmp-react-child-el-class-probs-should-be-a-guid-or-something')[0];reactChildEl.appendChild(slotEl);}使成为() {返回 (<主机><div class="alert-wrapper"></div><div class="slot-wrapper"><插槽/>
</主机>);}}
一些提示:
- 你不需要
wrapperEl
,你可以将你的 React 组件渲染到宿主元素this.el
中. - 由于您没有使用
shadow
,您可以克隆组件的插槽内容并将其用作 react 组件的子项. - 除了
this.el.getElementsByClassName('slot-wrapper')[0]
你也可以使用this.el.querySelector('.slot-wrapper')
>,或使用 元素引用. onClose
不应用作道具名称,因为on...
也是事件处理程序的绑定方式,例如.G.如果您的组件发出'click'
事件,那么您可以通过在组件上设置onClick
处理程序来为其绑定处理程序.虽然你已经把它装饰成一个事件,但那是行不通的.
codesandbox.io 上的工作示例:
https://codesandbox.io/s/stencil-react-mv5p4?file=/src/components/my-alert/my-alert.tsx
(这也适用于重新渲染)
import {成分,组件接口,主持人,H,元素,支柱,事件,来自'@stencil/core';从'react-bootstrap'导入{ Alert, AlertProps };从 'react-dom' 导入 ReactDOM;从反应"导入反应;@成分({标签:'我的警报',阴影:假,})导出类 MyAlert 实现了 ComponentInterface {@Element() 主机:HTMLMyAlertElement;@Prop() bsPrefix?: 字符串;@Prop() 变体?:|'基本的'|'次要'|'成功'|'危险'|'警告'|'信息'|'黑暗的'|'光';@Prop() 可以解雇吗?:布尔值;@Prop() 显示?:布尔值;@Prop() closeHandler?: () =>空白;@Prop() closeLabel?: 字符串;@Prop() 转换?:React.ElementType;原始内容:任何[];componentDidLoad() {//克隆原始(带槽的)内容this.originalContent = Array.from(this.host.childNodes).map(node =>node.cloneNode(true),);this.componentDidUpdate();}componentDidUpdate() {const alertProps: AlertProps = {变体:this.variant,可解雇:this.dismissible,显示:this.show,onClose: this.closeHandler,closeLabel: this.closeLabel,过渡:this.transition,参考:el =>el?.append(...this.originalContent),//这里注入的内容};ReactDOM.render(React.createElement(Alert, alertProps), this.host);}使成为() {返回 (<主机><插槽/></主机>);}}
I would like to wrap an existing React component inside a StencilJS component.
I have a kinda mvp thing working by calling ReactDom.render inside the StencilJS componentDidRender
hook and after rendering moving the into the react child element, however I'm wondering if there is a nicer way to achieve this.
I don't like that this requires two wrapper elements inside the host, and manually moving the slot into the React component feels pretty icky.
Example code - the existing react component I am trying to render here is a Bootstrap Alert from the react-bootstrap project, just as an example.
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>
);
}
}
Just a couple tips:
- You don't need the
wrapperEl
, you can just render your react component into the host elementthis.el
instead. - Since you're not using
shadow
, you can clone your component's slot content and use that as the react component's children. - Instead of
this.el.getElementsByClassName('slot-wrapper')[0]
you can also usethis.el.querySelector('.slot-wrapper')
, or use an element reference. onClose
shouldn't be used as a prop name becauseon...
is also how event handlers are bound, e. g. if your component emits a'click'
event, then you can bind a handler for it by setting anonClick
handler on the component. You've decorated it as an event though but that doesn't work afaik.
Working example on codesandbox.io:
https://codesandbox.io/s/stencil-react-mv5p4?file=/src/components/my-alert/my-alert.tsx
(this will also work with respect to re-rendering)
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>
);
}
}
这篇关于从 StencilJS 组件内部渲染 React 组件并将插槽映射到 props.children的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!